Merging in from the MOODLE_15_QUIZREFACTOR branch.

The quiz module now can handle adaptive questions and can render remote questions via RQP web services.
This commit is contained in:
gustav_delius
2005-05-06 06:24:04 +00:00
parent 69b8ba4c4c
commit ee1fb969c8
75 changed files with 16272 additions and 5435 deletions
+4 -4
View File
@@ -144,7 +144,7 @@
if (empty($_GET['confirmupgrade'])) {
print_header($strdatabasechecking, $stradministration, $strdatabasechecking,
"", "", false, " ", " ");
notice_yesno(get_string('upgradesure', 'admin', $a->newversion), 'index.php?confirmupgrade=yes', 'index.php');
notice_yesno(get_string('upgradesure', 'admin', $a->newversion).'<br />'.get_string('upgradesure', 'quiz'), 'index.php?confirmupgrade=yes', 'index.php');
exit;
} else {
@@ -260,11 +260,11 @@
if (empty($CFG->siteidentifier)) { // Unique site identification code
set_config('siteidentifier', random_string(32));
}
/// Check if the guest user exists. If not, create one.
if (! record_exists("user", "username", "guest")) {
$guest->auth = "manual";
$guest->username = "guest";
$guest->auth = "manual";
$guest->username = "guest";
$guest->password = md5("guest");
$guest->firstname = addslashes(get_string("guestuser"));
$guest->lastname = " ";
+1
View File
@@ -314,6 +314,7 @@ $string['uniform'] = 'decimals, from a uniform distribution';
$string['unit'] = 'Unit';
$string['unknowntype'] = 'Question type not supported at line $a. The question will be ignored';
$string['unsupportedformulafunction'] = 'The function $a is not supported';
$string['upgradesure'] = '<div style=\"color: red;\">In particular the quiz module will perform an extensive change of the quiz tables and this upgrade has not yet been sufficiently tested. You are very strongly urged to backup your database tables before proceeding.</div>';
$string['viewallanswers'] = 'View $a completed quizzes';
$string['viewallreports'] = 'View reports for $a attempts';
$string['warningsdetected'] = '$a warning(s) detected';
+374 -224
View File
@@ -1,12 +1,31 @@
<?php // $Id$
// This page prints a particular instance of quiz
/**
* This page prints a particular instance of quiz
*
* @version $Id$
* @author Martin Dougiamas and many others. This has recently been completely
* rewritten by Alex Smith, Julian Sedding and Gustav Delius as part of
* the Serving Mathematics project
* {@link http://maths.york.ac.uk/serving_maths}
* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
* @package quiz
*/
require_once("../../config.php");
require_once("locallib.php");
optional_variable($id); // Course Module ID, or
optional_variable($q); // quiz ID
$id = optional_param('id', 0, PARAM_INT); // Course Module ID
$q = optional_param('q', 0, PARAM_INT); // or quiz ID
$page = optional_param('page', 0, PARAM_INT);
$questionids = optional_param('questionids', '');
$finishattempt = optional_param('finishattempt', 0, PARAM_BOOL);
$timeup = optional_param('timeup', 0, PARAM_BOOL); // True if form was submitted by timer.
$forcenew = optional_param('forcenew', false, PARAM_BOOL); // Teacher has requested new preview
// We treat automatically closed attempts just like normally closed attempts
if ($timeup) {
$finishattempt = 1;
}
if ($id) {
if (! $cm = get_record("course_modules", "id", $id)) {
@@ -34,66 +53,87 @@
}
require_login($course->id, false, $cm);
$isteacher = isteacher($course->id);
/// Set number for next attempt:
if ($attempts = quiz_get_user_attempts($quiz->id, $USER->id)) {
$attemptnumber = 2;
foreach ($attempts as $attempt) {
if ($attempt->attempt >= $attemptnumber) {
$attemptnumber = $attempt->attempt + 1;
}
}
} else {
// Get number for the next or unfinished attempt
if(!$attemptnumber = (int)get_field_sql('SELECT MAX(attempt)+1 FROM ' .
"{$CFG->prefix}quiz_attempts WHERE quiz = '{$quiz->id}' AND " .
"userid = '{$USER->id}' AND timefinish > 0")) {
$attemptnumber = 1;
}
$strattemptnum = get_string("attempt", "quiz", $attemptnumber);
$strattemptnum = get_string('attempt', 'quiz', $attemptnumber);
$strquizzes = get_string("modulenameplural", "quiz");
$popup = $isteacher ? 0 : $quiz->popup; // Controls whether this is shown in a javascript-protected window.
// Print the page header
if (!empty($quiz->popup)) {
/// Print the page header
if (!empty($popup)) {
define('MESSAGE_WINDOW', true); // This prevents the message window coming up
print_header("$course->shortname: ".format_string($quiz->name), '', '', '', '', false, '', '', false, '');
print_header($course->shortname.': '.format_string($quiz->name), '', '', '', '', false, '', '', false, '');
include('protect_js.php');
} else {
$strquizzes = get_string("modulenameplural", "quiz");
$strquiz = get_string("modulename", "quiz");
$strupdatemodule = isteacheredit($course->id)
? update_module_button($cm->id, $course->id, get_string('modulename', 'quiz'))
: "";
print_header_simple(format_string($quiz->name), "",
"<a href=\"index.php?id=$course->id\">$strquizzes</a> ->
<a href=\"view.php?id=$cm->id\">".format_string($quiz->name,true)."</a> -> $strattemptnum",
"", "", true);
<a href=\"view.php?id=$cm->id\">".format_string($quiz->name)."</a> -> $strattemptnum",
"", "", true, $strupdatemodule);
}
echo '<div id="overDiv" style="position:absolute; visibility:hidden; z-index:1000;"></div>'; // for overlib
/// Check availability
/// Print the quiz name heading and tabs for teacher
if ($isteacher) {
$currenttab = 'preview';
include('tabs.php');
} else {
print_heading(format_string($quiz->name));
}
if ($quiz->attempts and $attempts and count($attempts) >= $quiz->attempts) {
error("Sorry, you've had $quiz->attempts attempts already.", "view.php?id=$cm->id");
/// Check availability
if (isguest()) {
print_heading(get_string('guestsno', 'quiz'));
if (empty($popup)) {
print_footer($course);
}
exit;
}
if ($quiz->attempts and $attemptnumber > $quiz->attempts) {
error(get_string('nomoreattempts', 'quiz'), "view.php?id={$cm->id}");
}
$timenow = time();
if (($timenow < $quiz->timeopen || $timenow > $quiz->timeclose)) {
if ($isteacher) {
notify(get_string('notavailabletostudents', 'quiz'));
} else {
error(get_string('notavailable', 'quiz'), "view.php?id={$cm->id}");
}
}
/// Check subnet access
if ($quiz->subnet and !address_in_subnet(getremoteaddr(), $quiz->subnet)) {
error(get_string("subneterror", "quiz"), "view.php?id=$cm->id");
if ($isteacher) {
notify(get_string('subnetnotice', 'quiz'));
} else {
error(get_string("subneterror", "quiz"), "view.php?id=$cm->id");
}
}
/// Check password access
if ($quiz->password and empty($_POST['q'])) {
if (empty($_POST['quizpassword'])) {
print_heading(format_string($quiz->name));
print_heading(get_string("attempt", "quiz", $attemptnumber));
if (trim(strip_tags($quiz->intro))) {
print_simple_box(format_text($quiz->intro), "center");
}
echo "<br />\n";
echo "<form name=\"passwordform\" method=\"post\" action=\"attempt.php?id=$cm->id\">\n";
print_simple_box_start("center");
echo "<div align=\"center\">\n";
print_string("requirepasswordmessage", "quiz");
echo "<br /><br />\n";
@@ -103,9 +143,9 @@
print_simple_box_end();
echo "</form>\n";
if (empty($quiz->popup)) {
print_footer($course);
if (empty($popup)) {
print_footer();
}
exit;
@@ -113,226 +153,336 @@
if (strcmp($quiz->password, $_POST['quizpassword']) !== 0) {
error(get_string("passworderror", "quiz"), "view.php?id=$cm->id");
}
unset($_POST['quizpassword']); /// needed so as not to confuse later code dealing with submitted answers
}
}
/// Load attempt or create a new attempt if there is no unfinished one
if ($isteacher and $forcenew) { // teacher wants a new preview
// so we set a finish time on the current attempt (if any).
// It will then automatically be deleted below
set_field('quiz_attempts', 'timefinish', time(), 'quiz', $quiz->id, 'userid', $USER->id);
}
$attempt = get_record('quiz_attempts', 'quiz', $quiz->id,
'userid', $USER->id, 'timefinish', 0);
if (!$attempt) {
// Check if this is a preview request from a teacher
// in which case the previous previews should be deleted
if ($isteacher) {
if ($oldattempts = get_records_select('quiz_attempts', "quiz = '$quiz->id'
AND userid = '$USER->id'")) {
delete_records('quiz_attempts', 'quiz', $quiz->id, 'userid', $USER->id);
delete_records('quiz_grades', 'quiz', $quiz->id, 'userid', $USER->id);
foreach ($oldattempts as $oldattempt) {
// there should only be one but we loop just in case
delete_records('quiz_states', 'attempt', $oldattempt->id);
delete_records('quiz_newest_states', 'attemptid', $oldattempt->id);
}
}
}
// Start a new attempt and initialize the question sessions
$attempt = quiz_create_attempt($quiz, $attemptnumber);
// If this is an attempt by a teacher mark it as a preview
if ($isteacher) {
$attempt->preview = 1;
}
// Save the attempt
if (!$attempt->id = insert_record('quiz_attempts', $attempt)) {
error('Could not create new attempt');
}
// make log entries
if ($isteacher) {
add_to_log($course->id, 'quiz', 'preview',
"attempt.php?id=$cm->id",
"$quiz->id", $cm->id);
} else {
add_to_log($course->id, 'quiz', 'attempt',
"review.php?attempt=$attempt->id",
"$quiz->id", $cm->id);
}
} else {
// log continuation of attempt only if some time has lapsed
if ((time() - $attempt->timemodified) > 600) { // 10 minutes have elapsed
add_to_log($course->id, 'quiz', 'continue attempt',
"review.php?attempt=$attempt->id",
"$quiz->id", $cm->id);
}
}
/// Load all the questions and states needed by this script
// list of questions needed by page
$pagelist = quiz_questions_on_page($attempt->layout, $page);
// add all questions that are on the submitted form
if ($questionids) {
$questionlist = $pagelist.','.$questionids;
} else {
$questionlist = $pagelist;
}
if (!$questionlist) {
error(get_string('noquestionsfound', 'quiz'), 'view.php?q='.$quiz->id);
}
$sql = "SELECT q.*, i.grade AS maxgrade, i.id AS instance".
" FROM {$CFG->prefix}quiz_questions q,".
" {$CFG->prefix}quiz_question_instances i".
" WHERE i.quiz = '$quiz->id' AND q.id = i.question".
" AND q.id IN ($questionlist)";
// Load the questions
if (!$questions = get_records_sql($sql)) {
error(get_string('noquestionsfound', 'quiz'), 'view.php?q='.$quiz->id);
}
// Load the question type specific information
if (!quiz_get_question_options($questions)) {
error('Could not load question options');
}
// Restore the question sessions to their most recent states
// creating new sessions where required
if (!$states = quiz_restore_question_sessions($questions, $quiz, $attempt)) {
error('Could not restore question sessions');
}
/// Process form data /////////////////////////////////////////////////
if ($responses = data_submitted() and empty($_POST['quizpassword'])) {
// set the default event. This can be overruled by individual buttons.
$event = (array_key_exists('markall', $responses)) ? QUIZ_EVENTGRADE :
($finishattempt ? QUIZ_EVENTCLOSE : QUIZ_EVENTSAVE);
// Unset any variables we know are not responses
unset($responses->id);
unset($responses->q);
unset($responses->oldpage);
unset($responses->newpage);
unset($responses->review);
unset($responses->questionids);
unset($responses->saveattempt); // responses get saved anway
unset($responses->finishattempt); // same as $finishattempt
unset($responses->markall);
unset($responses->forcenewattempt);
// extract responses
// $actions is an array indexed by the questions ids
$actions = quiz_extract_responses($questions, $responses, $event);
// Process each question in turn
$questionidarray = explode(',', $questionids);
foreach($questionidarray as $i) {
if (!isset($actions[$i])) {
$actions[$i]->responses = array('' => '');
}
quiz_process_responses($questions[$i], $states[$i], $actions[$i], $quiz, $attempt);
quiz_save_question_session($questions[$i], $states[$i]);
}
$attempt->timemodified = time();
// We have now finished processing form data
}
/// Finish attempt if requested
if ($finishattempt) {
// Set the attempt to be finished
$attempt->timefinish = time();
// Find all the questions for this attempt for which the newest
// state is not also the newest graded state
if ($closequestions = get_records_select('quiz_newest_states',
"attemptid = $attempt->id AND new != newgraded", '', 'questionid, questionid')) {
// load all the questions
$closequestionlist = implode(',', array_keys($closequestions));
$sql = "SELECT q.*, i.grade AS maxgrade, i.id AS instance".
" FROM {$CFG->prefix}quiz_questions q,".
" {$CFG->prefix}quiz_question_instances i".
" WHERE i.quiz = '$quiz->id' AND q.id = i.question".
" AND q.id IN ($closequestionlist)";
if (!$closequestions = get_records_sql($sql)) {
error('Questions missing');
}
// Load the question type specific information
if (!quiz_get_question_options($closequestions)) {
error('Could not load question options');
}
// Restore the question sessions
if (!$closestates = quiz_restore_question_sessions($closequestions, $quiz, $attempt)) {
error('Could not restore question sessions');
}
foreach($closequestions as $key => $question) {
$action->event = QUIZ_EVENTCLOSE;
$action->responses = $closestates[$key]->responses;
quiz_process_responses($question, $closestates[$key], $action, $quiz, $attempt);
quiz_save_question_session($question, $closestates[$key]);
}
}
add_to_log($course->id, 'quiz', 'close attempt',
"review.php?attempt=$attempt->id",
"$quiz->id", $cm->id);
}
/// Update the quiz attempt and the overall grade for the quiz
if ($responses || $finishattempt) {
if (!update_record('quiz_attempts', $attempt)) {
error('Failed to save the current quiz attempt!');
}
if (($attempt->attempt > 1 || $attempt->timefinish > 0) and !$attempt->preview) {
quiz_save_best_grade($quiz);
}
}
if ($finishattempt) {
redirect('review.php?attempt='.$attempt->id);
}
/// Get time limit if any.
$timelimit = $quiz->timelimit * 60;
$unattempt = quiz_get_user_attempt_unfinished($quiz->id, $USER->id);
if($timelimit > 0) {
$timestart = $unattempt->timestart;
if($timestart) {
$timesincestart = time() - $timestart;
if ($timelimit > 0) {
$timestart = $attempt->timestart;
if ($timestart) {
$timesincestart = $timenow - $timestart;
$timerstartvalue = $timelimit - $timesincestart;
} else {
$timerstartvalue = $timelimit;
}
if ($timerstartvalue <= 0) {
$timerstartvalue = 1;
}
if(($timelimit + 60) <= $timesincestart) {
// To pass it on to quiz_grade_responses
$quiz->timesincestart = $timesincestart;
}
}
if($timelimit and $timerstartvalue <= 0) {
$timerstartvalue = 1;
}
$timenow = time();
$available = ($quiz->timeopen < $timenow and $timenow < $quiz->timeclose) || isteacher($course->id);
/// Print the quiz page ////////////////////////////////////////////////////////
/// Check to see if they are submitting answers
if ($rawanswers = data_submitted()) {
$rawanswers = (array)$rawanswers;
$shuffleorder = NULL;
unset($rawanswers["q"]); // quiz id
if (! count($rawanswers) and ! $timelimit) {
print_heading(get_string("noanswers", "quiz"));
print_continue("attempt.php?q=$quiz->id");
exit;
}
if (!$questions = get_records_list("quiz_questions", "id", $quiz->questions)) {
error("No questions found!");
}
foreach ($rawanswers as $key => $value) { // Parse input for question->response
if ($postedquestionid = quiz_extract_posted_id($key)) {
$questions[$postedquestionid]->response[$key] = trim($value);
} else if ('shuffleorder' == $key) {
$shuffleorder = explode(",", $value); // Actual order questions were given in
} else if ('navigation' == $key) {
$navigation = $value; // Determines next page to show in quiz
} else { // Useful for debugging new question types. Must be last.
error("Unrecognizable input has been posted ($key -> $value)");
}
}
/// Retrieve ->maxgrade for all questions
If (!($grades = quiz_get_question_grades($quiz->id, $quiz->questions))) {
$grades = array();
}
foreach ($grades as $qid => $grade) {
$questions[$qid]->maxgrade = $grade->grade;
}
if (isset($timesincestart)) {
$quiz->timesincestart = $timesincestart; // To pass it on to quiz_grade_responses
}
if (!$result = quiz_grade_responses($quiz, $questions, $unattempt->id)) {
error("Could not grade your quiz attempt!");
}
if ($attempt = quiz_save_attempt($quiz, $questions, $result,
$attemptnumber, 0 == $navigation)) {
if (empty($navigation)) {
add_to_log($course->id, "quiz", "submit",
"review.php?id=$cm->id&amp;attempt=$attempt->id", "$quiz->id", $cm->id);
}
} else {
notice(get_string("alreadysubmitted", "quiz"), "view.php?id=$cm->id");
if (empty($quiz->popup)) {
print_footer($course);
}
exit;
}
if (empty($navigation)) {
/// Attempt has finished
if (! quiz_save_best_grade($quiz, $USER->id)) {
error("Sorry! Could not calculate your best grade!");
}
if (empty($quiz->popup) and !$quiz->feedback) {
// No need to stop on this page, go directly to view.php
redirect('view.php?q='.$quiz->id);
}
$strgrade = get_string("grade");
$strscore = get_string("score", "quiz");
if ($quiz->grade) {
print_heading("$strscore: $result->sumgrades/$quiz->sumgrades ($result->percentage %)");
print_heading("$strgrade: $result->grade/$quiz->grade");
}
/// continue button - use javascript to close down child window if in popup
include('attempt_close_js.php');
if ($quiz->feedback) {
$quiz->shuffleanswers = false; // Never shuffle answers in feedback
/// Make sure to get all questions in case not all are shown
/// in quiz all the time.
$questions = quiz_get_attempt_questions($quiz, $attempt);
quiz_print_quiz_questions($quiz, $questions,
quiz_grade_responses($quiz, $questions, $unattempt->id),
$shuffleorder);
/// continue button - use javascript to close down child window if in popup
include('attempt_close_js.php');
}
if (empty($quiz->popup)) {
print_footer($course);
}
exit;
}
/// Print the attempt number or preview heading
if ($isteacher) {
print_heading(get_string('previewquiz', 'quiz'));
unset($buttonoptions);
$buttonoptions['q'] = $quiz->id;
$buttonoptions['forcenew'] = true;
echo '<center>';
print_single_button($CFG->wwwroot.'/mod/quiz/attempt.php', $buttonoptions, get_string('startagain', 'quiz'));
echo '</center>';
} else {
$navigation= 1;
$shuffleorder= NULL;
print_heading($strattemptnum);
}
/// Print the quiz page
if (isguest()) {
print_heading(get_string("guestsno", "quiz"));
if (empty($quiz->popup)) {
print_footer($course);
}
exit;
}
/// Actually seeing the questions marks the start of an attempt
if (isset($unattempt) && $unattempt) {
$attempt = $unattempt;
} else if ($attempt = quiz_start_attempt($quiz->id, $USER->id, $attemptnumber)) {
add_to_log($course->id, "quiz", "attempt",
"review.php?id=$cm->id&amp;attempt=$attempt->id", "$quiz->id", $cm->id);
} else {
error("Sorry! Could not start the quiz (could not save starting time)");
}
/// First print the headings and so on
print_heading(format_string($quiz->name));
if (!$available) {
error("Sorry, this quiz is not available", "view.php?id=$cm->id");
}
print_heading(get_string("attempt", "quiz", $attemptnumber));
if (trim(strip_tags($quiz->intro))) {
print_simple_box(format_text($quiz->intro), "center");
}
/// Add the javascript timer in the title bar if the closing time appears close
$secondsleft = $quiz->timeclose - time();
if ($secondsleft > 0 and $secondsleft < 24*3600) { // less than a day remaining
include("jsclock.php");
include('jsclock.php');
}
/// Include Javascript protection for this page if required
/// Start the form
if($quiz->timelimit > 0) {
// Make sure javascript is enabled for time limited quizzes
?>
<script language="javascript" type="text/javascript">
<!--
document.write("<form name=\"responseform\" method=\"post\" action=\"attempt.php\">\n");
// -->
</script>
<noscript>
<center><p><strong><?php print_string('noscript', 'quiz'); ?></strong></p></center>
</noscript>
<?php
} else {
echo "<form name=\"responseform\" method=\"post\" action=\"attempt.php\">\n";
}
if (!empty($quiz->popup)) {
include("protect_js.php");
// Add a hidden field with the quiz id
echo '<input type="hidden" name="q" value="' . s($quiz->id) . "\" />\n";
/// Print the navigation panel if required
$numpages = quiz_number_of_pages($attempt->layout);
if ($numpages > 1) {
?>
<script language="javascript" type="text/javascript">
function navigate(page) {
document.responseform.page.value=page;
document.responseform.submit();
}
</script>
<?php
echo '<input type="hidden" id="page" name="page" value="'.$page."\" />\n";
quiz_print_navigation_panel($page, $numpages);
echo "<br />\n";
}
/// Print all the questions
echo "<br />";
// Add a hidden field with questionids
echo '<input type="hidden" name="questionids" value="'.$pagelist."\" />\n";
$questions = quiz_get_attempt_questions($quiz, $attempt, true);
if ($quiz->attemptonlast && $attemptnumber >= 2 and
$quiz->attempts == 0 || !$unattempt) {
// There are unlimited attempts or it is a new attempt.
// As the attempt also builds on the last, we can here
// have the student see the scores of the pre-entered
// responses that we here will have graded:
$result = quiz_grade_responses($quiz, $questions);
$result->attemptbuildsonthelast = true;
} else {
$result = NULL;
}
// We do not show feedback or correct answers during an attempt:
$quiz->feedback = $quiz->correctanswers = false;
if (!quiz_print_quiz_questions($quiz, $questions,
$result, $shuffleorder, $navigation)) {
print_continue("view.php?id=$cm->id");
$pagequestions = explode(',', $pagelist);
$number = quiz_first_questionnumber($attempt->layout, $pagelist);
foreach ($pagequestions as $i) {
$options = quiz_get_renderoptions($quiz, $states[$i]);
// Print the question
if ($i > 0) {
echo "<br />\n";
}
quiz_print_quiz_question($questions[$i], $states[$i], $number, $quiz, $options);
quiz_save_question_session($questions[$i], $states[$i]);
$number += $questions[$i]->length;
}
/// If quiz is available and time limit is set include floating timer.
/// Print the submit buttons
if ($available and $timelimit > 0) {
$strconfirmattempt = addslashes(get_string("confirmclose", "quiz"));
$onclick = "return confirm('$strconfirmattempt')";
echo "<center>\n";
echo "<input type=\"submit\" name=\"saveattempt\" value=\"".get_string("savenosubmit", "quiz")."\" />\n";
if ($quiz->optionflags & QUIZ_ADAPTIVE) {
echo "<input type=\"submit\" name=\"markall\" value=\"".get_string("markall", "quiz")."\" />\n";
}
echo "<input type=\"submit\" name=\"finishattempt\" value=\"".get_string("finishattempt", "quiz")."\" onclick=\"$onclick\" />\n";
echo '<input type="hidden" name="timeup" value="0" />';
echo "</center>";
// Print the navigation panel if required
if ($numpages > 1) {
echo "<br />\n";
quiz_print_navigation_panel($page, $numpages);
echo '<br />';
}
// Finish the form
echo "</form>\n";
// If time limit is set include floating timer.
if ($timelimit > 0) {
require('jstimer.php');
}
if (empty($quiz->popup)) {
if (!$isteacher) {
include('attempt_close_js.php');
}
// Finish the page
if (empty($popup)) {
print_footer($course);
}
+5 -5
View File
@@ -1,15 +1,15 @@
<?php defined('MOODLE_INTERNAL') or die('Direct access to this script is forbidden.');?>
<div align="center">
<?php
if (!empty($quiz->popup)) {
<?php
if (!empty($popup)) {
?>
<script language="javascript" type="text/javascript">
<!--
document.write('<input type="button" value="<?php print_string('continue') ?>" '+
'onclick="javascript: window.opener.location.href=\'../../course/view.php?id=<?php echo $course->id ?>\'; '+
document.write('<input type="button" value="<?php print_string('closewindow') ?>" '+
'onclick="javascript: window.opener.location.href=\'view.php?id=<?php echo $cm->id ?>\'; '+
'window.close();" />');
// -->
</script>
@@ -19,7 +19,7 @@ document.write('<input type="button" value="<?php print_string('continue') ?>" '
<?php
} else {
print_single_button("../../course/view.php", array( 'id' => $course->id ), get_string('continue'));
print_single_button("view.php", array( 'id' => $cm->id ), get_string('continue'));
}
?>
</div>
+354
View File
@@ -0,0 +1,354 @@
<?php // $Id$
// This script lists student attempts
require_once("../../config.php");
require_once("locallib.php");
require_once($CFG->libdir.'/tablelib.php');
optional_variable($id); // Course Module ID, or
optional_variable($q); // quiz ID
if ($id) {
if (! $cm = get_record("course_modules", "id", $id)) {
error("Course Module ID was incorrect");
}
if (! $course = get_record("course", "id", $cm->course)) {
error("Course is misconfigured");
}
if (! $quiz = get_record("quiz", "id", $cm->instance)) {
error("Course module is incorrect");
}
} else {
if (! $quiz = get_record("quiz", "id", $q)) {
error("Course module is incorrect");
}
if (! $course = get_record("course", "id", $quiz->course)) {
error("Course is misconfigured");
}
if (! $cm = get_coursemodule_from_instance("quiz", $quiz->id, $course->id)) {
error("Course Module ID was incorrect");
}
}
require_login($course->id, false);
if (!isteacher($course->id)) {
error("You are not allowed to use this script");
}
// if no questions have been set up yet redirect to edit.php
if (!$quiz->questions and isteacheredit($course->id)) {
redirect('edit.php?quizid='.$quiz->id);
}
add_to_log($course->id, "quiz", "attempts", "attempts.php?id=$cm->id", "$quiz->id", "$cm->id");
/// Define some strings
$strquizzes = get_string("modulenameplural", "quiz");
$strquiz = get_string("modulename", "quiz");
$strreallydel = addslashes(get_string('deleteattemptcheck','quiz'));
$strnoattempts = get_string('noattempts','quiz');
$strtimeformat = get_string('strftimedatetime');
$strreviewquestion = get_string('reviewresponse', 'quiz');
/// Print the page header
print_header_simple(format_string($quiz->name), "",
"<a href=\"index.php?id=$course->id\">$strquizzes</a>
-> ".format_string($quiz->name),
"", "", true, update_module_button($cm->id, $course->id, $strquiz), navmenu($course, $cm));
/// Print the tabs
$currenttab = 'attempts';
include('tabs.php');
/// Deal with actions
$action = optional_param('action', '');
switch($action) {
case 'delete': /// Some attempts need to be deleted
// the following needs to be improved to delete all associated data as well
$attemptids = isset($_POST['attemptid']) ? $_POST['attemptid'] : array();
if(!is_array($attemptids) || empty($attemptids)) {
break;
}
foreach($attemptids as $num => $attemptid) {
if(empty($attemptid)) {
unset($attemptids[$num]);
}
}
foreach($attemptids as $attemptid) {
if ($todelete = get_record('quiz_attempts', 'id', $attemptid)) {
delete_records('quiz_attempts', 'id', $attemptid);
delete_records('quiz_states', 'attempt', $attemptid);
// Search quiz_attempts for other instances by this user.
// If none, then delete record for this quiz, this user from quiz_grades
// else recalculate best grade
$userid = $todelete->userid;
if (!record_exists('quiz_attempts', 'userid', $userid, 'quiz', $quiz->id)) {
delete_records('quiz_grades', 'userid', $userid,'quiz', $quiz->id);
} else {
quiz_save_best_grade($quiz, $userid);
}
}
}
break;
}
/// Check to see if groups are being used in this quiz
if ($groupmode = groupmode($course, $cm)) { // Groups are being used
$currentgroup = setup_and_print_groups($course, $groupmode, "attempts.php?id=$cm->id&amp;mode=overview");
} else {
$currentgroup = false;
}
/// Get all students
if ($currentgroup) {
$users = get_group_students($currentgroup);
}
else {
$users = get_course_students($course->id);
}
if(empty($users)) {
print_heading($strnoattempts);
return true;
}
/// Set table options
if(!isset($SESSION->quiz_overview_table)) {
$SESSION->quiz_overview_table = array('noattempts' => false, 'detailedmarks' => false);
}
foreach($SESSION->quiz_overview_table as $option => $value) {
$urlparam = optional_param($option, NULL);
if($urlparam === NULL) {
$$option = $value;
}
else {
$$option = $SESSION->quiz_overview_table[$option] = $urlparam;
}
}
/// Print display options
echo '<div class="controls">';
echo '<form method="post" action="attempts.php">';
echo '<p>'.get_string('displayoptions', 'quiz').': ';
echo '<input type="hidden" name="id" value="'.$cm->id.'" />';
echo '<input type="hidden" name="noattempts" value="0" />';
echo '<input type="hidden" name="detailedmarks" value="0" />';
echo '<input type="checkbox" id="checknoattempts" name="noattempts" '.($noattempts?'checked="checked" ':'').'value="1" /> <label for="checknoattempts">'.get_string('shownoattempts', 'quiz').'</label> ';
echo '<input type="checkbox" id="checkdetailedmarks" name="detailedmarks" '.($detailedmarks?'checked="checked" ':'').'value="1" /> <label for="checkdetailedmarks">'.get_string('showdetailedmarks', 'quiz').'</label> ';
echo '<input type="submit" value="'.get_string('go').'" />';
echo '</p>';
echo '</form>';
echo '</div>';
/// Define table columns
$tablecolumns = array('checkbox', 'picture', 'fullname', 'timestart', 'duration');
$tableheaders = array(NULL, '', get_string('fullname'), get_string('startedon', 'quiz'), get_string('attemptduration', 'quiz'));
if ($quiz->grade) {
$tablecolumns[] = 'sumgrades';
$tableheaders[] = get_string('grade', 'quiz').'/'.$quiz->grade;
}
if($detailedmarks) {
// we want to display marks for all questions
// Start by getting all questions
$questionlist = quiz_questions_in_quiz($quiz->questions);
$questionids = explode(',', $questionlist);
$sql = "SELECT q.*, i.grade AS maxgrade, i.id AS instance".
" FROM {$CFG->prefix}quiz_questions q,".
" {$CFG->prefix}quiz_question_instances i".
" WHERE i.quiz = '$quiz->id' AND q.id = i.question".
" AND q.id IN ($questionlist)";
if (!$questions = get_records_sql($sql)) {
error('No questions found');
}
$number = 1;
foreach($questionids as $key => $id) {
if ($questions[$id]->length) {
// Only print questions of non-zero length
$tablecolumns[] = '$'.$id;
$tableheaders[] = '#'.$number;
$questions[$id]->number = $number;
$number += $questions[$id]->length;
} else {
// get rid of zero length questions
unset($questions[$id]);
unset($questionids[$key]);
}
}
}
/// Set up the table
$table = new flexible_table('mod-quiz-report-overview-report');
$table->define_columns($tablecolumns);
$table->define_headers($tableheaders);
$table->define_baseurl($CFG->wwwroot.'/mod/quiz/attempts.php?id='.$cm->id);
$table->sortable(true);
$table->collapsible(true);
$table->initialbars(count($users)>20);
$table->column_suppress('picture');
$table->column_suppress('fullname');
$table->column_class('picture', 'picture');
$table->set_attribute('cellspacing', '0');
$table->set_attribute('id', 'attempts');
$table->set_attribute('class', 'generaltable generalbox');
// Start working -- this is necessary as soon as the niceties are over
$table->setup();
/// Construct the SQL
if($where = $table->get_sql_where()) {
$where .= ' AND ';
}
if($sort = $table->get_sql_sort()) {
$sortparts = explode(',', $sort);
$newsort = array();
$firsttime = true;
foreach($sortparts as $sortpart) {
$sortpart = trim($sortpart);
if(substr($sortpart, 0, 1) == '$') {
if($firsttime) {
$qnum = intval(substr($sortpart, 1));
$where .= '('.sql_isnull('qr.question').' OR qr.question = '.$qnum.') AND ';
$newsort[] = 'grade '.(strpos($sortpart, 'ASC')? 'ASC' : 'DESC');
$firsttime = false;
}
}
else {
$newsort[] = $sortpart;
}
}
$sort = ' ORDER BY '.implode(', ', $newsort);
}
$select = 'SELECT '.$db->Concat('u.id', '\'#\'', $db->IfNull('qa.attempt', '0')).' AS uvsa, u.id AS userid, u.firstname, u.lastname, u.picture, qa.id AS attempt, qa.sumgrades, qa.timefinish, qa.timestart, qa.timefinish - qa.timestart AS duration ';
$group = 'GROUP BY uvsa';
$sql = 'FROM '.$CFG->prefix.'user u '.
'LEFT JOIN '.$CFG->prefix.'quiz_attempts qa ON u.id = qa.userid '.
'LEFT JOIN '.$CFG->prefix.'quiz_states qr ON qr.attempt = qa.id '.
'WHERE '.$where.'u.id IN ('.implode(',', array_keys($users)).') AND ('.($noattempts ? sql_isnull('qa.quiz').' OR ' : '') . 'qa.quiz = '.$quiz->id.') ';
$total = count_records_sql('SELECT COUNT(DISTINCT('.$db->Concat('u.id', '\'#\'', $db->IfNull('qa.attempt', '0')).')) '.$sql);
$table->pagesize(10, $total);
if($table->get_page_start() !== '' && $table->get_page_size() !== '') {
$limit = ' '.sql_paging_limit($table->get_page_start(), $table->get_page_size());
}
else {
$limit = '';
}
/// Fetch the attempts
$attempts = get_records_sql($select.$sql.$group.$sort.$limit);
/// Build table rows
if(!empty($attempts)) {
foreach ($attempts as $attempt) {
$picture = print_user_picture($attempt->userid, $course->id, $attempt->picture, false, true);
$row = array(
'<input type="checkbox" name="attemptid[]" value="'.$attempt->attempt.'" />',
$picture,
'<a href="'.$CFG->wwwroot.'/user/view.php?id='.$attempt->userid.'&amp;course='.$course->id.'">'.fullname($attempt).'</a>',
empty($attempt->attempt) ? '-' : '<a href="review.php?q='.$quiz->id.'&amp;attempt='.$attempt->attempt.'">'.userdate($attempt->timestart, $strtimeformat).'</a>',
empty($attempt->attempt) ? '-' :
(empty($attempt->timefinish) ? get_string('unfinished', 'quiz') :
format_time($attempt->duration))
);
if ($quiz->grade) {
$row[] = $attempt->sumgrades === NULL ? '-' : '<a href="review.php?q='.$quiz->id.'&amp;attempt='.$attempt->attempt.'">'.format_float($attempt->sumgrades / $quiz->sumgrades * $quiz->grade,$quiz->decimalpoints).'</a>';
}
if($detailedmarks) {
if(empty($attempt->attempt)) {
foreach($questionids as $questionid) {
$row[] = '-';
}
}
else {
foreach($questionids as $questionid) {
if ($gradedstateid = get_field('quiz_newest_states', 'newgraded', 'attemptid', $attempt->attempt, 'questionid', $questionid)) {
$grade = format_float(get_field('quiz_states', 'grade', 'id', $gradedstateid), $quiz->decimalpoints);
} else {
// This is an old-style attempt
$grade = format_float(get_field('quiz_states', 'grade', 'attempt', $attempt->attempt, 'question', $questionid), $quiz->decimalpoints);
}
$row[] = link_to_popup_window ('/mod/quiz/reviewquestion.php?state='.$gradedstateid.'&amp;number='.$questions[$questionid]->number, 'reviewquestion', $grade, 450, 650, $strreviewquestion, 'none', true);
}
}
}
$table->add_data($row);
}
/// Start form
echo '<div id="tablecontainer">';
echo '<form id="attemptsform" method="post" action="attempts.php" onsubmit="var menu = document.getElementById(\'actionmenu\'); return (menu.options[menu.selectedIndex].value == \'delete\' ? \''.$strreallydel.'\' : true);">';
echo '<input type="hidden" name="id" value="'.$cm->id.'" />';
/// Print table
$table->print_html();
/// Print "Select all" etc.
echo '<table id="commands">';
echo '<tr><td>';
echo '<a href="javascript:select_all_in(\'DIV\', null, \'tablecontainer\');">'.get_string('selectall', 'quiz').'</a> / ';
echo '<a href="javascript:deselect_all_in(\'DIV\', null, \'tablecontainer\');">'.get_string('selectnone', 'quiz').'</a> ';
echo '&nbsp;&nbsp;';
$options = array('delete' => get_string('delete'));
$menu = str_replace('<select', '<select id="actionmenu"', choose_from_menu($options, 'action', '', get_string('withselected', 'quiz'), 'if(this.selectedIndex > 0) submitFormById(\'attemptsform\');', '', true));
echo $menu;
echo '<noscript id="noscriptactionmenu" style="display: inline;">';
echo '<input type="submit" value="'.get_string('go').'" /></noscript>';
echo '<script type="text/javascript">'."\n<!--\n".'document.getElementById("noscriptactionmenu").style.display = "none";'."\n-->\n".'</script>';
echo '</td></tr></table>';
/// Close form
echo '</form></div>';
}
else {
print_heading(get_string('noattemptstoshow', 'quiz'));
}
/// Print footer
print_footer($course);
?>
+57 -26
View File
@@ -90,7 +90,7 @@
// - quiz_question_grades
// - quiz_attempts
// - quiz_grades
// - quiz_responses
// - quiz_states
// This step is the standard mod backup. (course dependent).
//STEP 1. Backup categories/questions and associated structures
@@ -618,31 +618,62 @@
return $status;
}
//Backup quiz_question_grades contents (executed from quiz_backup_mods)
function backup_quiz_question_grades ($bf,$preferences,$quiz) {
//Backup quiz_question_instances contents (executed from quiz_backup_mods)
function backup_quiz_question_instances ($bf,$preferences,$quiz) {
global $CFG;
$status = true;
$quiz_question_grades = get_records("quiz_question_grades","quiz",$quiz,"id");
//If there are question_grades
if ($quiz_question_grades) {
$quiz_question_instances = get_records("quiz_question_instances","quiz",$quiz,"id");
//If there are question_instances
if ($quiz_question_instances) {
//Write start tag
$status =fwrite ($bf,start_tag("QUESTION_GRADES",4,true));
//Iterate over each question_grade
foreach ($quiz_question_grades as $que_gra) {
//Start question grade
$status =fwrite ($bf,start_tag("QUESTION_GRADE",5,true));
//Print question_grade contents
fwrite ($bf,full_tag("ID",6,false,$que_gra->id));
fwrite ($bf,full_tag("QUESTION",6,false,$que_gra->question));
fwrite ($bf,full_tag("GRADE",6,false,$que_gra->grade));
//End question grade
$status =fwrite ($bf,end_tag("QUESTION_GRADE",5,true));
$status =fwrite ($bf,start_tag("QUESTION_INSTANCES",4,true));
//Iterate over each question_instance
foreach ($quiz_question_instances as $que_ins) {
//Start question instance
$status =fwrite ($bf,start_tag("QUESTION_INSTANCE",5,true));
//Print question_instance contents
fwrite ($bf,full_tag("ID",6,false,$que_ins->id));
fwrite ($bf,full_tag("QUESTION",6,false,$que_ins->question));
fwrite ($bf,full_tag("GRADE",6,false,$que_ins->grade));
//End question instance
$status =fwrite ($bf,end_tag("QUESTION_INSTANCE",5,true));
}
//Write end tag
$status =fwrite ($bf,end_tag("QUESTION_GRADES",4,true));
$status =fwrite ($bf,end_tag("QUESTION_INSTANCES",4,true));
}
return $status;
}
//Backup quiz_question_versions contents (executed from quiz_backup_mods)
function backup_quiz_question_versions ($bf,$preferences,$quiz) {
global $CFG;
$status = true;
$quiz_question_versions = get_records("quiz_question_versions","quiz",$quiz,"id");
//If there are question_versions
if ($quiz_question_versions) {
//Write start tag
$status =fwrite ($bf,start_tag("QUESTION_VERSIONS",4,true));
//Iterate over each question_version
foreach ($quiz_question_versions as $que_ver) {
//Start question version
$status =fwrite ($bf,start_tag("QUESTION_VERSION",5,true));
//Print question_version contents
fwrite ($bf,full_tag("ID",6,false,$que_ver->id));
fwrite ($bf,full_tag("OLDQUESTION",6,false,$que_ver->oldquestion));
fwrite ($bf,full_tag("NEWQUESTION",6,false,$que_ver->newquestion));
fwrite ($bf,full_tag("USERID",6,false,$que_ver->userid));
fwrite ($bf,full_tag("TIMESTAMP",6,false,$que_ver->timestamp));
//End question version
$status =fwrite ($bf,end_tag("QUESTION_VERSION",5,true));
}
//Write end tag
$status =fwrite ($bf,end_tag("QUESTION_VERSIONS",4,true));
}
return $status;
}
@@ -732,8 +763,8 @@
fwrite ($bf,full_tag("TIMESTART",6,false,$attempt->timestart));
fwrite ($bf,full_tag("TIMEFINISH",6,false,$attempt->timefinish));
fwrite ($bf,full_tag("TIMEMODIFIED",6,false,$attempt->timemodified));
//Now write to xml the responses (in this attempt)
$status = backup_quiz_responses ($bf,$preferences,$attempt->id);
//Now write to xml the states (in this attempt)
$status = backup_quiz_states ($bf,$preferences,$attempt->id);
//End attempt
$status =fwrite ($bf,end_tag("ATTEMPT",5,true));
}
@@ -743,16 +774,16 @@
return $status;
}
//Backup quiz_responses contents (executed from backup_quiz_attempts)
function backup_quiz_responses ($bf,$preferences,$attempt) {
//Backup quiz_states contents (executed from backup_quiz_attempts)
function backup_quiz_states ($bf,$preferences,$attempt) {
global $CFG;
$status = true;
$quiz_responses = get_records("quiz_responses","attempt",$attempt,"id");
//If there are responses
if ($quiz_responses) {
$quiz_states = get_records("quiz_states","attempt",$attempt,"id");
//If there are states
if ($quiz_states) {
//Write start tag
$status =fwrite ($bf,start_tag("RESPONSES",6,true));
//Iterate over each response
@@ -769,7 +800,7 @@
$status =fwrite ($bf,end_tag("RESPONSE",7,true));
}
//Write end tag
$status =fwrite ($bf,end_tag("RESPONSES",6,true));
$status =fwrite ($bf,end_tag("STATES",6,true));
}
return $status;
}
+17 -31
View File
@@ -25,6 +25,10 @@
"<a href=\"index.php?id=$course->id\">".get_string('modulenameplural', 'quiz').'</a>'.
' -> '.get_string('editcategories', 'quiz'));
if (isset($SESSION->modform->instance) and $quiz = get_record('quiz', 'id', $SESSION->modform->instance)) {
include('tabs.php');
}
/// CHECK FOR AND ACT UPON VARIABLES SUBMITTED VIA GET OR POST
$qcobject = new quiz_category_object();
$qcobject->set_course($course);
@@ -239,19 +243,14 @@ class quiz_category_object {
* @param object course
*/
function output_edit_table() {
$this->edittable->head = array ($this->str->course, $this->str->category, $this->str->categoryinfo, $this->str->questions, $this->str->publish,
$this->edittable->head = array ($this->str->category, $this->str->categoryinfo, $this->str->questions, $this->str->publish,
$this->str->delete, $this->str->order, $this->str->movecategoryto);
$this->edittable->width = 200;
$this->edittable->tablealign = 'center';
// get list of short names to add to the course display. Only necessary if user has admin view.
if (isadmin()) {
$keys = implode(',', $this->get_course_ids($this->categories));
$courses = get_records_select_menu('course', "id in ($keys)", '', 'id, shortname');
} else {
$courses = $this->course->shortname;
}
$this->build_edit_table_body($this->categories, $courses);
$courses = $this->course->shortname;
$this->build_edit_table_body($this->categories);
print_table($this->edittable);
}
/**
@@ -261,7 +260,7 @@ class quiz_category_object {
* @param mixed courses String with shortname of course | array containing courseid=>shortname
* @param int depth controls the indenting
*/
function build_edit_table_body($categories, $courses, $depth = 0) {
function build_edit_table_body($categories, $depth = 0) {
$countcats = count($categories);
$count = 0;
$first = true;
@@ -275,10 +274,9 @@ class quiz_category_object {
$up = $first ? false : true;
$down = $last ? false : true;
$first = false;
$courseshortname = is_string($courses) ? $courses : $courses[$category->course];
$this->quiz_edit_category_row($category, $courseshortname, $depth, $up, $down);
$this->quiz_edit_category_row($category, $depth, $up, $down);
if (isset($category->children)) {
$this->build_edit_table_body($category->children, $courses, $depth + 1);
$this->build_edit_table_body($category->children, $depth + 1);
}
}
}
@@ -309,16 +307,13 @@ class quiz_category_object {
* @param boolean up can it be moved up?
* @param boolean down can it be moved down?
*/
function quiz_edit_category_row($category, $shortname, $depth, $up = false, $down = false) {
function quiz_edit_category_row($category, $depth, $up = false, $down = false) {
global $USER;
$fill = str_repeat($this->tab, $depth);
$linkcss = $category->publish ? ' class="published"' : ' class="unpublished"';
/// Each section below adds a data cell to this table row
$this->edittable->align["$category->id.course"] = "left";
$this->edittable->wrap["$category->id.course"] = "nowrap";
$row["$category->id.course"] = $shortname;
$this->edittable->align["$category->id.name"] = "left";
$this->edittable->wrap["$category->id.name"] = "nowrap";
@@ -526,7 +521,7 @@ class quiz_category_object {
for ($index = count($levels) - 1; $index >= 0; $index--) {
foreach($levels[$index] as $key) {
$parentkey = $records[$key]->parent;
if (!($records[$key]->questioncount = count_records('quiz_questions', 'category', $records[$key]->id, 'hidden', 0))) {
if (!($records[$key]->questioncount = count_records('quiz_questions', 'category', $records[$key]->id, 'hidden', 0, 'parent', '0'))) {
$records[$key]->questioncount = 0;
}
if ($parentkey == 0) {
@@ -568,20 +563,11 @@ class quiz_category_object {
*/
function get_quiz_categories($parent=null, $sort="sortorder ASC") {
$admin = isadmin();
if ($admin) {
if (is_null($parent)) {
$categories = get_records("quiz_categories", "", "", $sort);
} else {
$categories = get_records("quiz_categories", "parent", $parent, $sort);
}
if (is_null($parent)) {
$categories = get_records('quiz_categories', 'course', "{$this->course->id}", $sort);
} else {
if (is_null($parent)) {
$categories = get_records('quiz_categories', 'course', "{$this->course->id}", $sort);
} else {
$select = "parent = '$parent' AND course = '{$this->course->id}'";
$categories = get_records_select('quiz_categories', $select, $sort);
}
$select = "parent = '$parent' AND course = '{$this->course->id}'";
$categories = get_records_select('quiz_categories', $select, $sort);
}
return $categories;
}
+171 -74
View File
@@ -1,5 +1,61 @@
<?php
require_once($CFG->dirroot . '/mod/quiz/locallib.php');
if (!isset($form->timeopen)) {
$form->timeopen = "";
}
if (!isset($form->timeclose)) {
$form->timeclose = "";
}
if (!isset($form->attempts)) {
$form->attempts = $CFG->quiz_attempts;
}
if (!isset($form->attemptonlast)) {
$form->attemptonlast = $CFG->quiz_attemptonlast;
}
if (!isset($form->grademethod)) {
$form->grademethod = $CFG->quiz_grademethod;
}
if (!isset($form->decimalpoints)) {
$form->decimalpoints = $CFG->quiz_decimalpoints;
}
if (!isset($form->review)) {
$form->review = $CFG->quiz_review;
}
if (!isset($form->questionsperpage)) {
$form->questionsperpage = $CFG->quiz_questionsperpage;
}
if (!isset($form->shufflequestions)) {
$form->shufflequestions = $CFG->quiz_shufflequestions;
}
if (!isset($form->shuffleanswers)) {
$form->shuffleanswers = $CFG->quiz_shuffleanswers;
}
if (!isset($form->grade)) {
$form->grade = $CFG->quiz_maximumgrade;
}
if (!isset($form->questions)) {
$form->questions = "";
}
if (!isset($form->password)) {
$form->password = $CFG->quiz_password;
}
if (!isset($form->subnet)) {
$form->subnet = $CFG->quiz_subnet;
}
if (!isset($form->timelimit)) {
$form->timelimit = $CFG->quiz_timelimit;
}
if (!isset($form->popup)) {
$form->popup = $CFG->quiz_popup;
}
if (!isset($form->optionflags)) {
$form->optionflags = $CFG->quiz_optionflags;
}
if (!isset($form->penaltyscheme)) {
$form->penaltyscheme = $CFG->quiz_penaltyscheme;
}
?>
<form method="post" action="module.php" name="form">
<input type="hidden" name="sesskey" value="<?php echo $USER->sesskey ?>">
@@ -75,6 +131,26 @@
<!-- Table of default values -->
<?php
// the following are used for drop-down menus
$yesnooptions = array();
$yesnooptions[0] = get_string("no");
$yesnooptions[1] = get_string("yes");
$attemptoptions = array();
$attemptoptions[0] = get_string("attemptsunlimited", "quiz");
$attemptoptions[1] = "1 ".strtolower(get_string("attempt", "quiz"));
for ($i=2;$i<=6;$i++) {
$attemptoptions[$i] = "$i ".strtolower(get_string("attempts", "quiz"));
}
$penaltyoptions = array();
$penaltyoptions[QUIZ_PENALTYNONE] = get_string('penaltynone', 'quiz');
$penaltyoptions[QUIZ_PENALTYMULTIPLY] = get_string('penaltymultiply', 'quiz');
$penaltyoptions[QUIZ_PENALTYSUBTRACT] = get_string('penaltysubtract', 'quiz');
?>
<table cellpadding="9" cellspacing="0" align="center">
<tr valign="top">
@@ -85,19 +161,19 @@
<th align="center">
<?php print_string('advancedsettings'); helpbutton("advancedsettings"); ?>
</th>
</tr>
</tr>
<tr valign="top">
<td align="right"><b><?php print_string("timelimit", "quiz") ?>:</b></td>
<td>
<?php
print_timer_selector($CFG->quiz_timelimit, get_string("minutes","quiz"), 'quiz_timelimit');
<?php
print_timer_selector($form->timelimit, get_string("minutes","quiz"), 'timelimit');
helpbutton("timelimit", get_string("quiztimer","quiz"), "quiz");
?>
</td>
<td align="center">
<input type="hidden" name="quiz_fix_timelimit" value="0">
<input type="checkbox" name="quiz_fix_timelimit" value="1" <?php p($CFG->quiz_fix_timelimit ? "checked" : "") ?> />
<input type="hidden" name="fix_timelimit" value="0">
<input type="checkbox" name="fix_timelimit" value="1" <?php p($form->fix_timelimit ? "checked" : "") ?> />
</td>
</tr>
@@ -111,13 +187,13 @@
}
$perpage[0] = get_string('allinone', 'quiz');
choose_from_menu($perpage, 'questionsperpage', $CFG->quiz_questionsperpage, '');
choose_from_menu($perpage, 'questionsperpage', $form->questionsperpage, '');
helpbutton('questionsperpage', get_string('questionsperpage'), 'quiz');
?>
</td>
<td align="center">
<input type="hidden" name="quiz_fix_questionsperpage" value="0">
<input type="checkbox" name="quiz_fix_questionsperpage" value="1" <?php p($CFG->quiz_fix_questionsperpage ? "checked" : "") ?> />
<input type="hidden" name="fix_questionsperpage" value="0">
<input type="checkbox" name="fix_questionsperpage" value="1" <?php p($form->fix_questionsperpage ? "checked" : "") ?> />
</td>
</tr>
@@ -128,13 +204,13 @@
unset($options);
$options[0] = get_string("no");
$options[1] = get_string("yes");
choose_from_menu ($options, "quiz_shufflequestions", $CFG->quiz_shufflequestions, "", "", "");
choose_from_menu ($options, "shufflequestions", $form->shufflequestions, "", "", "");
helpbutton("shufflequestions", get_string("shufflequestions","quiz"), "quiz");
?>
</td>
<td align="center">
<input type="hidden" name="quiz_fix_shufflequestions" value="0">
<input type="checkbox" name="quiz_fix_shufflequestions" value="1" <?php p($CFG->quiz_fix_shufflequestions ? "checked" : "") ?> />
<input type="hidden" name="fix_shufflequestions" value="0">
<input type="checkbox" name="fix_shufflequestions" value="1" <?php p($form->fix_shufflequestions ? "checked" : "") ?> />
</td>
</tr>
@@ -145,13 +221,13 @@
unset($options);
$options[0] = get_string("no");
$options[1] = get_string("yes");
choose_from_menu ($options, "quiz_shuffleanswers", $CFG->quiz_shuffleanswers, "", "", "");
choose_from_menu ($options, "shuffleanswers", $form->shuffleanswers, "", "", "");
helpbutton("attempts", get_string("attemptsallowed","quiz"), "quiz");
?>
</td>
<td align="center">
<input type="hidden" name="quiz_fix_shuffleanswers" value="0">
<input type="checkbox" name="quiz_fix_shuffleanswers" value="1" <?php p($CFG->quiz_fix_shuffleanswers ? "checked" : "") ?> />
<input type="hidden" name="fix_shuffleanswers" value="0">
<input type="checkbox" name="fix_shuffleanswers" value="1" <?php p($form->fix_shuffleanswers ? "checked" : "") ?> />
</td>
</tr>
@@ -165,13 +241,13 @@
for ($i=2;$i<=6;$i++) {
$options[$i] = "$i ".strtolower(get_string("attempts", "quiz"));
}
choose_from_menu ($options, "quiz_attempts", $CFG->quiz_attempts, "", "", "");
choose_from_menu ($options, "attempts", $form->attempts, "", "", "");
helpbutton("attempts", get_string("attemptsallowed","quiz"), "quiz");
?>
</td>
<td align="center">
<input type="hidden" name="quiz_fix_attempts" value="0">
<input type="checkbox" name="quiz_fix_attempts" value="1" <?php p($CFG->quiz_fix_attempts ? "checked" : "") ?> />
<input type="hidden" name="fix_attempts" value="0">
<input type="checkbox" name="fix_attempts" value="1" <?php p($form->fix_attempts ? "checked" : "") ?> />
</td>
</tr>
@@ -182,13 +258,13 @@
unset($options);
$options[0] = get_string("no");
$options[1] = get_string("yes");
choose_from_menu ($options, "quiz_attemptonlast", $CFG->quiz_attemptonlast, "", "", "");
choose_from_menu ($options, "attemptonlast", $form->attemptonlast, "", "", "");
helpbutton("repeatattempts", get_string("eachattemptbuildsonthelast", "quiz"), "quiz");
?>
</td>
<td align="center">
<input type="hidden" name="quiz_fix_attemptonlast" value="0">
<input type="checkbox" name="quiz_fix_attemptonlast" value="1" <?php p($CFG->quiz_fix_attemptonlast ? "checked" : "") ?> />
<input type="hidden" name="fix_attemptonlast" value="0">
<input type="checkbox" name="fix_attemptonlast" value="1" <?php p($form->fix_attemptonlast ? "checked" : "") ?> />
</td>
</tr>
@@ -196,13 +272,41 @@
<td align="right"><b><?php print_string("grademethod", "quiz") ?>:</b></td>
<td>
<?php
choose_from_menu ($QUIZ_GRADE_METHOD, "quiz_grademethod", $CFG->quiz_grademethod, "", "", "");
choose_from_menu ($QUIZ_GRADE_METHOD, "grademethod", $form->grademethod, "", "", "");
helpbutton("grademethod", get_string("grademethod","quiz"), "quiz");
?>
</td>
<td align="center">
<input type="hidden" name="quiz_fix_grademethod" value="0">
<input type="checkbox" name="quiz_fix_grademethod" value="1" <?php p($CFG->quiz_fix_grademethod ? "checked" : "") ?> />
<input type="hidden" name="fix_grademethod" value="0">
<input type="checkbox" name="fix_grademethod" value="1" <?php p($form->fix_grademethod ? "checked" : "") ?> />
</td>
</tr>
<tr valign="top">
<td align="right"><b><?php print_string("adaptive", "quiz") ?>:</b></td>
<td>
<?php
choose_from_menu($yesnooptions, "adaptive", $CFG->quiz_optionflags & QUIZ_ADAPTIVE, "");
helpbutton("adaptive", get_string("adaptive","quiz"), "quiz");
?>
</td>
<td align="center">
<input type="hidden" name="fix_adaptive" value="0">
<input type="checkbox" name="fix_adaptive" value="1" <?php p($form->fix_adaptive ? "checked" : "") ?> />
</td>
</tr>
<tr valign="top">
<td align="right"><b><?php print_string("penaltyscheme", "quiz") ?>:</b></td>
<td>
<?php
choose_from_menu($penaltyoptions, "penaltyscheme", "$form->penaltyscheme", "");
helpbutton("penaltyscheme", get_string("penaltyscheme","quiz"), "quiz");
?>
</td>
<td align="center">
<input type="hidden" name="fix_penaltyscheme" value="0">
<input type="checkbox" name="fix_penaltyscheme" value="1" <?php p($form->fix_penaltyscheme ? "checked" : "") ?> />
</td>
</tr>
@@ -215,13 +319,13 @@
$options[1] = '1';
$options[2] = '2';
$options[3] = '3';
choose_from_menu ($options, "quiz_decimalpoints", $CFG->quiz_decimalpoints, "", "", "");
choose_from_menu ($options, "decimalpoints", $form->decimalpoints, "", "", "");
helpbutton("decimalpoints", get_string("decimalpoints","quiz"), "quiz");
?>
</td>
<td align="center">
<input type="hidden" name="quiz_fix_decimalpoints" value="0">
<input type="checkbox" name="quiz_fix_decimalpoints" value="1" <?php p($CFG->quiz_fix_decimalpoints ? "checked" : "") ?> />
<input type="hidden" name="fix_decimalpoints" value="0">
<input type="checkbox" name="fix_decimalpoints" value="1" <?php p($form->fix_decimalpoints ? "checked" : "") ?> />
</td>
</tr>
@@ -232,13 +336,13 @@
unset($options);
$options[0] = get_string("no");
$options[1] = get_string("yes");
choose_from_menu ($options, "quiz_showfeedback", $CFG->quiz_showfeedback, "", "", "");
choose_from_menu ($options, "showfeedback", $form->showfeedback, "", "", "");
helpbutton("feedback", get_string("showfeedback","quiz"), "quiz");
?>
</td>
<td align="center">
<input type="hidden" name="quiz_fix_showfeedback" value="0">
<input type="checkbox" name="quiz_fix_showfeedback" value="1" <?php p($CFG->quiz_fix_showfeedback ? "checked" : "") ?> />
<input type="hidden" name="fix_showfeedback" value="0">
<input type="checkbox" name="fix_showfeedback" value="1" <?php p($form->fix_showfeedback ? "checked" : "") ?> />
</td>
</tr>
@@ -249,51 +353,21 @@
unset($options);
$options[0] = get_string("no");
$options[1] = get_string("yes");
choose_from_menu ($options, "quiz_showanswer", $CFG->quiz_showanswer, "", "", "");
choose_from_menu ($options, "showanswer", $form->showanswer, "", "", "");
helpbutton("correctanswers", get_string("showcorrectanswer","quiz"), "quiz");
?>
</td>
<td align="center">
<input type="hidden" name="quiz_fix_showanswer" value="0">
<input type="checkbox" name="quiz_fix_showanswer" value="1" <?php p($CFG->quiz_fix_showanswer ? "checked" : "") ?> />
<input type="hidden" name="fix_showanswer" value="0">
<input type="checkbox" name="fix_showanswer" value="1" <?php p($form->fix_showanswer ? "checked" : "") ?> />
</td>
</tr>
<tr valign="top">
<td align="right"><b><?php print_string("allowreview", "quiz") ?>:</b></td>
<td>
<?php
$reviewoptions = array();
$reviewoptions[0] = get_string('reviewnever', 'quiz');
$reviewoptions[1] = get_string('reviewafter', 'quiz');
$reviewoptions[2] = get_string('reviewbefore', 'quiz');
$reviewoptions[3] = get_string('reviewalways', 'quiz');
choose_from_menu ($reviewoptions, "quiz_allowreview", $CFG->quiz_allowreview, "", "", "");
helpbutton("review2", get_string("allowreview","quiz"), "quiz");
?>
</td>
<?php include('reviewoptions.html'); ?>
<td align="center">
<input type="hidden" name="quiz_fix_allowreview" value="0">
<input type="checkbox" name="quiz_fix_allowreview" value="1" <?php p($CFG->quiz_fix_allowreview ? "checked" : "") ?> />
</td>
</tr>
<tr valign="top">
<td align="right"><b><?php print_string("maximumgrade") ?>:</b></td>
<td>
<?php
unset($grades);
for ($i=100; $i>=1; $i--) {
$grades[$i] = $i;
}
$grades[0] = get_string("nograde");
choose_from_menu ($grades, "quiz_maximumgrade", $CFG->quiz_maximumgrade, "", "", "");
helpbutton("maxgrade", get_string("maximumgrade"), "quiz");
?>
</td>
<td align="center">
<input type="hidden" name="quiz_fix_maximumgrade" value="0">
<input type="checkbox" name="quiz_fix_maximumgrade" value="1" <?php p($CFG->quiz_fix_maximumgrade ? "checked" : "") ?> />
<input type="hidden" name="fix_review" value="0">
<input type="checkbox" name="fix_review" value="1" <?php p($form->fix_review ? "checked" : "") ?> />
</td>
</tr>
@@ -304,42 +378,65 @@
unset($options);
$options[0] = get_string("no");
$options[1] = get_string("yes");
choose_from_menu ($options, "quiz_popup", $CFG->quiz_popup, "", "", "");
choose_from_menu ($options, "popup", $form->popup, "", "", "");
helpbutton("popup", get_string("popup","quiz"), "quiz");
?>
</td>
<td align="center">
<input type="hidden" name="quiz_fix_popup" value="0">
<input type="checkbox" name="quiz_fix_popup" value="1" <?php p($CFG->quiz_fix_popup ? "checked" : "") ?> />
<input type="hidden" name="fix_popup" value="0">
<input type="checkbox" name="fix_popup" value="1" <?php p($form->fix_popup ? "checked" : "") ?> />
</td>
</tr>
<tr valign="top">
<td align="right"><b><?php print_string("requirepassword", "quiz") ?>:</b></td>
<td>
<input type="text" name="quiz_password" size="20" value="<?php p($CFG->quiz_password) ?>" />
<input type="text" name="password" size="20" value="<?php p($form->password) ?>" />
<?php helpbutton("requirepassword", get_string("requirepassword", "quiz"), "quiz"); ?>
</td>
<td align="center">
<input type="hidden" name="quiz_fix_password" value="0">
<input type="checkbox" name="quiz_fix_password" value="1" <?php p($CFG->quiz_fix_password ? "checked" : "") ?> />
<input type="hidden" name="fix_password" value="0">
<input type="checkbox" name="fix_password" value="1" <?php p($form->fix_password ? "checked" : "") ?> />
</td>
</tr>
<tr valign="top">
<td align="right"><b><?php print_string("requiresubnet", "quiz") ?>:</b></td>
<td>
<input type="text" name="quiz_subnet" size="20" value="<?php p($CFG->quiz_subnet) ?>" />
<input type="text" name="subnet" size="20" value="<?php p($form->subnet) ?>" />
<?php helpbutton("requiresubnet", get_string("requiresubnet", "quiz"), "quiz"); ?>
</td>
<td align="center">
<input type="hidden" name="quiz_fix_subnet" value="0">
<input type="checkbox" name="quiz_fix_subnet" value="1" <?php p($CFG->quiz_fix_subnet ? "checked" : "") ?> />
<input type="hidden" name="fix_subnet" value="0">
<input type="checkbox" name="fix_subnet" value="1" <?php p($form->fix_subnet ? "checked" : "") ?> />
</td>
</tr>
<tr valign="top">
<td align="right"><b><?php print_string("maximumgrade") ?>:</b></td>
<td>
<?php
unset($grades);
for ($i=100; $i>=1; $i--) {
$grades[$i] = $i;
}
$grades[0] = get_string("nograde");
choose_from_menu ($grades, "maximumgrade", $form->maximumgrade, "", "", "");
helpbutton("maxgrade", get_string("maximumgrade"), "quiz");
?>
</td>
<!-- Can't hide this setting, it always shows up on edit.php
<td align="center">
<input type="hidden" name="fix_maximumgrade" value="0">
<input type="checkbox" name="fix_maximumgrade" value="1" <?php p($form->fix_maximumgrade ? "checked" : "") ?> />
</td>
-->
</tr>
<tr>
<td colspan="3" align="center">
<input type="hidden" name="module" value="quiz" /> </input>
<input type="submit" value="<?php print_string("savechanges") ?>" />
</td>
</tr>
+287 -5
View File
@@ -4,8 +4,8 @@ function quiz_upgrade($oldversion) {
// This function does anything necessary to upgrade
// older versions to match current functionality
global $CFG;
include_once("$CFG->dirroot/mod/quiz/locallib.php");
global $CFG, $QUIZ_QTYPES;
require_once("$CFG->dirroot/mod/quiz/locallib.php");
if ($oldversion < 2002101800) {
execute_sql(" ALTER TABLE `quiz_attempts` ".
@@ -208,7 +208,6 @@ function quiz_upgrade($oldversion) {
if ($oldversion < 2004073001) {
// Six new tables:
// One table for handling units for numerical questions
modify_database ("", " CREATE TABLE `prefix_quiz_numerical_units` (
`id` int(10) unsigned NOT NULL auto_increment,
@@ -218,7 +217,6 @@ function quiz_upgrade($oldversion) {
PRIMARY KEY (`id`)
) TYPE=MyISAM COMMENT='Optional unit options for numerical questions'; ");
// Four tables for handling distribution and storage of
// individual data for dataset dependent question types
modify_database ("", " CREATE TABLE `prefix_quiz_attemptonlast_datasets` (
@@ -347,13 +345,297 @@ function quiz_upgrade($oldversion) {
}
if ($oldversion < 2005041304) {
// make random questions hidden
modify_database('', "UPDATE prefix_quiz_questions SET hidden = '1' WHERE qtype ='".RANDOM."';");
}
if ($oldversion < 2005042002) {
table_column('quiz_answers', 'answer', 'answer', 'text', '', '', '', 'not null', '');
}
if ($oldversion < 2005042400) {
// Changes to quiz table
// The bits of the optionflags field will hold various option flags
table_column('quiz', '', 'optionflags', 'integer', '10', 'unsigned', '0', 'not null', 'timeclose');
// The penalty scheme
table_column('quiz', '', 'penaltyscheme', 'integer', '4', 'unsigned', '0', 'not null', 'optionflags');
// The review options are now all stored in the bits of the review field
table_column('quiz', 'review', 'review', 'integer', 10, 'unsigned', 0, 'not null', '');
// We need to set the bits using the data in the existing fields
if ($quizzes = get_records('quiz')) {
foreach ($quizzes as $quiz) {
$review = (QUIZ_REVIEW_IMMEDIATELY & (QUIZ_REVIEW_RESPONSES + QUIZ_REVIEW_SCORES));
if ($quiz->feedback) {
$review += (QUIZ_REVIEW_IMMEDIATELY & QUIZ_REVIEW_FEEDBACK);
}
if ($quiz->correctanswers) {
$review += (QUIZ_REVIEW_IMMEDIATELY & QUIZ_REVIEW_ANSWERS);
}
if ($quiz->review & 1) {
$review += QUIZ_REVIEW_CLOSED;
}
if ($quiz->review & 2) {
$review += QUIZ_REVIEW_OPEN;
}
set_field('quiz', 'review', $review, 'id', $quiz->id);
}
}
// update the default settings in $CFG
$review = (QUIZ_REVIEW_IMMEDIATELY & (QUIZ_REVIEW_RESPONSES + QUIZ_REVIEW_SCORES));
if (!empty($CFG->quiz_feedback)) {
$review += (QUIZ_REVIEW_IMMEDIATELY & QUIZ_REVIEW_FEEDBACK);
}
if (!empty($CFG->quiz_correctanswers)) {
$review += (QUIZ_REVIEW_IMMEDIATELY & QUIZ_REVIEW_ANSWERS);
}
if (isset($CFG->quiz_review) and ($CFG->quiz_review & 1)) {
$review += QUIZ_REVIEW_CLOSED;
}
if (isset($CFG->quiz_review) and ($CFG->quiz_review & 2)) {
$review += QUIZ_REVIEW_OPEN;
}
set_config('quiz_review', $review);
// We can now drop the fields whose data has been moved to the review field
execute_sql(" ALTER TABLE `{$CFG->prefix}quiz` DROP feedback");
execute_sql(" ALTER TABLE `{$CFG->prefix}quiz` DROP correctanswers");
/// Changes to quiz_attempts table
// The preview flag marks teacher previews
table_column('quiz_attempts', '', 'preview', 'tinyint', '2', 'unsigned', '0', 'not null', 'timemodified');
// The layout is the list of questions with inserted page breaks.
table_column('quiz_attempts', '', 'layout', 'text', '', '', '', 'not null', 'timemodified');
// For old quiz attempts we will set this to the repaginated question list from $quiz->questions
if ($quizzes) {
$quiz->questions = ($quiz->questionsperpage) ? quiz_repaginate($quiz->questions, $quiz->questionsperpage) : $quiz->questions;
foreach ($quizzes as $quiz) {
// repaginate
if ($quiz->questionsperpage) {
$quiz->questions = quiz_repaginate($quiz->questions, $quiz->questionsperpage);
set_field('quiz', 'questions', $quiz->questions, 'id', $quiz->id);
}
set_field('quiz_attempts', 'layout', $quiz->questions, 'quiz', $quiz->id);
// set preview flag
if ($teachers = get_course_teachers($quiz->course)) {
$teacherids = implode(',', array_keys($teachers));
execute_sql("UPDATE {$CFG->prefix}quiz_attempts SET preview = 1 WHERE userid IN ($teacherids)");
}
}
}
/// Renaming tables
// rename the quiz_question_grades table to quiz_question_instances
modify_database ('', 'ALTER TABLE prefix_quiz_question_grades RENAME prefix_quiz_question_instances;');
// rename the quiz_responses table quiz_states
modify_database ('', 'ALTER TABLE prefix_quiz_responses RENAME prefix_quiz_states;');
/// add columns to quiz_states table
// The sequence number of the state.
table_column('quiz_states', '', 'seq_number', 'integer', '6', 'unsigned', '0', 'not null', 'originalquestion');
// For existing states we leave this at 0 because in the old quiz code there was only one response allowed
// The time the state was created.
table_column('quiz_states', '', 'timestamp', 'integer', '10', 'unsigned', '0', 'not null', 'answer');
// For existing states we will below set this to the timemodified field of the attempt
// The type of event that led to the creation of the state
table_column('quiz_states', '', 'event', 'integer', '4', 'unsigned', '0', 'not null', 'timestamp');
// The raw grade
table_column('quiz_states', '', 'raw_grade', 'varchar', '10', '', '', 'not null', 'grade');
// For existing states (no penalties) this is equal to the grade
execute_sql("UPDATE {$CFG->prefix}quiz_states SET raw_grade = grade");
// The penalty that the response attracted
table_column('quiz_states', '', 'penalty', 'varchar', '10', '', '0.0', 'not null', 'raw_grade');
// For existing states this can stay at 0 because penalties did not exist previously.
/// New table for pointers to newest and newest graded states
modify_database('', "CREATE TABLE `prefix_quiz_newest_states` (
`id` int(10) unsigned NOT NULL auto_increment,
`attemptid` int(10) unsigned NOT NULL default '0',
`questionid` int(10) unsigned NOT NULL default '0',
`new` int(10) unsigned NOT NULL default '0',
`newgraded` int(10) unsigned NOT NULL default '0',
`sumpenalty` varchar(10) NOT NULL default '0.0',
PRIMARY KEY (`id`),
UNIQUE KEY `attemptid` (`attemptid`,`questionid`)
) TYPE=MyISAM COMMENT='Gives ids of the newest open and newest graded states';");
/// Now upgrade states and newest_states where necessary
// to save time on large sites only do this for attempts that have not yet been finished.
if ($attempts = get_records_select('quiz_attempts', 'timefinish = 0')) {
foreach ($attempts as $attempt) {
quiz_upgrade_states($attempt);
}
}
/// Entries for the log_display table
modify_database('', " INSERT INTO prefix_log_display VALUES ('quiz', 'preview', 'quiz', 'name');");
modify_database('', " INSERT INTO prefix_log_display VALUES ('quiz', 'start attempt', 'quiz', 'name');");
modify_database('', " INSERT INTO prefix_log_display VALUES ('quiz', 'close attempt', 'quiz', 'name');");
/// Fix numerical question type
table_column('quiz_numerical', '', 'tolerance', 'varchar', '255', '', '0.0', 'not null', 'question');
if($rows = get_records('quiz_numerical')) {
foreach ($rows as $row) {
$row->tolerance = ((float)$row->min + (float)$row->max)/2;
set_field('quiz_numerical', 'tolerance', $row->tolerance, 'id', $row->id);
}
}
// modify_database('', 'ALTER TABLE `prefix_quiz_numerical` DROP `answer`'); // There is no need for it at all
modify_database('', 'ALTER TABLE `prefix_quiz_numerical` DROP `min`'); // Replaced by tolerance
modify_database('', 'ALTER TABLE `prefix_quiz_numerical` DROP `max`'); // Replaced by tolerance
/// Tables for Remote Questions
modify_database ('', "CREATE TABLE `prefix_quiz_rqp` (
`id` int(10) unsigned NOT NULL auto_increment,
`question` int(10) unsigned NOT NULL default '0',
`type` int(10) unsigned NOT NULL default '0',
`source` longblob NOT NULL default '',
`format` varchar(255) NOT NULL default '',
`flags` tinyint(3) unsigned NOT NULL default '0',
`maxscore` int(10) unsigned NOT NULL default '1',
PRIMARY KEY (`id`),
KEY `question` (`question`)
) TYPE=MyISAM COMMENT='Options for RQP questions';");
modify_database ('', "CREATE TABLE `prefix_quiz_rqp_type` (
`id` int(10) unsigned NOT NULL auto_increment,
`name` varchar(255) NOT NULL default '',
`rendering_server` varchar(255) NOT NULL default '',
`cloning_server` varchar(255) NOT NULL default '',
`flags` tinyint(3) NOT NULL default '0',
PRIMARY KEY (`id`),
UNIQUE KEY `name` (`name`)
) TYPE=MyISAM COMMENT='RQP question types and the servers to be used to process them';");
modify_database ('', "CREATE TABLE `prefix_quiz_rqp_states` (
`id` int(10) unsigned NOT NULL auto_increment,
`stateid` int(10) unsigned NOT NULL default '0',
`responses` text NOT NULL default '',
`persistent_data` text NOT NULL default '',
`template_vars` text NOT NULL default '',
PRIMARY KEY (`id`)
) TYPE=MyISAM COMMENT='RQP question type specific state information';");
}
if ($oldversion < 2005042900) {
table_column('quiz_multianswers', '', 'sequence', 'varchar', '255', '', '', 'not null', 'question');
table_column('quiz_numerical', '', 'answers', 'varchar', '255', '', '', 'not null', 'answer');
modify_database('', 'UPDATE prefix_quiz_numerical SET answers = answer');
table_column('quiz_questions', '', 'parent', 'integer', '10', 'unsigned', '0', 'not null', 'category');
modify_database('', "UPDATE prefix_quiz_questions SET parent = id WHERE qtype ='".RANDOM."';");
// convert multianswer questions to the new model
if ($multianswers = get_records_sql("SELECT m.id, q.category, q.id AS parent,
q.name, q.questiontextformat, m.norm AS
defaultgrade, m.answertype AS qtype,
q.version, q.hidden, m.answers,
m.positionkey
FROM {$CFG->prefix}quiz_questions q,
{$CFG->prefix}quiz_multianswers m
WHERE q.qtype = '".MULTIANSWER."'
AND q.id = m.question
ORDER BY q.id ASC, m.positionkey ASC")) {
$multianswers = array_values($multianswers);
$n = count($multianswers);
$parent = $multianswers[0]->parent;
$sequence = array();
// turn reporting off temporarily to avoid one line output per set_field
global $db;
$olddebug = $db->debug;
// $db->debug = false;
for ($i = 0; $i < $n; $i++) {
$answers = $multianswers[$i]->answers; unset($multianswers[$i]->answers);
$pos = $multianswers[$i]->positionkey; unset($multianswers[$i]->positionkey);
// create questions for all the multianswer victims
unset($multianswers[$i]->id);
$multianswers[$i]->length = 0;
$multianswers[$i]->questiontext = '';
$multianswers[$i]->stamp = make_unique_id_code();
$id = insert_record('quiz_questions', $multianswers[$i]);
$sequence[$pos] = $id;
// update the answers table to point to these new questions
modify_database('', "UPDATE prefix_quiz_answers SET question = '$id' WHERE id IN ($answers);");
// update the questiontype tables to point to these new questions
if (SHORTANSWER == $multianswers[$i]->qtype) {
modify_database('', "UPDATE prefix_quiz_shortanswer SET question = '$id' WHERE answers = '$answers';");
} else if (NUMERICAL == $multianswers[$i]->qtype) {
if (strpos($answers, ',')) {
$numerical = get_records_list('quiz_numerical', 'answer', $answers);
// Get the biggest tolerance value
$tolerance = 0;
foreach ($numerical as $num) {
$tolerance = ($tolerance < $num->tolerance ? $num->tolerance : $tolerance);
}
delete_records_select('quiz_numerical', "answer IN ($answers)");
$new = new stdClass;
$new->question = $id;
$new->tolerance = $tolerance;
$new->answers = $answers;
insert_record('quiz_numerical', $new);
unset($numerical, $new, $tolerance);
} else {
modify_database('', "UPDATE prefix_quiz_numerical SET question = '$id', answers = '$answers' WHERE answer IN ($answers);");
}
} else if (MULTICHOICE == $multianswers[$i]->qtype) {
modify_database('', "UPDATE prefix_quiz_multichoice SET question = '$id' WHERE answers = '$answers';");
}
if (!isset($multianswers[$i+1]) || $parent != $multianswers[$i+1]->parent) {
delete_records('quiz_multianswers', 'question', $parent);
$multi = new stdClass;
$multi->question = $parent;
$multi->sequence = implode(',', $sequence);
insert_record('quiz_multianswers', $multi);
if (isset($multianswers[$i+1])) {
$parent = $multianswers[$i+1]->parent;
$sequence = array();
}
}
}
$db->debug = $olddebug;
}
// Remove redundant fields from quiz_multianswers
modify_database('', 'ALTER TABLE `prefix_quiz_multianswers` DROP `answers`');
modify_database('', 'ALTER TABLE `prefix_quiz_multianswers` DROP `positionkey`');
modify_database('', 'ALTER TABLE `prefix_quiz_multianswers` DROP `answertype`');
modify_database('', 'ALTER TABLE `prefix_quiz_multianswers` DROP `norm`');
// Change numerical from answer to answers
modify_database('', 'ALTER TABLE `prefix_quiz_numerical` DROP `answer`');
}
if ($oldversion < 2005050300) {
// length of question determines question numbering. Currently all questions require one
// question number except for DESCRIPTION questions.
table_column('quiz_questions', '', 'length', 'integer', '10', 'unsigned', '1', 'not null', 'qtype');
execute_sql("UPDATE {$CFG->prefix}quiz_questions SET length = 0 WHERE qtype = ".DESCRIPTION);
}
if ($oldversion < 2005050500) {
table_column('quiz_questions', '', 'penalty', 'float', '', '', '0.1', 'not null', 'defaultgrade');
}
return true;
}
+416 -318
View File
@@ -1,389 +1,484 @@
# phpMyAdmin SQL Dump
# version 2.5.7
# http://www.phpmyadmin.net
#
# Host: localhost
# Generation Time: Jul 30, 2004 at 02:52 PM
# Server version: 4.0.18
# PHP Version: 4.3.7
# Database : `moodle`
# --------------------------------------------------------
-- phpMyAdmin SQL Dump
-- version 2.6.0-pl1
-- http://www.phpmyadmin.net
--
-- Host: localhost
-- Generation Time: May 04, 2005 at 06:27 PM
-- Server version: 4.0.15
-- PHP Version: 4.3.3
--
-- Database: `upgrading`
--
#
# Table structure for table `quiz`
#
-- --------------------------------------------------------
CREATE TABLE `prefix_quiz` (
`id` int(10) unsigned NOT NULL auto_increment,
`course` int(10) unsigned NOT NULL default '0',
`name` varchar(255) NOT NULL default '',
`intro` text NOT NULL,
`timeopen` int(10) unsigned NOT NULL default '0',
`timeclose` int(10) unsigned NOT NULL default '0',
`attempts` smallint(6) NOT NULL default '0',
`attemptonlast` tinyint(4) NOT NULL default '0',
`feedback` tinyint(4) NOT NULL default '0',
`correctanswers` tinyint(4) NOT NULL default '1',
`grademethod` tinyint(4) NOT NULL default '1',
`decimalpoints` tinyint(4) NOT NULL default '2',
`review` tinyint(4) NOT NULL default '0',
`questionsperpage` int(10) NOT NULL default '0',
`shufflequestions` tinyint(4) NOT NULL default '0',
`shuffleanswers` tinyint(4) NOT NULL default '0',
`questions` text NOT NULL,
`sumgrades` int(10) NOT NULL default '0',
`grade` int(10) NOT NULL default '0',
`timecreated` int(10) unsigned NOT NULL default '0',
`timemodified` int(10) unsigned NOT NULL default '0',
`timelimit` int(2) unsigned NOT NULL default '0',
`password` varchar(255) NOT NULL default '',
`subnet` varchar(255) NOT NULL default '',
`popup` tinyint(4) NOT NULL default '0',
PRIMARY KEY (`id`),
KEY `course` (`course`)
--
-- Table structure for table `prefix_quiz`
--
CREATE TABLE prefix_quiz (
id int(10) unsigned NOT NULL auto_increment,
course int(10) unsigned NOT NULL default '0',
name varchar(255) NOT NULL default '',
intro text NOT NULL,
timeopen int(10) unsigned NOT NULL default '0',
timeclose int(10) unsigned NOT NULL default '0',
optionflags int(10) unsigned NOT NULL default '0',
penaltyscheme int(4) unsigned NOT NULL default '0',
attempts smallint(6) NOT NULL default '0',
attemptonlast tinyint(4) NOT NULL default '0',
grademethod tinyint(4) NOT NULL default '1',
decimalpoints int(4) NOT NULL default '2',
review int(10) unsigned NOT NULL default '0',
questionsperpage int(10) NOT NULL default '0',
shufflequestions tinyint(4) NOT NULL default '0',
shuffleanswers tinyint(4) NOT NULL default '0',
questions text NOT NULL,
sumgrades int(10) NOT NULL default '0',
grade int(10) NOT NULL default '0',
timecreated int(10) unsigned NOT NULL default '0',
timemodified int(10) unsigned NOT NULL default '0',
timelimit int(2) unsigned NOT NULL default '0',
password varchar(255) NOT NULL default '',
subnet varchar(255) NOT NULL default '',
popup tinyint(4) NOT NULL default '0',
PRIMARY KEY (id),
KEY course (course)
) TYPE=MyISAM COMMENT='Main information about each quiz';
# --------------------------------------------------------
#
# Table structure for table `quiz_answers`
#
-- --------------------------------------------------------
CREATE TABLE `prefix_quiz_answers` (
`id` int(10) unsigned NOT NULL auto_increment,
`question` int(10) unsigned NOT NULL default '0',
`answer` text NOT NULL,
`fraction` varchar(10) NOT NULL default '0.0',
`feedback` text NOT NULL,
PRIMARY KEY (`id`),
KEY `question` (`question`)
--
-- Table structure for table `prefix_quiz_answers`
--
CREATE TABLE prefix_quiz_answers (
id int(10) unsigned NOT NULL auto_increment,
question int(10) unsigned NOT NULL default '0',
answer text NOT NULL,
fraction varchar(10) NOT NULL default '0.0',
feedback text NOT NULL,
PRIMARY KEY (id),
KEY question (question)
) TYPE=MyISAM COMMENT='Answers, with a fractional grade (0-1) and feedback';
# --------------------------------------------------------
#
# Table structure for table `quiz_attemptonlast_datasets`
#
-- --------------------------------------------------------
CREATE TABLE `prefix_quiz_attemptonlast_datasets` (
`id` int(10) unsigned NOT NULL auto_increment,
`category` int(10) unsigned NOT NULL default '0',
`userid` int(10) unsigned NOT NULL default '0',
`datasetnumber` int(10) unsigned NOT NULL default '0',
PRIMARY KEY (`id`),
UNIQUE KEY `category` (`category`,`userid`)
--
-- Table structure for table `prefix_quiz_attemptonlast_datasets`
--
CREATE TABLE prefix_quiz_attemptonlast_datasets (
id int(10) unsigned NOT NULL auto_increment,
category int(10) unsigned NOT NULL default '0',
userid int(10) unsigned NOT NULL default '0',
datasetnumber int(10) unsigned NOT NULL default '0',
PRIMARY KEY (id),
UNIQUE KEY category (category,userid)
) TYPE=MyISAM COMMENT='Dataset number for attemptonlast attempts per user';
# --------------------------------------------------------
#
# Table structure for table `quiz_attempts`
#
-- --------------------------------------------------------
CREATE TABLE `prefix_quiz_attempts` (
`id` int(10) unsigned NOT NULL auto_increment,
`quiz` int(10) unsigned NOT NULL default '0',
`userid` int(10) unsigned NOT NULL default '0',
`attempt` smallint(6) NOT NULL default '0',
`sumgrades` varchar(10) NOT NULL default '0.0',
`timestart` int(10) unsigned NOT NULL default '0',
`timefinish` int(10) unsigned NOT NULL default '0',
`timemodified` int(10) unsigned NOT NULL default '0',
PRIMARY KEY (`id`),
KEY `quiz` (`quiz`),
KEY `userid` (`userid`)
--
-- Table structure for table `prefix_quiz_attempts`
--
CREATE TABLE prefix_quiz_attempts (
id int(10) unsigned NOT NULL auto_increment,
quiz int(10) unsigned NOT NULL default '0',
userid int(10) unsigned NOT NULL default '0',
attempt smallint(6) NOT NULL default '0',
sumgrades varchar(10) NOT NULL default '0.0',
timestart int(10) unsigned NOT NULL default '0',
timefinish int(10) unsigned NOT NULL default '0',
timemodified int(10) unsigned NOT NULL default '0',
layout text NOT NULL,
preview tinyint(3) unsigned NOT NULL default '0',
PRIMARY KEY (id),
KEY quiz (quiz),
KEY userid (userid)
) TYPE=MyISAM COMMENT='Stores various attempts on a quiz';
# --------------------------------------------------------
#
# Table structure for table `quiz_calculated`
#
-- --------------------------------------------------------
CREATE TABLE `prefix_quiz_calculated` (
`id` int(10) unsigned NOT NULL auto_increment,
`question` int(10) unsigned NOT NULL default '0',
`answer` int(10) unsigned NOT NULL default '0',
`tolerance` varchar(20) NOT NULL default '0.0',
`tolerancetype` int(10) NOT NULL default '1',
`correctanswerlength` int(10) NOT NULL default '2',
`correctanswerformat` int(10) NOT NULL default '2',
PRIMARY KEY (`id`),
KEY `question` (`question`),
KEY `answer` (`answer`)
--
-- Table structure for table `prefix_quiz_calculated`
--
CREATE TABLE prefix_quiz_calculated (
id int(10) unsigned NOT NULL auto_increment,
question int(10) unsigned NOT NULL default '0',
answer int(10) unsigned NOT NULL default '0',
tolerance varchar(20) NOT NULL default '0.0',
tolerancetype int(10) NOT NULL default '1',
correctanswerlength int(10) NOT NULL default '2',
correctanswerformat int(10) NOT NULL default '2',
PRIMARY KEY (id),
KEY question (question),
KEY answer (answer)
) TYPE=MyISAM COMMENT='Options for questions of type calculated';
# --------------------------------------------------------
#
# Table structure for table `quiz_categories`
#
-- --------------------------------------------------------
CREATE TABLE `prefix_quiz_categories` (
`id` int(10) unsigned NOT NULL auto_increment,
`course` int(10) unsigned NOT NULL default '0',
`name` varchar(255) NOT NULL default '',
`info` text NOT NULL,
`publish` tinyint(4) NOT NULL default '0',
`stamp` varchar(255) NOT NULL default '',
`parent` int(10) unsigned NOT NULL default '0',
`sortorder` int(10) unsigned NOT NULL default '999',
PRIMARY KEY (`id`),
KEY `course` (`course`)
--
-- Table structure for table `prefix_quiz_categories`
--
CREATE TABLE prefix_quiz_categories (
id int(10) unsigned NOT NULL auto_increment,
course int(10) unsigned NOT NULL default '0',
name varchar(255) NOT NULL default '',
info text NOT NULL,
publish tinyint(4) NOT NULL default '0',
stamp varchar(255) NOT NULL default '',
parent int(10) unsigned NOT NULL default '0',
sortorder int(10) unsigned NOT NULL default '999',
PRIMARY KEY (id),
KEY course (course)
) TYPE=MyISAM COMMENT='Categories are for grouping questions';
# --------------------------------------------------------
#
# Table structure for table `quiz_dataset_definitions`
#
-- --------------------------------------------------------
CREATE TABLE `prefix_quiz_dataset_definitions` (
`id` int(10) unsigned NOT NULL auto_increment,
`category` int(10) unsigned NOT NULL default '0',
`name` varchar(255) NOT NULL default '',
`type` int(10) NOT NULL default '0',
`options` varchar(255) NOT NULL default '',
`itemcount` int(10) unsigned NOT NULL default '0',
PRIMARY KEY (`id`),
KEY `category` (`category`)
--
-- Table structure for table `prefix_quiz_dataset_definitions`
--
CREATE TABLE prefix_quiz_dataset_definitions (
id int(10) unsigned NOT NULL auto_increment,
category int(10) unsigned NOT NULL default '0',
name varchar(255) NOT NULL default '',
type int(10) NOT NULL default '0',
options varchar(255) NOT NULL default '',
itemcount int(10) unsigned NOT NULL default '0',
PRIMARY KEY (id),
KEY category (category)
) TYPE=MyISAM COMMENT='Organises and stores properties for dataset items';
# --------------------------------------------------------
#
# Table structure for table `quiz_dataset_items`
#
-- --------------------------------------------------------
CREATE TABLE `prefix_quiz_dataset_items` (
`id` int(10) unsigned NOT NULL auto_increment,
`definition` int(10) unsigned NOT NULL default '0',
`number` int(10) unsigned NOT NULL default '0',
`value` varchar(255) NOT NULL default '',
PRIMARY KEY (`id`),
KEY `definition` (`definition`)
--
-- Table structure for table `prefix_quiz_dataset_items`
--
CREATE TABLE prefix_quiz_dataset_items (
id int(10) unsigned NOT NULL auto_increment,
definition int(10) unsigned NOT NULL default '0',
number int(10) unsigned NOT NULL default '0',
value varchar(255) NOT NULL default '',
PRIMARY KEY (id),
KEY definition (definition)
) TYPE=MyISAM COMMENT='Individual dataset items';
# --------------------------------------------------------
#
# Table structure for table `quiz_grades`
#
-- --------------------------------------------------------
CREATE TABLE `prefix_quiz_grades` (
`id` int(10) unsigned NOT NULL auto_increment,
`quiz` int(10) unsigned NOT NULL default '0',
`userid` int(10) unsigned NOT NULL default '0',
`grade` real NOT NULL default '0',
`timemodified` int(10) unsigned NOT NULL default '0',
PRIMARY KEY (`id`),
KEY `quiz` (`quiz`),
KEY `userid` (`userid`)
--
-- Table structure for table `prefix_quiz_grades`
--
CREATE TABLE prefix_quiz_grades (
id int(10) unsigned NOT NULL auto_increment,
quiz int(10) unsigned NOT NULL default '0',
userid int(10) unsigned NOT NULL default '0',
grade double NOT NULL default '0',
timemodified int(10) unsigned NOT NULL default '0',
PRIMARY KEY (id),
KEY quiz (quiz),
KEY userid (userid)
) TYPE=MyISAM COMMENT='Final quiz grade (may be best of several attempts)';
# --------------------------------------------------------
#
# Table structure for table `quiz_match`
#
-- --------------------------------------------------------
CREATE TABLE `prefix_quiz_match` (
`id` int(10) unsigned NOT NULL auto_increment,
`question` int(10) unsigned NOT NULL default '0',
`subquestions` varchar(255) NOT NULL default '',
PRIMARY KEY (`id`),
KEY `question` (`question`)
--
-- Table structure for table `prefix_quiz_match`
--
CREATE TABLE prefix_quiz_match (
id int(10) unsigned NOT NULL auto_increment,
question int(10) unsigned NOT NULL default '0',
subquestions varchar(255) NOT NULL default '',
PRIMARY KEY (id),
KEY question (question)
) TYPE=MyISAM COMMENT='Defines fixed matching questions';
# --------------------------------------------------------
#
# Table structure for table `quiz_match_sub`
#
-- --------------------------------------------------------
CREATE TABLE `prefix_quiz_match_sub` (
`id` int(10) unsigned NOT NULL auto_increment,
`question` int(10) unsigned NOT NULL default '0',
`questiontext` text NOT NULL,
`answertext` varchar(255) NOT NULL default '',
PRIMARY KEY (`id`),
KEY `question` (`question`)
--
-- Table structure for table `prefix_quiz_match_sub`
--
CREATE TABLE prefix_quiz_match_sub (
id int(10) unsigned NOT NULL auto_increment,
question int(10) unsigned NOT NULL default '0',
questiontext text NOT NULL,
answertext varchar(255) NOT NULL default '',
PRIMARY KEY (id),
KEY question (question)
) TYPE=MyISAM COMMENT='Defines the subquestions that make up a matching question';
# --------------------------------------------------------
#
# Table structure for table `quiz_multianswers`
#
-- --------------------------------------------------------
CREATE TABLE `prefix_quiz_multianswers` (
`id` int(10) unsigned NOT NULL auto_increment,
`question` int(10) unsigned NOT NULL default '0',
`answers` varchar(255) NOT NULL default '',
`positionkey` varchar(255) NOT NULL default '',
`answertype` smallint(6) NOT NULL default '0',
`norm` int(10) unsigned NOT NULL default '1',
PRIMARY KEY (`id`),
KEY `question` (`question`)
--
-- Table structure for table `prefix_quiz_multianswers`
--
CREATE TABLE prefix_quiz_multianswers (
id int(10) unsigned NOT NULL auto_increment,
question int(10) unsigned NOT NULL default '0',
sequence varchar(255) NOT NULL default '',
PRIMARY KEY (id),
KEY question (question)
) TYPE=MyISAM COMMENT='Options for multianswer questions';
# --------------------------------------------------------
#
# Table structure for table `quiz_multichoice`
#
-- --------------------------------------------------------
CREATE TABLE `prefix_quiz_multichoice` (
`id` int(10) unsigned NOT NULL auto_increment,
`question` int(10) unsigned NOT NULL default '0',
`layout` tinyint(4) NOT NULL default '0',
`answers` varchar(255) NOT NULL default '',
`single` tinyint(4) NOT NULL default '0',
PRIMARY KEY (`id`),
KEY `question` (`question`)
--
-- Table structure for table `prefix_quiz_multichoice`
--
CREATE TABLE prefix_quiz_multichoice (
id int(10) unsigned NOT NULL auto_increment,
question int(10) unsigned NOT NULL default '0',
layout tinyint(4) NOT NULL default '0',
answers varchar(255) NOT NULL default '',
single tinyint(4) NOT NULL default '0',
PRIMARY KEY (id),
KEY question (question)
) TYPE=MyISAM COMMENT='Options for multiple choice questions';
# --------------------------------------------------------
#
# Table structure for table `quiz_numerical`
#
-- --------------------------------------------------------
CREATE TABLE `prefix_quiz_numerical` (
`id` int(10) unsigned NOT NULL auto_increment,
`question` int(10) unsigned NOT NULL default '0',
`answer` int(10) unsigned NOT NULL default '0',
`min` varchar(255) NOT NULL default '',
`max` varchar(255) NOT NULL default '',
PRIMARY KEY (`id`),
KEY `answer` (`answer`),
KEY `question` (`question`)
--
-- Table structure for table `prefix_quiz_newest_states`
--
CREATE TABLE prefix_quiz_newest_states (
id int(10) unsigned NOT NULL auto_increment,
attemptid int(10) unsigned NOT NULL default '0',
questionid int(10) unsigned NOT NULL default '0',
new int(10) unsigned NOT NULL default '0',
newgraded int(10) unsigned NOT NULL default '0',
sumpenalty varchar(10) NOT NULL default '0.0',
PRIMARY KEY (id),
UNIQUE KEY attemptid (attemptid,questionid)
) TYPE=MyISAM COMMENT='Gives ids of the newest open and newest graded states';
-- --------------------------------------------------------
--
-- Table structure for table `prefix_quiz_numerical`
--
CREATE TABLE prefix_quiz_numerical (
id int(10) unsigned NOT NULL auto_increment,
question int(10) unsigned NOT NULL default '0',
tolerance varchar(255) NOT NULL default '0.0',
answers varchar(255) NOT NULL default '',
PRIMARY KEY (id),
KEY question (question)
) TYPE=MyISAM COMMENT='Options for numerical questions';
# --------------------------------------------------------
#
# Table structure for table `quiz_numerical_units`
#
-- --------------------------------------------------------
CREATE TABLE `prefix_quiz_numerical_units` (
`id` int(10) unsigned NOT NULL auto_increment,
`question` int(10) unsigned NOT NULL default '0',
`multiplier` decimal(40,20) NOT NULL default '1.00000000000000000000',
`unit` varchar(50) NOT NULL default '',
PRIMARY KEY (`id`),
KEY `question` (`question`)
--
-- Table structure for table `prefix_quiz_numerical_units`
--
CREATE TABLE prefix_quiz_numerical_units (
id int(10) unsigned NOT NULL auto_increment,
question int(10) unsigned NOT NULL default '0',
multiplier decimal(40,20) NOT NULL default '1.00000000000000000000',
unit varchar(50) NOT NULL default '',
PRIMARY KEY (id),
KEY question (question)
) TYPE=MyISAM COMMENT='Optional unit options for numerical questions';
# --------------------------------------------------------
#
# Table structure for table `quiz_question_datasets`
#
-- --------------------------------------------------------
CREATE TABLE `prefix_quiz_question_datasets` (
`id` int(10) unsigned NOT NULL auto_increment,
`question` int(10) unsigned NOT NULL default '0',
`datasetdefinition` int(10) unsigned NOT NULL default '0',
PRIMARY KEY (`id`),
KEY `question` (`question`,`datasetdefinition`)
--
-- Table structure for table `prefix_quiz_question_datasets`
--
CREATE TABLE prefix_quiz_question_datasets (
id int(10) unsigned NOT NULL auto_increment,
question int(10) unsigned NOT NULL default '0',
datasetdefinition int(10) unsigned NOT NULL default '0',
PRIMARY KEY (id),
KEY question (question,datasetdefinition)
) TYPE=MyISAM COMMENT='Many-many relation between questions and dataset definitions';
# --------------------------------------------------------
#
# Table structure for table `quiz_question_grades`
#
-- --------------------------------------------------------
CREATE TABLE `prefix_quiz_question_grades` (
`id` int(10) unsigned NOT NULL auto_increment,
`quiz` int(10) unsigned NOT NULL default '0',
`question` int(10) unsigned NOT NULL default '0',
`grade` smallint(6) NOT NULL default '0',
PRIMARY KEY (`id`),
KEY `quiz` (`quiz`),
KEY `question` (`question`)
--
-- Table structure for table `prefix_quiz_question_instances`
--
CREATE TABLE prefix_quiz_question_instances (
id int(10) unsigned NOT NULL auto_increment,
quiz int(10) unsigned NOT NULL default '0',
question int(10) unsigned NOT NULL default '0',
grade smallint(6) NOT NULL default '0',
PRIMARY KEY (id),
KEY quiz (quiz),
KEY question (question)
) TYPE=MyISAM COMMENT='The grade for a question in a quiz';
# --------------------------------------------------------
#
# Table structure for table `quiz_question_versions`
#
-- --------------------------------------------------------
CREATE TABLE `prefix_quiz_question_versions` (
`id` int(10) unsigned NOT NULL auto_increment,
`quiz` int(10) unsigned NOT NULL default '0',
`oldquestion` int(10) unsigned NOT NULL default '0',
`newquestion` int(10) unsigned NOT NULL default '0',
`userid` int(10) unsigned NOT NULL default '0',
`timestamp` int(10) unsigned NOT NULL default '0',
PRIMARY KEY (`id`)
--
-- Table structure for table `prefix_quiz_question_versions`
--
CREATE TABLE prefix_quiz_question_versions (
id int(10) unsigned NOT NULL auto_increment,
quiz int(10) unsigned NOT NULL default '0',
oldquestion int(10) unsigned NOT NULL default '0',
newquestion int(10) unsigned NOT NULL default '0',
userid int(10) unsigned NOT NULL default '0',
timestamp int(10) unsigned NOT NULL default '0',
PRIMARY KEY (id)
) TYPE=MyISAM COMMENT='The mapping between old and new versions of a question';
# --------------------------------------------------------
#
# Table structure for table `quiz_questions`
#
-- --------------------------------------------------------
CREATE TABLE `prefix_quiz_questions` (
`id` int(10) NOT NULL auto_increment,
`category` int(10) NOT NULL default '0',
`name` varchar(255) NOT NULL default '',
`questiontext` text NOT NULL,
`questiontextformat` tinyint(2) NOT NULL default '0',
`image` varchar(255) NOT NULL default '',
`defaultgrade` int(10) unsigned NOT NULL default '1',
`qtype` smallint(6) NOT NULL default '0',
`stamp` varchar(255) NOT NULL default '',
`version` int(10) NOT NULL default '1',
`hidden` int(1) unsigned NOT NULL default '0',
PRIMARY KEY (`id`),
KEY `category` (`category`)
--
-- Table structure for table `prefix_quiz_questions`
--
CREATE TABLE prefix_quiz_questions (
id int(10) NOT NULL auto_increment,
category int(10) NOT NULL default '0',
parent int(10) unsigned NOT NULL default '0',
name varchar(255) NOT NULL default '',
questiontext text NOT NULL,
questiontextformat tinyint(2) NOT NULL default '0',
image varchar(255) NOT NULL default '',
defaultgrade int(10) unsigned NOT NULL default '1',
penalty float NOT NULL default '0.1',
qtype smallint(6) NOT NULL default '0',
length int(10) unsigned NOT NULL default '1',
stamp varchar(255) NOT NULL default '',
version int(10) NOT NULL default '1',
hidden int(1) unsigned NOT NULL default '0',
PRIMARY KEY (id),
KEY category (category)
) TYPE=MyISAM COMMENT='The quiz questions themselves';
# --------------------------------------------------------
#
# Table structure for table `quiz_randomsamatch`
#
-- --------------------------------------------------------
CREATE TABLE `prefix_quiz_randomsamatch` (
`id` int(10) unsigned NOT NULL auto_increment,
`question` int(10) unsigned NOT NULL default '0',
`choose` int(10) unsigned NOT NULL default '4',
PRIMARY KEY (`id`),
KEY `question` (`question`)
--
-- Table structure for table `prefix_quiz_randomsamatch`
--
CREATE TABLE prefix_quiz_randomsamatch (
id int(10) unsigned NOT NULL auto_increment,
question int(10) unsigned NOT NULL default '0',
choose int(10) unsigned NOT NULL default '4',
PRIMARY KEY (id),
KEY question (question)
) TYPE=MyISAM COMMENT='Info about a random short-answer matching question';
# --------------------------------------------------------
#
# Table structure for table `quiz_responses`
#
-- --------------------------------------------------------
CREATE TABLE `prefix_quiz_responses` (
`id` int(10) unsigned NOT NULL auto_increment,
`attempt` int(10) unsigned NOT NULL default '0',
`question` int(10) unsigned NOT NULL default '0',
`originalquestion` int(10) unsigned NOT NULL default '0',
`answer` text NOT NULL default '',
`grade` varchar(10) NOT NULL default '0.0',
PRIMARY KEY (`id`),
KEY `attempt` (`attempt`),
KEY `question` (`question`)
) TYPE=MyISAM COMMENT='Stores user responses to a quiz, and percentage grades';
# --------------------------------------------------------
--
-- Table structure for table `prefix_quiz_rqp`
--
#
# Table structure for table `quiz_shortanswer`
#
CREATE TABLE prefix_quiz_rqp (
id int(10) unsigned NOT NULL auto_increment,
question int(10) unsigned NOT NULL default '0',
type int(10) unsigned NOT NULL default '0',
source longblob NOT NULL,
format varchar(255) NOT NULL default '',
flags tinyint(3) unsigned NOT NULL default '0',
maxscore int(10) unsigned NOT NULL default '1',
PRIMARY KEY (id),
KEY question (question)
) TYPE=MyISAM COMMENT='Options for RQP questions';
CREATE TABLE `prefix_quiz_shortanswer` (
`id` int(10) unsigned NOT NULL auto_increment,
`question` int(10) unsigned NOT NULL default '0',
`answers` varchar(255) NOT NULL default '',
`usecase` tinyint(2) NOT NULL default '0',
PRIMARY KEY (`id`),
KEY `question` (`question`)
-- --------------------------------------------------------
--
-- Table structure for table `prefix_quiz_rqp_states`
--
CREATE TABLE prefix_quiz_rqp_states (
id int(10) unsigned NOT NULL auto_increment,
stateid int(10) unsigned NOT NULL default '0',
responses text NOT NULL,
persistent_data text NOT NULL,
template_vars text NOT NULL,
PRIMARY KEY (id)
) TYPE=MyISAM COMMENT='RQP question type specific state information';
-- --------------------------------------------------------
--
-- Table structure for table `prefix_quiz_rqp_type`
--
CREATE TABLE prefix_quiz_rqp_type (
id int(10) unsigned NOT NULL auto_increment,
name varchar(255) NOT NULL default '',
rendering_server varchar(255) NOT NULL default '',
cloning_server varchar(255) NOT NULL default '',
flags tinyint(3) NOT NULL default '0',
PRIMARY KEY (id),
UNIQUE KEY name (name)
) TYPE=MyISAM COMMENT='RQP question types and the servers to be used to process the';
-- --------------------------------------------------------
--
-- Table structure for table `prefix_quiz_shortanswer`
--
CREATE TABLE prefix_quiz_shortanswer (
id int(10) unsigned NOT NULL auto_increment,
question int(10) unsigned NOT NULL default '0',
answers varchar(255) NOT NULL default '',
usecase tinyint(2) NOT NULL default '0',
PRIMARY KEY (id),
KEY question (question)
) TYPE=MyISAM COMMENT='Options for short answer questions';
# --------------------------------------------------------
#
# Table structure for table `quiz_truefalse`
#
-- --------------------------------------------------------
CREATE TABLE `prefix_quiz_truefalse` (
`id` int(10) unsigned NOT NULL auto_increment,
`question` int(10) unsigned NOT NULL default '0',
`trueanswer` int(10) unsigned NOT NULL default '0',
`falseanswer` int(10) unsigned NOT NULL default '0',
PRIMARY KEY (`id`),
KEY `question` (`question`)
--
-- Table structure for table `prefix_quiz_states`
--
CREATE TABLE prefix_quiz_states (
id int(10) unsigned NOT NULL auto_increment,
attempt int(10) unsigned NOT NULL default '0',
question int(10) unsigned NOT NULL default '0',
originalquestion int(10) unsigned NOT NULL default '0',
seq_number int(6) unsigned NOT NULL default '0',
answer text NOT NULL,
timestamp int(10) unsigned NOT NULL default '0',
event int(4) unsigned NOT NULL default '0',
grade varchar(10) NOT NULL default '0.0',
raw_grade varchar(10) NOT NULL default '',
penalty varchar(10) NOT NULL default '0.0',
PRIMARY KEY (id),
KEY attempt (attempt),
KEY question (question)
) TYPE=MyISAM COMMENT='Stores user responses to a quiz, and percentage grades';
-- --------------------------------------------------------
--
-- Table structure for table `prefix_quiz_truefalse`
--
CREATE TABLE prefix_quiz_truefalse (
id int(10) unsigned NOT NULL auto_increment,
question int(10) unsigned NOT NULL default '0',
trueanswer int(10) unsigned NOT NULL default '0',
falseanswer int(10) unsigned NOT NULL default '0',
PRIMARY KEY (id),
KEY question (question)
) TYPE=MyISAM COMMENT='Options for True-False questions';
# --------------------------------------------------------
INSERT INTO prefix_log_display VALUES ('quiz', 'add', 'quiz', 'name');
INSERT INTO prefix_log_display VALUES ('quiz', 'update', 'quiz', 'name');
INSERT INTO prefix_log_display VALUES ('quiz', 'view', 'quiz', 'name');
@@ -392,3 +487,6 @@ INSERT INTO prefix_log_display VALUES ('quiz', 'attempt', 'quiz', 'name');
INSERT INTO prefix_log_display VALUES ('quiz', 'submit', 'quiz', 'name');
INSERT INTO prefix_log_display VALUES ('quiz', 'review', 'quiz', 'name');
INSERT INTO prefix_log_display VALUES ('quiz', 'editquestions', 'quiz', 'name');
INSERT INTO prefix_log_display VALUES ('quiz', 'preview', 'quiz', 'name');
INSERT INTO prefix_log_display VALUES ('quiz', 'start attempt', 'quiz', 'name');
INSERT INTO prefix_log_display VALUES ('quiz', 'close attempt', 'quiz', 'name');
+10 -10
View File
@@ -4,37 +4,37 @@
// It defines default values for any important configuration variables
$defaults = array (
'quiz_allowreview' => 1,
'quiz_review' => hexdec('ffffff'),
'quiz_attemptonlast' => 0,
'quiz_attempts' => 0,
'quiz_grademethod' => '',
'quiz_decimalpoints' => 2,
'quiz_maximumgrade' => 0,
'quiz_maximumgrade' => 10,
'quiz_password' => '',
'quiz_popup' => 0,
'quiz_questionsperpage' => 0,
'quiz_showanswer' => '',
'quiz_showfeedback' => '',
'quiz_shuffleanswers' => 1,
'quiz_shufflequestions' => 0,
'quiz_subnet' => '',
'quiz_timelimit' => 0,
'quiz_fix_allowreview' => 0,
'quiz_optionflags' => 1,
'quiz_penaltyscheme' => 0,
'quiz_adaptive' => 1,
'quiz_fix_review' => 0,
'quiz_fix_attemptonlast' => 0,
'quiz_fix_attempts' => 0,
'quiz_fix_grademethod' => 0,
'quiz_fix_decimalpoints' => 0,
'quiz_fix_maximumgrade' => 0,
'quiz_fix_password' => 0,
'quiz_fix_popup' => 0,
'quiz_fix_questionsperpage' => 0,
'quiz_fix_showanswer' => 0,
'quiz_fix_showfeedback' => 0,
'quiz_fix_shuffleanswers' => 0,
'quiz_fix_shufflequestions' => 0,
'quiz_fix_subnet' => 0,
'quiz_fix_timelimit' => 0
'quiz_fix_timelimit' => 0,
'quiz_fix_adaptive' => 0,
'quiz_fix_penaltyscheme' => 0
);
?>
+217 -165
View File
@@ -1,7 +1,40 @@
<?php // $Id$
/**
* Page to edit quizzes
*
* This page generally has two columns:
* The right column lists all available questions in a chosen category and
* allows them to be edited or more to be added. This column is only there if
* the quiz does not already have student attempts
* The left column lists all questions that have been added to the current quiz.
* This column is only there if this page has been called in the context of a quiz.
* The lecturer can add questions from the right hand list to the quiz or remove them
*
* The script also processes a number of actions:
* Actions affecting a quiz:
* up and down Changes the order of questions and page breaks
* addquestion Adds a single question to the quiz
* add Adds several selected questions to the quiz
* addrandom Adds a certain number of random questions to the quiz
* delete Removes a question from the quiz
* setgrades Changes the maximum grades for questions in the quiz
* repaginate Re-paginates the quiz
* Actions affecting the question pool:
* move Moves a question to a different category
* deleteselected Deletes the selected questions from the category
* Other actions:
* cat Chooses the category
* displayoptions Sets display options
*
* @version $Id$
* @author Martin Dougiamas and many others. This has recently been extensively
* rewritten by Gustav Delius and other members of the Serving Mathematics project
* {@link http://maths.york.ac.uk/serving_maths}
* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
* @package quiz
*/
require_once("../../config.php");
require_once("locallib.php");
require_once("editlib.php");
require_login();
@@ -20,16 +53,24 @@
$SESSION->modform = $modform; // Save the form in the current session
} else if ($quizid) {
if (! $modform = get_record('quiz', 'id', $quizid)) {
error("The required quiz doesn't exist");
}
$modform->instance = $modform->id;
$SESSION->modform = $modform; // Save the form in the current session
if (isset($SESSION->modform) and $SESSION->modform->id == $quizid) {
// modform for this quiz already exists, use it
$modform = $SESSION->modform;
} else {
// create new modform from database
if (! $modform = get_record('quiz', 'id', $quizid)) {
error("The required quiz doesn't exist");
}
$modform->instance = $modform->id;
$SESSION->modform = $modform; // Save the form in the current session
$cm = get_coursemodule_from_instance('quiz', $modform->instance);
add_to_log($cm->course, 'quiz', 'editquestions',
"view.php?id=$cm->id",
"$quizid", $cm->id);
$cm = get_coursemodule_from_instance('quiz', $modform->instance);
$modform->cmid = $cm->id;
// We don't log all visits to this page but only those that recreate modform
add_to_log($cm->course, 'quiz', 'editquestions',
"view.php?id=$cm->id",
"$quizid", $cm->id);
}
} else if ($courseid) { // Page retrieve through "Edit Questions" link - no quiz selected
$modform->course = $courseid;
@@ -39,18 +80,13 @@
add_to_log($courseid, 'quiz', 'editquestions', "index.php?id=$courseid");
} else {
// no quiz or course was specified so we need to use the stored modform
if (!isset($SESSION->modform)) {
// We currently also get here after editing a question by
// following the edit link on the review page. Perhaps that should be fixed.
error('');
error(get_string('wronguse', 'quiz'), '../../course/view.php?id='.$course->id);
}
// The data is obtained from a $SESSION variable. This is mostly for historic reasons.
// With the way things work now it would be just as possible to get the data from the database.
$modform = $SESSION->modform;
}
if (! $course = get_record("course", "id", $modform->course)) {
error("This course doesn't exist");
}
@@ -64,113 +100,122 @@
if (isset($modform->instance)
&& empty($modform->grades)) // Construct an array to hold all the grades.
{
$modform->grades = quiz_get_all_question_grades($modform->questions, $modform->instance);
$modform->grades = quiz_get_all_question_grades($modform);
}
/// Now, check for commands on this page and modify variables as necessary
if (isset($_REQUEST['up']) and confirm_sesskey()) { /// Move the given question up a slot
$questions = explode(",", $modform->questions);
if ($questions[0] <> $up) {
foreach ($questions as $key => $question) {
if ($up == $question) {
$swap = $questions[$key-1];
$questions[$key-1] = $question;
$questions[$key] = $swap;
break;
}
}
if ($up > 0 and isset($questions[$up])) {
$prevkey = ($questions[$up-1] == 0) ? $up-2 : $up-1;
$swap = $questions[$prevkey];
$questions[$prevkey] = $questions[$up];
$questions[$up] = $swap;
$modform->questions = implode(",", $questions);
}
if (!set_field('quiz', 'questions', $modform->questions, 'id', $modform->instance)) {
error('Could not save question list');
// Always have a page break at the end
$modform->questions = $modform->questions . ',0';
// Avoid duplicate page breaks
$modform->questions = str_replace(',0,0', ',0', $modform->questions);
if (!set_field('quiz', 'questions', $modform->questions, 'id', $modform->instance)) {
error('Could not save question list');
}
}
}
if (isset($_REQUEST['down']) and confirm_sesskey()) { /// Move the given question down a slot
$questions = explode(",", $modform->questions);
if ($questions[count($questions)-1] <> $down) {
foreach ($questions as $key => $question) {
if ($down == $question) {
$swap = $questions[$key+1];
$questions[$key+1] = $question;
$questions[$key] = $swap;
break;
}
}
if ($down < count($questions)) {
$nextkey = ($questions[$down+1] == 0) ? $down+2 : $down+1;
$swap = $questions[$nextkey];
$questions[$nextkey] = $questions[$down];
$questions[$down] = $swap;
$modform->questions = implode(",", $questions);
}
if (!set_field('quiz', 'questions', $modform->questions, 'id', $modform->instance)) {
error('Could not save question list');
// Avoid duplicate page breaks
$modform->questions = str_replace(',0,0', ',0', $modform->questions);
if (!set_field('quiz', 'questions', $modform->questions, 'id', $modform->instance)) {
error('Could not save question list');
}
}
}
if (isset($_REQUEST['addquestion']) and confirm_sesskey()) { /// Add a single question to the current quiz
// add question to quiz->questions
$key = $_REQUEST['addquestion'];
$questions = array();
if (!empty($modform->questions)) {
$questions = explode(",", $modform->questions);
}
if (!in_array($key, $questions)) {
$questions[] = $key;
}
$modform->questions = implode(",", $questions);
if (!set_field('quiz', 'questions', $modform->questions, 'id', $modform->instance)) {
error('Could not save question list');
}
// update question grades
$questionrecord = get_record("quiz_questions", "id", $key);
if (!empty($questionrecord->defaultgrade)) {
$modform->grades[$key] = $questionrecord->defaultgrade;
} else if ($questionrecord->qtype == DESCRIPTION){
$modform->grades[$key] = 0;
} else {
$modform->grades[$key] = 1;
}
quiz_questiongrades_update($modform->grades, $modform->instance);
quiz_add_quiz_question($_REQUEST['addquestion'], $modform);
}
if (isset($_REQUEST['add']) and confirm_sesskey()) { /// Add selected questions to the current quiz
$rawquestions = $_POST;
if (!empty($modform->questions)) {
$questions = explode(",", $modform->questions);
}
foreach ($rawquestions as $key => $value) { // Parse input for question ids
foreach ($_POST as $key => $value) { // Parse input for question ids
if (substr($key, 0, 1) == "q") {
$key = substr($key,1);
if (!empty($questions)) {
foreach ($questions as $question) {
if ($question == $key) {
continue 2;
}
}
}
$questions[] = $key;
$questionrecord = get_record("quiz_questions", "id", $key);
if (!empty($questionrecord->defaultgrade)) {
$modform->grades[$key] = $questionrecord->defaultgrade;
} else if ($questionrecord->qtype == DESCRIPTION){
$modform->grades[$key] = 0;
} else {
$modform->grades[$key] = 1;
}
quiz_add_quiz_question(substr($key,1), $modform);
}
}
if (!empty($questions)) {
$modform->questions = implode(",", $questions);
}
if (isset($_REQUEST['addrandom']) and confirm_sesskey()) { /// Add random questions to the quiz
$recurse = optional_param('recurse', 0, PARAM_BOOL);
$categoryid = required_param('categoryid', PARAM_INT);
$randomcount = required_param('randomcount', PARAM_INT);
// load category
if (! $category = get_record('quiz_categories', 'id', $categoryid)) {
error('Category ID is incorrect');
}
// find existing random questions in this category
$random = RANDOM;
if ($existingquestions = get_records_select('quiz_questions', "qtype = '$random' AND category = '$category->id'")) {
// now remove the ones that are already used in this quiz
if ($questionids = explode(',', $modform->questions)) {
foreach ($questionids as $questionid) {
unset($existingquestions[$questionid]);
}
}
// now take as many of these as needed
$i = 0;
while (($existingquestion = array_pop($existingquestions)) and ($i < $randomcount)) {
if ($existingquestion->questiontext == $recurse) {
// this question has the right recurse property, so use it
quiz_add_quiz_question($existingquestion->id, $modform);
$i++;
}
}
$randomcreate = $randomcount - $i; // the number of additional random questions needed.
} else {
$modform->questions = "";
$randomcreate = $randomcount;
}
if (!set_field('quiz', 'questions', $modform->questions, 'id', $modform->instance)) {
error('Could not save question list');
if ($randomcreate > 0) {
$form->name = get_string('random', 'quiz') .' ('. $category->name .')';
$form->questiontext = $recurse; // we use the questiontext field to store the info
// on whether to include questions in subcategories
$form->questiontextformat = 0;
$form->image = '';
$form->defaultgrade = 1;
$form->hidden = 1;
for ($i=0; $i<$randomcreate; $i++) {
$form->stamp = make_unique_id_code(); // Set the unique code (not to be changed)
$question = new stdClass;
$question->category = $category->id;
$question->qtype = RANDOM;
$question = $QUIZ_QTYPES[RANDOM]->save_question($question, $form, $course);
if(!isset($question->id)) {
error('Could not insert new random question!');
}
quiz_add_quiz_question($question->id, $modform);
}
}
}
if (isset($_REQUEST['repaginate']) and confirm_sesskey()) { /// Re-paginate the quiz
if (isset($_REQUEST['questionsperpage'])) {
$modform->questionsperpage = required_param('questionsperpage', 1, PARAM_INT);
if (!set_field('quiz', 'questionsperpage', $modform->questionsperpage, 'id', $modform->id)) {
error('Could not save number of questions per page');
}
}
$modform->questions = quiz_repaginate($modform->questions, $modform->questionsperpage);
if (!set_field('quiz', 'questions', $modform->questions, 'id', $modform->id)) {
error('Could not save layout');
}
quiz_questiongrades_update($modform->grades, $modform->instance);
}
if (isset($_REQUEST['move']) and confirm_sesskey()) { /// Move selected questions to new category
@@ -180,8 +225,7 @@
if (!isteacheredit($tocategory->course)) {
error(get_string('categorynoedit', 'quiz', $tocategory->name), 'edit.php');
}
$rawquestions = $_POST;
foreach ($rawquestions as $key => $value) { // Parse input for question ids
foreach ($_POST as $key => $value) { // Parse input for question ids
if (substr($key, 0, 1) == "q") {
$key = substr($key,1);
if (!set_field('quiz_questions', 'category', $tocategory->id, 'id', $key)) {
@@ -192,29 +236,7 @@
}
if (isset($_REQUEST['delete']) and confirm_sesskey()) { /// Remove a question from the quiz
$questions = explode(",", $modform->questions);
foreach ($questions as $key => $question) {
if ($question == $delete) {
unset($questions[$key]);
unset($modform->grades[$question]);
// Delete all responses associated with all attempts for this question in the quiz.
if ($attempts = get_records('quiz_attempts', 'quiz', $modform->instance)) {
foreach ($attempts as $attempt) {
if (!delete_records('quiz_responses', 'question', $question, 'attempt', $attempt->id)) {
error('Could not delete all responses for this question');
}
}
}
// Delete question from quiz_question_grades table
if (!delete_records('quiz_question_grades', 'quiz', $modform->instance, 'question', $question)) {
error('Could not delete the question from this quiz');
}
}
}
$modform->questions = implode(",", $questions);
if (!set_field('quiz', 'questions', $modform->questions, 'id', $modform->instance)) {
error('Could not save question list');
}
quiz_delete_quiz_question($_REQUEST['delete'], $modform);
}
if (isset($_REQUEST['deleteselected'])) { // delete selected questions from the category
@@ -224,16 +246,13 @@
if ($questionlist = explode(',', $deleteselected)) {
// for each question either hide it if it is in use or delete it
foreach ($questionlist as $questionid) {
if (record_exists('quiz_responses', 'question', $questionid) or
record_exists('quiz_responses', 'originalquestion', $questionid) or
record_exists('quiz_question_grades', 'question', $questionid)) {
if (record_exists('quiz_question_instances', 'question', $questionid) or
record_exists('quiz_states', 'originalquestion', $questionid)) {
if (!set_field('quiz_questions', 'hidden', 1, 'id', $questionid)) {
error('Was not able to hide question');
error('Was not able to hide question');
}
} else {
if (!delete_records("quiz_questions", "id", $questionid)) {
error("An error occurred trying to delete question (id $questionid)");
}
delete_records("quiz_questions", "id", $questionid);
}
}
}
@@ -253,9 +272,8 @@
if (substr($key, 0, 1) == "q") {
$key = substr($key,1);
$questionlist .= $key.',';
if (record_exists('quiz_responses', 'question', $key) or
record_exists('quiz_responses', 'originalquestion', $key) or
record_exists('quiz_question_grades', 'question', $key)) {
if (record_exists('quiz_question_instances', 'question', $key) or
record_exists('quiz_states', 'originalquestion', $key)) {
$questionnames .= '* ';
$inuse = true;
}
@@ -281,28 +299,44 @@
}
}
if (isset($_REQUEST['setgrades']) and confirm_sesskey()) { /// The grades have been updated, so update our internal list
if (isset($_REQUEST['setgrades']) and confirm_sesskey()) {
/// The grades have been updated, so update our internal list
$rawgrades = $_POST;
unset($modform->grades);
foreach ($rawgrades as $key => $value) { // Parse input for question -> grades
if (substr($key, 0, 1) == "q") {
$key = substr($key,1);
$modform->grades[$key] = $value;
quiz_update_question_instance($modform->grades[$key], $key, $modform->instance);
}
}
if (!set_field('quiz', 'questions', $modform->questions, 'id', $modform->instance)) {
error('Could not save question list');
// If rescaling is required save the new maximum
if (isset($_REQUEST['maxgrade'])) {
$modform->grade = optional_param('maxgrade', 0);
if (!set_field('quiz', 'grade', $modform->grade, 'id', $modform->instance)) {
error('Could not set new maximal grade for quiz');
}
}
quiz_questiongrades_update($modform->grades, $modform->instance);
}
if (isset($_REQUEST['cat'])) { /// coming from category selection drop-down menu
$modform->category = $cat;
}
if(isset($_REQUEST['displayoptions'])) {
$modform->recurse = isset($_REQUEST['recurse']) ? 1 : 0;
$modform->showhidden = isset($_REQUEST['showhidden']);
if(isset($_REQUEST['recurse'])) {
$SESSION->quiz_recurse = optional_param('recurse', 0, PARAM_BOOL);
}
if(isset($_REQUEST['showbreaks'])) {
$SESSION->quiz_showbreaks = optional_param('showbreaks', 0, PARAM_BOOL);
}
if(isset($_REQUEST['showhidden'])) {
$SESSION->quiz_showhidden = optional_param('showhidden', 0, PARAM_BOOL);
}
/// Delete any teacher preview attempts if the quiz has been modified
if (isset($_REQUEST['delete']) or isset($_REQUEST['repaginate']) or isset($_REQUEST['addrandom']) or isset($_REQUEST['addquestion']) or isset($_REQUEST['up']) or isset($_REQUEST['down']) or isset($_REQUEST['add'])) {
delete_records('quiz_attempts', 'preview', '1', 'quiz', $modform->id);
}
/// all commands have been dealt with, now print the page
@@ -311,43 +345,54 @@
$category = quiz_get_default_category($course->id);
$modform->category = $category->id;
}
if (!isset($modform->recurse)) {
$modform->recurse = 1;
if (!isset($SESSION->quiz_recurse)) {
$SESSION->quiz_recurse = 1;
}
if (!isset($modform->showhidden)) {
$modform->showhidden = false;
if (!isset($SESSION->quiz_showhidden)) {
$SESSION->quiz_showhidden = false;
}
if (!isset($SESSION->quiz_showbreaks)) {
$SESSION->quiz_showbreaks = ($CFG->quiz_questionsperpage < 2) ? 0 : 1;
}
$SESSION->modform = $modform;
// Print basic page layout.
if (isset($modform->instance) and record_exists_sql("SELECT * FROM {$CFG->prefix}quiz_attempts WHERE quiz = '$modform->instance' AND NOT (userid = '$USER->id') LIMIT 1")){
// one column layout with table of questions used in this quiz
if (isset($modform->instance) and record_exists_sql("SELECT * FROM {$CFG->prefix}quiz_attempts WHERE quiz = '$modform->instance' AND preview = '0' LIMIT 1")){
// one column layout with table of questions used in this quiz
$strupdatemodule = isteacheredit($course->id)
? update_module_button($modform->cmid, $course->id, get_string('modulename', 'quiz'))
: "";
print_header_simple($streditingquiz, '',
"<a href=\"index.php?id=$course->id\">$strquizzes</a>".
" -> <a href=\"view.php?q=$modform->instance\">".format_string($modform->name,true)."</a>".
" -> $streditingquiz");
" -> <a href=\"view.php?q=$modform->instance\">".format_string($modform->name).'</a>'.
" -> $streditingquiz", "", "",
true, $strupdatemodule);
$currenttab = 'edit';
$quiz = &$modform;
include('tabs.php');
print_simple_box_start("center");
print_heading(format_string($modform->name));
$attemptcount = count_records_select("quiz_attempts", "quiz = '$modform->instance' AND timefinish > 0");
$attemptcount = count_records('quiz_attempts', 'quiz', $modform->instance, 'preview', 0);
$strviewallanswers = get_string("viewallanswers","quiz",$attemptcount);
$strattemptsexist = get_string("attemptsexist","quiz");
$usercount = count_records("quiz_grades", "quiz", "$modform->instance");
$strusers = get_string("users");
$usercount = count_records_select('quiz_attempts', "quiz = '$modform->id' AND preview = '0'", 'COUNT(DISTINCT userid)');
$strusers = $course->students;
if (! $cm = get_coursemodule_from_instance("quiz", $modform->instance, $course->id)) {
error("Course Module ID was incorrect");
}
notify("$strattemptsexist<br /><a href=\"report.php?id=$cm->id\">$strviewallanswers ($usercount $strusers)</a>");
notify("$strattemptsexist<br /><a href=\"attempts.php?id=$cm->id\">$strviewallanswers ($usercount $strusers)</a>");
$sumgrades = quiz_print_question_list($modform->questions, $modform->grades, false, $modform->instance);
$sumgrades = quiz_print_question_list($modform, false, $SESSION->quiz_showbreaks);
if (!set_field('quiz', 'sumgrades', $sumgrades, 'id', $modform->instance)) {
error('Failed to set sumgrades');
}
print_simple_box_end();
print_continue('view.php?q='.$modform->instance);
print_footer($course);
exit;
}
@@ -362,39 +407,46 @@
} else {
// two column layout with quiz info in left column
$strupdatemodule = isteacheredit($course->id)
? update_module_button($modform->cmid, $course->id, get_string('modulename', 'quiz'))
: "";
print_header_simple($streditingquiz, '',
"<a href=\"index.php?id=$course->id\">$strquizzes</a>".
" -> <a href=\"view.php?q=$modform->instance\">".format_string($modform->name,true)."</a>".
" -> $streditingquiz");
" -> <a href=\"view.php?q=$modform->instance\">".format_string($modform->name).'</a>'.
" -> $streditingquiz",
"", "", true, $strupdatemodule);
$currenttab = 'edit';
$quiz = &$modform;
include('tabs.php');
echo '<table border="0" width="100%" cellpadding="2" cellspacing="0">';
echo '<tr><td width="50%" valign="top">';
print_simple_box_start("center", "100%");
print_heading(format_string($modform->name));
$sumgrades = quiz_print_question_list($modform->questions, $modform->grades, true, $modform->instance);
$sumgrades = quiz_print_question_list($modform, true, $SESSION->quiz_showbreaks);
if (!set_field('quiz', 'sumgrades', $sumgrades, 'id', $modform->instance)) {
error('Failed to set sumgrades');
}
print_simple_box_end();
print_continue('view.php?q='.$modform->instance);
echo '</td><td valign="top" width="50%">';
}
// non-quiz-specific column
print_simple_box_start("center", "100%");
// starts with category selection form
quiz_print_category_form($course, $modform->category, $modform->recurse, $modform->showhidden);
quiz_print_category_form($course, $modform->category, $SESSION->quiz_recurse, $SESSION->quiz_showhidden);
print_simple_box_end();
print_spacer(5,1);
// continues with list of questions
print_simple_box_start("center", "100%");
quiz_print_cat_question_list($course, $modform->category,
isset($modform->instance), $modform->recurse, $page, $perpage, $modform->showhidden);
isset($modform->instance), $SESSION->quiz_recurse, $page, $perpage, $SESSION->quiz_showhidden);
print_simple_box_end();
if (!isset($modform->instance)) {
print_continue("index.php?id=$modform->course");
} else {
print_continue('view.php?q='.$modform->instance);
}
echo '</td></tr>';
echo '</table>';
+581
View File
@@ -0,0 +1,581 @@
<?php // $Id$
/**
* Functions used by edit.php to edit quizzes
*
* @version $Id$
* @author Martin Dougiamas and many others. This has recently been extensively
* rewritten by members of the Serving Mathematics project
* {@link http://maths.york.ac.uk/serving_maths}
* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
* @package quiz
*/
require_once("locallib.php");
/**
* Delete a question from a quiz
*
* Deletes a question or a pagebreak from a quiz by updating $modform
* as well as the quiz, quiz_question_instances, quiz_attemtps, quiz_states
* and quiz_newest_states tables.
* @return boolean false if the question was not in the quiz
* @param int $id The id of the question to be deleted
* @param object $modform The extended quiz object as used by edit.php
* This is updated by this function
*/
function quiz_delete_quiz_question($id, &$modform) {
$questions = explode(",", $modform->questions);
// only do something if this question exists
if (!isset($questions[$id])) {
return false;
}
$question = $questions[$id];
unset($questions[$id]);
// If we deleted the question at the top and it was followed by
// a page break then delete page break as well
if ($id == 0 and $questions[1] == 0) {
unset($questions[1]);
}
// if what we deleted was not a page break but a question then also
// delete associated grades, instances, attempts, states
if ($question != 0) {
unset($modform->grades[$question]);
// Delete all states associated with all attempts for this question in the quiz.
if ($attempts = get_records('quiz_attempts', 'quiz', $modform->instance)) {
foreach ($attempts as $attempt) {
delete_records('quiz_states', 'question', $question, 'attempt', $attempt->id);
delete_records('quiz_newest_states', 'questionid', $question, 'attemptid', $attempt->id);
}
}
// Delete all instances of the question in the quiz (there
// should only be one)
delete_records('quiz_question_instances', 'quiz',
$modform->instance, 'question', $question);
}
$modform->questions = implode(",", $questions);
// Avoid duplicate page breaks
$modform->questions = str_replace(',0,0', ',0', $modform->questions);
// save new questionlist in database
if (!set_field('quiz', 'questions', $modform->questions, 'id', $modform->instance)) {
error('Could not save question list');
}
}
/**
* Add a question to a quiz
*
* Adds a question to a quiz by updating $modform as well as the
* quiz and quiz_question_instances tables. It also adds a page break
* if required.
* @return boolean false if the question was already in the quiz
* @param int $id The id of the question to be added
* @param object $modform The extended quiz object as used by edit.php
* This is updated by this function
*/
function quiz_add_quiz_question($id, &$modform) {
$questions = explode(",", $modform->questions);
if (in_array($id, $questions)) {
return false;
}
// remove ending page break if it is not needed
if ($breaks = array_keys($questions, 0)) {
// determine location of the last two page breaks
$end = end($breaks);
$last = prev($breaks);
$last = $last ? $last : -1;
if (!$modform->questionsperpage or (($end - $last -1) < $modform->questionsperpage)) {
array_pop($questions);
}
}
// add question
$questions[] = $id;
// add ending page break
$questions[] = 0;
// Save new questionslist in database
$modform->questions = implode(",", $questions);
if (!set_field('quiz', 'questions', $modform->questions, 'id', $modform->id)) {
error('Could not save question list');
}
// update question grades
$questionrecord = get_record("quiz_questions", "id", $id);
if (!empty($questionrecord->defaultgrade)) {
$modform->grades[$id] = $questionrecord->defaultgrade;
} else if ($questionrecord->qtype == DESCRIPTION){
$modform->grades[$id] = 0;
} else {
$modform->grades[$id] = 1;
}
quiz_update_question_instance($modform->grades[$id], $id, $modform->instance);
return true;
}
/**
* Save changes to question instance
*
* Saves changes to the question grades in the quiz_question_instances table.
* It does not update 'sumgrades' in the quiz table.
* @return boolean Indicates success or failure.
* @param integer grade The maximal grade for the question
* @param integer $questionid The id of the question
* @param integer $quizid The id of the quiz to update / add the instances for.
*/
function quiz_update_question_instance($grade, $questionid, $quizid) {
if ($instance = get_record("quiz_question_instances", "quiz", $quizid, 'question', $questionid)) {
$instance->grade = $grade;
return update_record('quiz_question_instances', $instance);
} else {
unset($instance);
$instance->quiz = $quizid;
$instance->question = $questionid;
$instance->grade = $grade;
return insert_record("quiz_question_instances", $instance);
}
}
/**
* Prints a list of quiz questions in a small layout form with knobs
*
* @return int sum of maximum grades
* @param object $quiz This is not the standard quiz object used elsewhere but
* it contains the quiz layout in $quiz->questions and the grades in
* $quiz->grades
* @param boolean $allowdelete Indicates whether the delete icons should be displayed
* @param boolean $showbreaks Indicates whether the page breaks should be displayed
*/
function quiz_print_question_list($quiz, $allowdelete=true, $showbreaks=true) {
global $USER, $CFG, $QUIZ_QTYPES;
$strorder = get_string("order");
$strquestionname = get_string("questionname", "quiz");
$strgrade = get_string("grade");
$strremove = get_string('remove', 'quiz');
$stredit = get_string("edit");
$straction = get_string("action");
$strmoveup = get_string("moveup");
$strmovedown = get_string("movedown");
$strsavegrades = get_string("savegrades", "quiz");
$strtype = get_string("type", "quiz");
$strpreview = get_string("preview", "quiz");
if (!$quiz->questions) {
echo "<p align=\"center\">";
print_string("noquestions", "quiz");
echo "</p>";
return;
}
$order = explode(",", $quiz->questions);
if (!$questions = get_records_sql("SELECT q.*,c.course
FROM {$CFG->prefix}quiz_questions q,
{$CFG->prefix}quiz_categories c
WHERE q.id in ($quiz->questions)
AND q.category = c.id")) {
echo "<p align=\"center\">";
print_string("noquestions", "quiz");
echo "</p>";
return;
}
$count = 0;
$qno = 1;
$sumgrade = 0;
$total = count($order);
echo "<form method=\"post\" action=\"edit.php\">";
echo "<input type=\"hidden\" name=\"sesskey\" value=\"$USER->sesskey\" />";
print_simple_box_start('center', '100%', '#ffffff', 0);
echo "<table border=\"0\" cellpadding=\"2\" cellspacing=\"0\" width=\"100%\">\n";
echo "<tr><th colspan=\"3\" nowrap=\"nowrap\" class=\"header\">$strorder</th><th align=\"left\" width=\"100%\" nowrap=\"nowrap\" class=\"header\">$strquestionname</th><th nowrap=\"nowrap\" class=\"header\">$strtype</th><th nowrap=\"nowrap\" class=\"header\">$strgrade</th><th align=\"center\" width=\"60\" nowrap=\"nowrap\" class=\"header\">$straction</th></tr>\n";
foreach ($order as $qnum) {
if ($qnum == 0) { // This is a page break
if ($showbreaks) {
echo '<tr><td colspan ="3">&nbsp;</td>';
echo '<td><table width="100%" style="line-height:11px; font-size:9px; margin: -5px -5px;"><tr>';
echo '<td><hr noshade="noshade" /></td>';
echo '<td width="50">Page break</td>';
echo '<td><hr noshade="noshade" /></td><td width="45">';
if ($count != 1) {
echo "<a title=\"$strmoveup\" href=\"edit.php?up=$count&amp;sesskey=$USER->sesskey\"><img
src=\"../../pix/t/up.gif\" border=\"0\" alt=\"$strmoveup\" /></a>";
}
echo '&nbsp;';
if ($count < $total-1) {
echo "<a title=\"$strmovedown\" href=\"edit.php?down=$count&amp;sesskey=$USER->sesskey\"><img
src=\"../../pix/t/down.gif\" border=\"0\" alt=\"$strmovedown\" /></a>";
echo "<a title=\"$strremove\" href=\"edit.php?delete=$count&amp;sesskey=$USER->sesskey\">
<img src=\"../../pix/t/delete.gif\" border=\"0\" alt=\"$strremove\" /></a>";
}
echo '</td></tr></table></td>';
echo '<td colspan="2">&nbsp;</td></tr>';
}
$count++;
continue;
}
if (empty($questions[$qnum])) {
continue;
}
$question = $questions[$qnum];
$canedit = isteacheredit($question->course);
echo "<tr>";
echo "<td>";
if ($count != 0) {
echo "<a title=\"$strmoveup\" href=\"edit.php?up=$count&amp;sesskey=$USER->sesskey\"><img
src=\"../../pix/t/up.gif\" border=\"0\" alt=\"$strmoveup\" /></a>";
}
echo "</td>";
echo "<td>";
if ($count != $total-2) {
echo "<a title=\"$strmovedown\" href=\"edit.php?down=$count&amp;sesskey=$USER->sesskey\"><img
src=\"../../pix/t/down.gif\" border=\"0\" alt=\"$strmovedown\" /></a>";
}
echo "</td>";
if (!$quiz->shufflequestions) {
// Print and increment question number
echo '<td>'.($question->length ? $qno : '&nbsp;').'</td>';
$qno += $question->length;
} else {
echo '<td>&nbsp;</td>';
}
echo "<td>$question->name</td>";
echo "<td align=\"center\">";
quiz_print_question_icon($question, $canedit);
echo "</td>";
echo '<td align="left">';
if ($question->qtype == DESCRIPTION) {
echo "<input type=\"hidden\" name=\"q$qnum\" value=\"0\" /> \n";
} else {
echo '<input type="text" name="q'.$qnum.'" size="2" value="'.$quiz->grades[$qnum].
'" tabindex="'.$qno.'" />';
}
echo '</td><td align="center">';
if ($canedit) {
$context = $quiz->id ? '&amp;contextquiz='.$quiz->id : '';
$quiz_id = $quiz->id ? '&amp;quizid=' . $quiz->id : '';
echo "<a title=\"$strpreview\" href=\"javascript:void();\" onClick=\"openpopup('/mod/quiz/preview.php?id=$qnum$quiz_id','$strpreview','scrollbars=yes,resizable=yes,width=700,height=480', false)\">
<img src=\"../../pix/t/preview.gif\" border=\"0\" alt=\"$strpreview\" /></a>";
echo "<a title=\"$stredit\" href=\"question.php?id=$qnum$context\">
<img src=\"../../pix/t/edit.gif\" border=\"0\" alt=\"$stredit\" /></a>";
if ($allowdelete) {
echo "<a title=\"$strremove\" href=\"edit.php?delete=$count&amp;sesskey=$USER->sesskey\">
<img src=\"../../pix/t/removeright.gif\" border=\"0\" alt=\"$strremove\" /></a>";
}
}
echo "</td>";
$count++;
$sumgrade += $quiz->grades[$qnum];
}
echo "<tr><td colspan=\"5\" align=\"right\">\n";
print_string('total');
echo ": <td align=\"left\">\n";
echo "<b>$sumgrade</b>";
echo "</td><td>&nbsp;\n</td></tr>\n";
echo "<tr><td colspan=\"5\" align=\"right\">\n";
print_string('maximumgrade');
echo ": <td align=\"left\">\n";
echo '<input type="text" name="maxgrade" size="2" tabindex="'.($qno+1)
.'" value="'.$quiz->grade.'" />';
echo '</td><td align="left">';
helpbutton("maxgrade", get_string("maximumgrade"), "quiz");
echo "</td></tr>\n";
echo "<tr><td colspan=\"6\" align=\"right\">\n";
echo "<input type=\"submit\" value=\"$strsavegrades\" />\n";
echo "<input type=\"hidden\" name=\"setgrades\" value=\"save\" />\n";
echo "</td><td>&nbsp;</td><td>\n</td></tr>\n";
echo "</table>\n";
print_simple_box_end();
echo "</form>\n";
/// Form to choose to show pagebreaks and to repaginate quiz
echo '<form method="post" action="edit.php" name="showbreaks">';
echo '<input type="hidden" name="showbreaks" value="0" />';
echo '<input type="checkbox" name="showbreaks" value="1"';
if ($showbreaks) {
echo ' checked="checked"';
}
echo ' onchange="document.showbreaks.submit(); return true;" />';
print_string('showbreaks', 'quiz');
echo ' <input type="submit" value="'. get_string('go') .'" />';
if ($showbreaks) {
$perpage= array();
for ($i=0; $i<=50; ++$i) {
$perpage[$i] = $i;
}
$perpage[0] = get_string('allinone', 'quiz');
echo '<br />';
echo '<input type="hidden" name="sesskey" value="'.$USER->sesskey.'" />';
print_string('repaginate', 'quiz',
choose_from_menu($perpage, 'questionsperpage', $quiz->questionsperpage, '', '', '', true));
echo ' <input type="submit" name="repaginate" value="'. get_string('go') .'" />';
}
echo '</form>';
return $sumgrade;
}
function quiz_print_category_form($course, $current, $recurse=1, $showhidden=false) {
/// Prints a form to choose categories
/// Make sure the default category exists for this course
if (!$categories = get_records("quiz_categories", "course", $course->id, "id ASC")) {
if (!$category = quiz_get_default_category($course->id)) {
notify("Error creating a default category!");
}
}
/// Get all the existing categories now
if (!$categories = get_records_select("quiz_categories", "course = '{$course->id}' OR publish = '1'", "parent, sortorder, name ASC")) {
notify("Could not find any question categories!");
return false; // Something is really wrong
}
$categories = add_indented_names($categories);
foreach ($categories as $key => $category) {
if ($catcourse = get_record("course", "id", $category->course)) {
if ($category->publish && $category->course != $course->id) {
$category->indentedname .= " ($catcourse->shortname)";
}
$catmenu[$category->id] = $category->indentedname;
}
}
$strcategory = get_string("category", "quiz");
$strshow = get_string("show", "quiz");
$streditcats = get_string("editcategories", "quiz");
echo "<table width=\"100%\"><tr><td width=\"20\" nowrap=\"nowrap\">";
echo "<b>$strcategory:</b>&nbsp;";
echo "</td><td>";
popup_form ("edit.php?cat=", $catmenu, "catmenu", $current, "", "", "", false, "self");
echo "</td><td align=\"right\">";
echo "<form method=\"get\" action=\"category.php\">";
echo "<input type=\"hidden\" name=\"id\" value=\"$course->id\" />";
echo "<input type=\"submit\" value=\"$streditcats\" />";
echo "</form>";
echo '</td></tr></table>';
echo '<form method="post" action="edit.php" name="displayoptions">';
echo '<table><tr><td>';
echo '<input type="hidden" name="recurse" value="0" />';
echo '<input type="checkbox" name="recurse" value="1"';
if ($recurse) {
echo ' checked="checked"';
}
echo ' onchange="document.displayoptions.submit(); return true;" />';
print_string('recurse', 'quiz');
// hide-feature
echo '<br />';
echo '<input type="hidden" name="showhidden" value="0" />';
echo '<input type="checkbox" name="showhidden"';
if ($showhidden) {
echo ' checked="checked"';
}
echo ' onchange="document.displayoptions.submit(); return true;" />';
print_string('showhidden', 'quiz');
echo '</td><td valign="center">';
echo ' <input type="submit" value="'. get_string('go') .'" />';
echo '</td></tr></table></form>';
}
/**
* Prints the table of questions in a category with interactions
*
* @param object $course The course object
* @param int $categoryid The id of the question category to be displayed
* @param boolean $quizselected True if we are in the context of a particular quiz
* @param int $recurse This is 1 if subcategories should be included, 0 otherwise
* @param int $page The number of the page to be displayed
* @param int $perpage Number of questions to show per page
* @param boolean $showhidden True if also hidden questions should be displayed
*/
function quiz_print_cat_question_list($course, $categoryid, $quizselected=true,
$recurse=1, $page, $perpage, $showhidden=false) {
global $QUIZ_QUESTION_TYPE, $USER;
$strcategory = get_string("category", "quiz");
$strquestion = get_string("question", "quiz");
$straddquestions = get_string("addquestions", "quiz");
$strimportquestions = get_string("importquestions", "quiz");
$strexportquestions = get_string("exportquestions", "quiz");
$strnoquestions = get_string("noquestions", "quiz");
$strselect = get_string("select", "quiz");
$strselectall = get_string("selectall", "quiz");
$strselectnone = get_string("selectnone", "quiz");
$strcreatenewquestion = get_string("createnewquestion", "quiz");
$strquestionname = get_string("questionname", "quiz");
$strdelete = get_string("delete");
$stredit = get_string("edit");
$straction = get_string("action");
$strrestore = get_string('restore');
$straddtoquiz = get_string("addtoquiz", "quiz");
$strtype = get_string("type", "quiz");
$strcreatemultiple = get_string("createmultiple", "quiz");
$strpreview = get_string("preview","quiz");
if (!$categoryid) {
echo "<p align=\"center\"><b>";
print_string("selectcategoryabove", "quiz");
echo "</b></p>";
if ($quizselected) {
echo "<p>";
print_string("addingquestions", "quiz");
echo "</p>";
}
return;
}
if (!$category = get_record("quiz_categories", "id", "$categoryid")) {
notify("Category not found!");
return;
}
echo "<center>";
echo format_text($category->info, FORMAT_MOODLE);
echo '<table><tr>';
// check if editing of this category is allowed
if (isteacheredit($category->course)) {
echo "<td valign=\"top\"><b>$strcreatenewquestion:</b></td>";
echo '<td valign="top" align="right">';
popup_form ("question.php?category=$category->id&amp;qtype=", $QUIZ_QUESTION_TYPE, "addquestion",
"", "choose", "", "", false, "self");
echo '</td><td width="10" valign="top" align="right">';
helpbutton("questiontypes", $strcreatenewquestion, "quiz");
echo '</td></tr>';
}
else {
echo '<tr><td>';
print_string("publishedit","quiz");
echo '</td></tr>';
}
echo '<tr><td colspan="3" align="right"><font size="2">';
if (isteacheredit($category->course)) {
echo '<a href="import.php?category='.$category->id.'">'.$strimportquestions.'</a>';
helpbutton("import", $strimportquestions, "quiz");
echo ' | ';
}
echo '<a href="export.php?category='.$category->id.'">'.$strexportquestions.'</a>';
helpbutton("export", $strexportquestions, "quiz");
echo '</font></td></tr>';
echo '</table>';
echo '</center>';
$categorylist = ($recurse) ? quiz_categorylist($category->id) : $category->id;
// hide-feature
$showhidden = $showhidden ? '' : " AND hidden = '0'";
if (!$questions = get_records_select('quiz_questions', "category IN ($categorylist) AND parent = '0' $showhidden", 'qtype, name ASC', '*', $page*$perpage, $perpage)) {
echo "<p align=\"center\">";
print_string("noquestions", "quiz");
echo "</p>";
return;
}
$canedit = isteacheredit($category->course);
echo '<form method="post" action="edit.php">';
echo '<input type="hidden" name="sesskey" value="'.$USER->sesskey.'" />';
print_simple_box_start('center', '100%', '#ffffff', 0);
echo '<table id="categoryquestions" cellspacing="0"><tr>';
if ($canedit) {
echo "<th width=\"95\" nowrap=\"nowrap\" class=\"header\">$straction</th>";
}
echo "<th width=\"100%\" align=\"left\" nowrap=\"nowrap\" class=\"header\">$strquestionname</th>
<th nowrap=\"nowrap\" class=\"header\">$strtype</th>";
echo "</tr>\n";
foreach ($questions as $question) {
if ($question->qtype == RANDOM) {
//continue;
}
echo "<tr>\n";
if ($canedit) {
echo "<td>\n";
if ($quizselected) {
echo "<a title=\"$straddtoquiz\" href=\"edit.php?addquestion=$question->id&amp;sesskey=$USER->sesskey\"><img
src=\"../../pix/t/moveleft.gif\" border=\"0\" alt=\"$straddtoquiz\" /></a>&nbsp;";
}
echo "<a title=\"$strpreview\" href=\"javascript:void();\" onClick=\"openpopup('/mod/quiz/preview.php?id=$question->id','$strpreview','scrollbars=yes,resizable=yes,width=700,height=480', false)\"><img
src=\"../../pix/t/preview.gif\" border=\"0\" alt=\"$strpreview\" /></a>&nbsp;";
echo "<a title=\"$stredit\" href=\"question.php?id=$question->id\"><img
src=\"../../pix/t/edit.gif\" border=\"0\" alt=\"$stredit\" /></a>&nbsp;";
// hide-feature
if($question->hidden) {
echo "<a title=\"$strrestore\" href=\"question.php?id=$question->id&amp;hide=0&amp;sesskey=$USER->sesskey\"><img
src=\"../../pix/t/restore.gif\" border=\"0\" alt=\"$strrestore\" /></a>";
} else {
echo "<a title=\"$strdelete\" href=\"question.php?id=$question->id&amp;delete=$question->id\"><img
src=\"../../pix/t/delete.gif\" border=\"0\" alt=\"$strdelete\" /></a>";
}
echo "&nbsp;<input title=\"$strselect\" type=\"checkbox\" name=\"q$question->id\" value=\"1\" />";
echo "</td>\n";
}
if ($question->hidden) {
echo '<td class="dimmed_text">'.$question->name."</td>\n";
} else {
echo "<td>".$question->name."</td>\n";
}
echo "<td align=\"center\">\n";
quiz_print_question_icon($question, $canedit);
echo "</td>\n";
echo "</tr>\n";
}
echo "</table>\n";
print_simple_box_end();
echo '<table class="quiz-edit-selected"><tr><td colspan="2">';
echo '<a href="javascript:select_all_in(\'TABLE\', null, \'categoryquestions\');">'.$strselectall.'</a> /'.
' <a href="javascript:deselect_all_in(\'TABLE\', null, \'categoryquestions\');">'.$strselectnone.'</a>'.
'</td><td align="right"><b>'.get_string('withselected', 'quiz').':</b></td></tr><tr><td>';
if ($quizselected) {
echo "<input type=\"submit\" name=\"add\" value=\"<< $straddtoquiz\" />\n";
echo '</td><td>';
}
echo '<input type="submit" name="deleteselected" value="'.$strdelete."\" /></td><td>\n";
echo '<input type="submit" name="move" value="'.get_string('moveto', 'quiz')."\" />\n";
quiz_category_select_menu($course->id, false, true, $category->id);
echo "</td></tr></table>";
$numquestions = count($questions);
print_paging_bar($numquestions, $page, $perpage,
"edit.php?perpage=$perpage&amp;");
if ($quizselected) {
for ($i=1;$i<=10; $i++) {
$randomcount[$i] = $i;
}
echo '<br />';
print_string('addrandom', 'quiz',
choose_from_menu($randomcount, 'randomcount', '1', '', '', '', true));
echo '<input type="hidden" name="recurse" value="'.$recurse.'" />';
echo "<input type=\"hidden\" name=\"categoryid\" value=\"$category->id\" />";
echo ' <input type="submit" name="addrandom" value="'. get_string('add') .'" />';
helpbutton('random', get_string('random', 'quiz'), 'quiz');
}
echo "</form>\n";
}
?>
+19 -13
View File
@@ -1,6 +1,12 @@
<?php // $Id$
// This page lists all the instances of quiz in a particular course
/**
* This page lists all the instances of quiz in a particular course
*
* @version $Id$
* @author Martin Dougiamas and many others.
* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
* @package quiz
*/
require_once("../../config.php");
require_once("locallib.php");
@@ -49,32 +55,32 @@
$strbestgrade = get_string("bestgrade", "quiz");
$strquizcloses = get_string("quizcloses", "quiz");
$strattempts = get_string("attempts", "quiz");
$strusers = get_string("users");
$strusers = $course->students;
if (isteacher($course->id)) {
$gradecol = $strattempts;
} else {
$gradecol = $strbestgrade;
}
if ($course->format == "weeks") {
$table->head = array ($strweek, $strname, $strquizcloses, $gradecol);
$table->align = array ("center", "left", "left", "left");
$table->size = array (10, "*", "*", "*");
$table->size = array (10, "", "", "");
} else if ($course->format == "topics") {
$table->head = array ($strtopic, $strname, $strquizcloses, $gradecol);
$table->align = array ("center", "left", "left", "left");
$table->size = array (10, "*", "*", "*");
$table->size = array (10, "", "", "");
} else {
$table->head = array ($strname, $strquizcloses, $gradecol);
$table->align = array ("left", "left", "left");
$table->size = array ("*", "*", "*");
$table->size = array ("", "", "");
}
$currentsection = "";
foreach ($quizzes as $quiz) {
if (!$quiz->visible) {
if (!$quiz->visible) {
//Show dimmed if the mod is hidden
$link = "<a class=\"dimmed\" href=\"view.php?id=$quiz->coursemodule\">".format_string($quiz->name,true)."</a>";
} else {
@@ -98,15 +104,15 @@
$closequiz = userdate($quiz->timeclose);
if (isteacher($course->id)) {
if ($usercount = count_records('quiz_grades', 'quiz', $quiz->id)) {
$attemptcount = count_records_select('quiz_attempts', 'quiz = '.$quiz->id.' AND timefinish > 0');
$strviewallanswers = get_string('viewallanswers', 'quiz', $attemptcount);
$gradecol = "<a href=\"report.php?q=$quiz->id\">$strviewallanswers ($usercount $strusers)</a>";
if ($usercount = count_records_select('quiz_attempts', "quiz = '$quiz->id' AND preview = '0'", 'COUNT(DISTINCT userid)')) {
$attemptcount = count_records('quiz_attempts', 'quiz', $quiz->id, 'preview', 0);
$strviewallreports = get_string('viewallreports', 'quiz', $attemptcount);
$gradecol = "<a href=\"attempts.php?q=$quiz->id\">$strviewallreports ($usercount $strusers)</a>";
} else {
$answercount = 0;
$gradecol = "";
}
} else {
} else {
if ($bestgrade === NULL || $quiz->grade == 0) {
$gradecol = "";
} else {
+67 -67
View File
@@ -1,67 +1,67 @@
<?php // $Id$
defined('MOODLE_INTERNAL') or die('Direct access to this script is forbidden.');
?>
<script language="javascript">
<!--
/// This Javascript clock provides a little countdown in the title bar
var timerID = null;
var timerRunning = false;
var secondsleft = <?php echo $secondsleft ?>;
var titleafter = '<?php echo format_string($quiz->name) ?>';
var titlebefore = '<?php echo get_string("countdown", "quiz").": " ?>';
var alertmessage = '<?php print_string("countdownfinished", "quiz") ?>';
var alertmessage10 = '<?php print_string("countdowntenminutes", "quiz") ?>';
function stopclock() {
if (timerRunning) {
clearTimeout(timerID);
timerRunning = false;
}
}
function startclock() {
stopclock();
showtime();
}
function showtime() {
secondsleft = secondsleft - 1;
if (secondsleft == 600) {
alert(alertmessage10);
}
if (secondsleft == 0) {
stopclock();
document.title = titleafter;
return alert(alertmessage);
} else {
current = secondsleft;
var hours = Math.floor( current / 3600 );
current = current - (hours*3600);
var minutes = Math.floor( current / 60 );
current = current - (minutes*60);
var seconds = current;
var timeValue = "" + hours;
timeValue += ((minutes < 10) ? ":0" : ":") + minutes;
timeValue += ((seconds < 10) ? ":0" : ":") + seconds;
document.title = titlebefore+timeValue+' '+titleafter;
timerID = setTimeout("showtime()",1000);
timerRunning = true;
}
}
document.onLoad = startclock();
// -- End of JavaScript code -------------- -->
</script>
<?php // $Id$
defined('MOODLE_INTERNAL') or die('Direct access to this script is forbidden.');
?>
<script language="javascript">
<!--
/// This Javascript clock provides a little countdown in the title bar
var timerID = null;
var timerRunning = false;
var secondsleft = <?php echo $secondsleft ?>;
var titleafter = '<?php echo format_string($quiz->name) ?>';
var titlebefore = '<?php echo get_string("countdown", "quiz").": " ?>';
var alertmessage = '<?php print_string("countdownfinished", "quiz") ?>';
var alertmessage10 = '<?php print_string("countdowntenminutes", "quiz") ?>';
function stopclock() {
if (timerRunning) {
clearTimeout(timerID);
timerRunning = false;
}
}
function startclock() {
stopclock();
showtime();
}
function showtime() {
secondsleft = secondsleft - 1;
if (secondsleft == 600) {
alert(alertmessage10);
}
if (secondsleft == 0) {
stopclock();
document.title = titleafter;
return alert(alertmessage);
} else {
current = secondsleft;
var hours = Math.floor( current / 3600 );
current = current - (hours*3600);
var minutes = Math.floor( current / 60 );
current = current - (minutes*60);
var seconds = current;
var timeValue = "" + hours;
timeValue += ((minutes < 10) ? ":0" : ":") + minutes;
timeValue += ((seconds < 10) ? ":0" : ":") + seconds;
document.title = titlebefore+timeValue+' '+titleafter;
timerID = setTimeout("showtime()",1000);
timerRunning = true;
}
}
document.onLoad = startclock();
// -- End of JavaScript code -------------- -->
</script>
-6
View File
@@ -8,11 +8,6 @@ defined('MOODLE_INTERNAL') or die('Direct access to this script is forbidden.');
<script language="javascript" type="text/javascript">
<!--
function send_data() {
document.forms[0].submit();
return true;
}
var timesup = "<?php print_string("timesup","quiz");?>";
var quizclose = <?php echo ($quiz->timeclose - time()) - $timerstartvalue; ?>; // in seconds
var quizTimerValue = <?php echo $timerstartvalue; ?>; // in seconds
@@ -58,4 +53,3 @@ movecounter(this);
document.onload = countdown_clock();
// -->
</script>
+14
View File
@@ -0,0 +1,14 @@
<p align="center"><b>Allow adaptive questions</b></p>
<p>If you choose Yes for this option then an additional <?php print_string('mark', 'quiz') ?> button is shown for each question.
If the student presses this button then the answer to that particular question is submitted to be scored and the mark achieved
is displayed to the student. If the question is an adaptive question then it is displayed in its new state that takes the
student's answer into account and will in many cases ask the student for another input. In the simplest adaptive questions
this new state may differ only in the feedback text and prompt the student to try again; in more
complicated question also the question text and even the interaction elements can change.
</p>
<p>For most adaptive questions the student will be able to interact with a single question repeatedly until he gets the right answer.
However a penalty will usually be subtracted from the students score for each wrong attempt. This is under the teacher's control.
The question may also close itself to new attempts. For example true/false questions will not allow
the student to answer again after the first attempt because the answer would be obvious.</p>
+3
View File
@@ -0,0 +1,3 @@
<p align="center"><b>Penalty</b></p>
<p>You can specify what fraction of the achieved score should be subtracted for each wrong attempt. This is only relevant if the quiz is run in adaptive mode so that the student is allowed to have several attempts at the question. The penalty should be a number between 0 and 1. A penalty of 1 means that the student has to get the answer right on the first attempt to get any credit for it at all.</p>
@@ -0,0 +1,6 @@
<p align="center"><b>Penalty scheme</b></p>
<p>If a question allows a student to have repeated attempts then the question may impose a penalty for wrong attempts.
This penalty is a number between 0 and 1 and denotes the fraction of the final score that should be deducted as a penalty.
If the student makes several wrong attempts then the penalties for the individual attempts are combined before
being used to calculate the final grade.</p>
@@ -0,0 +1,4 @@
<p align="center"><b>Questions per page</b></p>
<p>For longer quizzes it makes sense to stretch the quiz over several pages by limiting the number of questions per page. When adding questions to the quiz page breaks will automatically be inserted according to the setting you choose here. However you will also be able to move page breaks around by hand later on the editing page.
</p>
+5
View File
@@ -0,0 +1,5 @@
<p align="center"><b>Remote Questions (RQP)</b></p>
<p>The Remote Question Protocol may be used to include questions in a quiz with all processing occurring on a remote server. Questions may be included in any format for which a server is available without any change to Moodle.</p>
+109
View File
@@ -0,0 +1,109 @@
<?php // $Id$
// quiz.php - created with Moodle 1.4 development (2004080300)
$string['action'] = 'Action';
$string['addingitemtype'] = 'Adding an Item Type';
$string['additemtype'] = 'Add item type';
$string['addrandom'] = 'Add $a random questions';
$string['answers'] = 'Answers';
$string['attemptnotcompleted'] = "Attempt has not been completed.";
$string['attemptsallowed'] = 'Multiple attempts at quiz';
$string['attemptsby'] = 'Attempts by $a';
$string['capabilities'] = 'Capabilities';
$string['chooseafile'] = 'Choose or upload a file';
$string['cloning'] = 'Cloning';
$string['cloningserver'] = 'Cloning Server';
$string['cloningserverconnectfailed'] = 'The cloning server $a failed to process an RQP request. Check that the URL is correct.';
$string['cloningserverdoesnt'] = 'The cloning server $a does not support cloning!';
$string['cloningrenderingserverdoesnt'] = 'The cloning (and rendering) server $a does not support cloning (or implicit cloning).';
$string['close'] = 'Close window';
$string['closepreview'] = 'Close preview';
$string['closereview'] = 'Close review';
$string['completedon'] = 'Completed on';
$string['confirmattemptclose'] = 'You still have an unfinished attempt. Do you want to close this and start a new attempt now?';
$string['confirmclose'] = 'You are about to close this attempt. Once you close the attempt you will no longer be able to change your answers.';
$string['continueattemptfail'] = 'Error: Could not load the last attempt';
$string['continueattemptquiz'] = 'Continue the last attempt';
$string['deleteitemtypecheck'] = 'Are you absolutely sure you want to delete \'$a\'?';
$string['duplicateresponse'] = 'This submission has been ignored because you gave an equivalent answer earlier.';
$string['editingitemtype'] = 'Editing an Item Type';
$string['editingrqp'] = 'Editing a Remote Question (RQP)';
$string['editquiz'] = 'Edit Quiz';
$string['event1'] = 'Autosave';
$string['event2'] = 'Save';
$string['event3'] = 'Grade';
$string['event5'] = 'Validate';
$string['event6'] = 'Close';
$string['fillcorrect'] = 'Fill with correct';
$string['gradeavailable'] = 'Maximum possible marks: $a';
$string['gradingdetails'] = 'Marks for this submission: $a->raw/$a->max.';
$string['gradingdetailsnopenalty'] = 'Marks for this submission: $a->raw/$a->max. Counting all previous submissions this gives <strong>$a->cur/$a->max</strong>.';
$string['gradingdetailszeropenalty'] = 'Marks for this submission: $a->raw/$a->max. You were not penalized for this submission. Counting all previous submissions this gives <strong>$a->cur/$a->max</strong>.';
$string['gradingdetailspenalty'] = 'Marks for this submission: $a->raw/$a->max with a penalty of $a->penalty. Counting all previous submissions this gives <strong>$a->cur/$a->max</strong>.';
$string['info'] = 'Info';
$string['invalidsource'] = 'The source is not accepted as valid by the server $a->rendering_server.';
$string['invalidsourcetype'] = 'Invalid source type.';
$string['itemsource'] = 'Item Source';
$string['itemsourceformat'] = 'Item Source Format';
$string['itemrequirescloning'] = 'The item source supplied is for an item template but the server for the selected item type does not support cloning (or implicit cloning). Check that the correct item type was selected';
$string['itemtype'] = 'Item Type';
$string['itemtypes'] = 'Item Types';
$string['lastanswer'] = 'Your last answer was';
$string['managetypes'] = 'Manage question types and servers';
$string['adaptive'] = 'Allow adaptive questions';
$string['finishattempt'] = 'Submit all and finish';
$string['markall'] = 'Submit page';
$string['mark'] = 'Submit';
$string['missingitemtypename'] = 'Missing name';
$string['missingsource'] = 'Missing item source';
$string['missingrenderingserver'] = 'Missing rendering server URL';
$string['newattemptfail'] = 'Error: Could not start a new attempt at the quiz';
$string['noattemptstoshow'] = 'There are no attempts to show';
$string['noquestionsfound'] = 'No questions found';
$string['notavailable'] = 'Sorry, this quiz is not available';
$string['notavailabletostudents'] = 'Note: This quiz is not currently available to your students';
$string['notimedependentitems'] = 'Time dependent items are not currently supported by the quiz module. As a work around, set a time limit for the whole quiz. Do you wish to choose a different item (or use the current item regardless)?';
$string['notypes'] = 'No item types have been defined! The administrator must add some types and set the servers to be used to process them before RQP questions can be used';
$string['notypeselected'] = 'No type selected';
$string['numberabbr'] = '#';
$string['outof'] = ' out of a maximum of ';
$string['penalty'] = 'Penalty';
$string['penaltynone'] = 'None';
$string['penaltymultiply'] = 'Multiplicative';
$string['penaltysubtract'] = 'Additive';
$string['penaltyscheme'] = 'Penalty scheme';
$string['previewquiz'] = 'Preview quiz';
$string['previous'] = 'Previous state';
$string['readytosend'] = 'You are about to finalize this quiz attempt. Are you sure you want to continue?';
$string['rendering'] = 'Rendering';
$string['renderingserver'] = 'Rendering Server';
$string['renderingserverconnectfailed'] = 'The rendering server $a failed to process an RQP request. Check that the URL is correct.';
$string['renderingserverdoesnt'] = 'The rendering server $a does not support rendering!';
$string['repaginate'] = 'Repaginate with $a questions per page';
$string['replacementoptions'] = 'Replacement Options';
$string['reports'] = 'Reports';
$string['response'] = 'Response';
$string['responses'] = 'Responses';
$string['reviewimmediately'] = 'Immediately after the attempt';
$string['reviewofattempt'] = 'Review of Attempt $a';
$string['reviewopen'] = 'Later, while the quiz is still open';
$string['reviewclosed'] = 'After the quiz is closed';
$string['reviewoptions'] = 'Students may review';
$string['reviewresponse'] = 'Review response';
$string['rqp'] = 'Remote Question';
$string['rqps'] = 'Remote Questions';
$string['savenosubmit'] = 'Save without submitting';
$string['scores'] = 'Scores';
$string['servers'] = 'Servers';
$string['showall'] = 'Show all questions on one page';
$string['showbreaks'] = 'Show page breaks';
$string['startagain'] = 'Start again';
$string['startedon'] = 'Started on';
$string['subnetnotice'] = 'This quiz has been locked so that it is only accessible from certain locations. Your computer is not on an allowed subnet. As teacher you are allowed to preview anyway.';
$string['timestr'] = '%%H:%%M:%%S on %%d/%%m/%%y';
$string['unfinished'] = 'open';
$string['validate'] = 'Validate';
$string['withselected'] = 'With selected';
$string['wronguse'] = 'You can not use this page like that';
?>
+176 -4
View File
@@ -1,13 +1,70 @@
<?php // $Id$
/// Library of function for module quiz
/**
* Library of functions for the quiz module.
*
* This contains functions that are called also from outside the quiz module
* Functions that are only called by the quiz module itself are in {@link locallib.php}
* @version $Id$
* @author Martin Dougiamas and many others.
* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
* @package quiz
*/
require_once($CFG->libdir.'/pagelib.php');
/// CONSTANTS ///////////////////////////////////////////////////////////////////
/**#@+
* Option flags for $quiz->optionflags
*/
/**
* Whether the quiz is to be run in adaptive mode
*/
define('QUIZ_ADAPTIVE', 1);
/**
* Determines if when checking whether an answer should
* be checked against all previous ones to see if it is duplicate
*/
define('QUIZ_IGNORE_DUPRESP', 2);
/**#@-*/
/**
* If start and end date for the quiz are more than this many seconds apart
* they will be represented by two separate events in the calendar
*/
define("QUIZ_MAX_EVENT_LENGTH", "432000"); // 5 days maximum
/**#@+
* The different review options are stored in the bits of $quiz->review
* These constants help to extract the options
*/
/**
* The first 6 bits refer to the time immediately after the attempt
*/
define('QUIZ_REVIEW_IMMEDIATELY', 64-1);
/**
* the next 6 bits refer to the time after the attempt but while the quiz is open
*/
define('QUIZ_REVIEW_OPEN', 4096-64);
/**
* the final 6 bits refer to the time after the quiz closes
*/
define('QUIZ_REVIEW_CLOSED', 262144-4096);
// within each group of 6 bits we determine what should be shown
define('QUIZ_REVIEW_RESPONSES', 1+64+4096); // Show responses
define('QUIZ_REVIEW_SCORES', 2*4161); // Show scores
define('QUIZ_REVIEW_FEEDBACK', 4*4161); // Show feedback
define('QUIZ_REVIEW_ANSWERS', 8*4161); // Show correct answers
// Some handling of worked solutions is already in the code but not yet fully supported
// and not switched on in the user interface.
define('QUIZ_REVIEW_SOLUTIONS', 16*4161); // Show solutions
// the 6th bit is as yet unused
/**#@-*/
/// FUNCTIONS ///////////////////////////////////////////////////////////////////
function quiz_add_instance($quiz) {
@@ -16,6 +73,8 @@ function quiz_add_instance($quiz) {
/// will create a new instance and return the id number
/// of the new instance.
quiz_process_options($quiz);
$quiz->created = time();
$quiz->timemodified = time();
$quiz->timeopen = make_timestamp($quiz->openyear, $quiz->openmonth, $quiz->openday,
@@ -80,6 +139,8 @@ function quiz_update_instance($quiz) {
/// (defined by the form in mod.html or edit.php) this function
/// will update an existing instance with new data.
quiz_process_options($quiz);
$quiz->timemodified = time();
if (isset($quiz->openyear)) { // this would not be set if we come from edit.php
$quiz->timeopen = make_timestamp($quiz->openyear, $quiz->openmonth, $quiz->openday,
@@ -93,6 +154,9 @@ function quiz_update_instance($quiz) {
return false; // some error occurred
}
// Delete any preview attempts
delete_records('quiz_attempts', 'preview', '1', 'quiz', $quiz->id);
if (isset($quiz->optionsettingspref)) {
set_user_preference('quiz_optionsettingspref', $quiz->optionsettingspref);
}
@@ -150,7 +214,7 @@ function quiz_delete_instance($id) {
if ($attempts = get_records("quiz_attempts", "quiz", "$quiz->id")) {
foreach ($attempts as $attempt) {
if (! delete_records("quiz_responses", "attempt", "$attempt->id")) {
if (! delete_records("quiz_states", "attempt", "$attempt->id")) {
$result = false;
}
}
@@ -164,7 +228,7 @@ function quiz_delete_instance($id) {
$result = false;
}
if (! delete_records("quiz_question_grades", "quiz", "$quiz->id")) {
if (! delete_records("quiz_question_instances", "quiz", "$quiz->id")) {
$result = false;
}
@@ -235,13 +299,32 @@ function quiz_user_outline($course, $user, $mod, $quiz) {
}
function quiz_user_complete($course, $user, $mod, $quiz) {
/// Print a detailed representation of what a user has done with
/// a given particular instance of this module, for user activity reports.
if ($attempts = get_records_select('quiz_attempts', "userid='$user->id' AND quiz='$quiz->id'", 'attempt ASC')) {
if ($quiz->grade != 0 && $grade = get_record('quiz_grades', 'userid', $user->id, 'quiz', $quiz->id)) {
echo get_string('grade').': '.format_float($grade->grade, $quiz->decimalpoints).'/'.$quiz->grade.'<br />';
}
foreach ($attempts as $attempt) {
echo get_string('attempt', 'quiz').' '.$attempt->attempt.': ';
if ($attempt->timefinish == 0) {
print_string('unfinished');
} else {
echo format_float($attempt->sumgrades, $quiz->decimalpoints).'/'.$quiz->sumgrades;
}
echo ' - '.userdate($attempt->timemodified).'<br />';
}
} else {
print_string('noattempts', 'quiz');
}
return true;
}
function quiz_cron () {
/// Function to be run periodically according to the moodle cron
/// This function searches for things that need to be done, such
@@ -481,4 +564,93 @@ function quiz_print_recent_mod_activity($activity, $course, $detail=false) {
return;
}
/**
* Pre-process the options form data
*
* Encode the review options from the setup form into the bits of $form->review
* and other options into $form->optionflags
* The form data is passed by reference and modified by this function
* @return integer
* @param object $form The variables set on the form.
*/
function quiz_process_options(&$form) {
$optionflags = 0;
$review = 0;
if (!empty($form->adaptive)) {
$optionflags += QUIZ_ADAPTIVE;
}
if (isset($form->responsesimmediately)) {
$review += (QUIZ_REVIEW_RESPONSES & QUIZ_REVIEW_IMMEDIATELY);
unset($form->responsesimmediately);
}
if (isset($form->responsesopen)) {
$review += (QUIZ_REVIEW_RESPONSES & QUIZ_REVIEW_OPEN);
unset($form->responsesopen);
}
if (isset($form->responsesclosed)) {
$review += (QUIZ_REVIEW_RESPONSES & QUIZ_REVIEW_CLOSED);
unset($form->responsesclosed);
}
if (isset($form->scoreimmediately)) {
$review += (QUIZ_REVIEW_SCORES & QUIZ_REVIEW_IMMEDIATELY);
unset($form->scoreimmediately);
}
if (isset($form->scoreopen)) {
$review += (QUIZ_REVIEW_SCORES & QUIZ_REVIEW_OPEN);
unset($form->scoreopen);
}
if (isset($form->scoreclosed)) {
$review += (QUIZ_REVIEW_SCORES & QUIZ_REVIEW_CLOSED);
unset($form->scoreclosed);
}
if (isset($form->feedbackimmediately)) {
$review += (QUIZ_REVIEW_FEEDBACK & QUIZ_REVIEW_IMMEDIATELY);
unset($form->feedbackimmediately);
}
if (isset($form->feedbackopen)) {
$review += (QUIZ_REVIEW_FEEDBACK & QUIZ_REVIEW_OPEN);
unset($form->feedbackopen);
}
if (isset($form->feedbackclosed)) {
$review += (QUIZ_REVIEW_FEEDBACK & QUIZ_REVIEW_CLOSED);
unset($form->feedbackclosed);
}
if (isset($form->answersimmediately)) {
$review += (QUIZ_REVIEW_ANSWERS & QUIZ_REVIEW_IMMEDIATELY);
unset($form->answersimmediately);
}
if (isset($form->answersopen)) {
$review += (QUIZ_REVIEW_ANSWERS & QUIZ_REVIEW_OPEN);
unset($form->answersopen);
}
if (isset($form->answersclosed)) {
$review += (QUIZ_REVIEW_ANSWERS & QUIZ_REVIEW_CLOSED);
unset($form->answersclosed);
}
if (isset($form->solutionsimmediately)) {
$review += (QUIZ_REVIEW_SOLUTIONS & QUIZ_REVIEW_IMMEDIATELY);
unset($form->solutionsimmediately);
}
if (isset($form->solutionsopen)) {
$review += (QUIZ_REVIEW_SOLUTIONS & QUIZ_REVIEW_OPEN);
unset($form->solutionsopen);
}
if (isset($form->solutionsclosed)) {
$review += (QUIZ_REVIEW_SOLUTIONS & QUIZ_REVIEW_CLOSED);
unset($form->solutionsclosed);
}
$form->review = $review;
$form->optionflags = $optionflags;
}
?>
+1822 -1220
View File
File diff suppressed because it is too large Load Diff
+108 -146
View File
@@ -1,9 +1,38 @@
<!-- This page defines the form to create or edit an instance of this module -->
<!-- It is used from /course/mod.php. The whole instance is available as $form. -->
<!-- Some javascript to allow for the hiding of advanced options
This code was derived from mod.html in the resource module -->
<script language="javascript" type="text/javascript">
function showhide (id, set) {
divobj = document.getElementById(id);
butobj = document.getElementById(id+'button');
prefobj = document.getElementById(id+'pref');
if (set == true) {
if (prefobj.value == '1') {
divobj.style.display = 'block';
butobj.value = '<?php print_string("hideadvancedsettings") ?>';
} else {
divobj.style.display = 'none';
butobj.value = '<?php print_string("showadvancedsettings") ?>...';
}
} else {
if (prefobj.value == '1') {
divobj.style.display = 'none';
butobj.value = '<?php print_string("showadvancedsettings") ?>...';
prefobj.value = '0';
} else {
divobj.style.display = 'block';
butobj.value = '<?php print_string("hideadvancedsettings") ?>';
prefobj.value = '1';
}
}
}
</script>
<?php
require_once("$CFG->dirroot/mod/quiz/locallib.php");
// Ensure that all form variables are initialized
/// Ensure that all form variables are initialized
if (!isset($form->name)) {
$form->name = "";
}
@@ -28,17 +57,11 @@
if (!isset($form->decimalpoints)) {
$form->decimalpoints = $CFG->quiz_decimalpoints;
}
if (!isset($form->feedback)) {
$form->feedback = $CFG->quiz_showfeedback;
}
if (!isset($form->correctanswers)) {
$form->correctanswers = $CFG->quiz_showanswer;
}
if (!isset($form->review)) {
$form->review = $CFG->quiz_allowreview;
$form->review = $CFG->quiz_review;
}
if (!isset($form->questionsperpage)) {
$form->questionsperpage = 0;
$form->questionsperpage = $CFG->quiz_questionsperpage;
}
if (!isset($form->shufflequestions)) {
$form->shufflequestions = $CFG->quiz_shufflequestions;
@@ -64,15 +87,16 @@
if (!isset($form->popup)) {
$form->popup = $CFG->quiz_popup;
}
if (!isset($form->optionflags)) {
$form->optionflags = $CFG->quiz_optionflags;
}
if (!isset($form->penaltyscheme)) {
$form->penaltyscheme = $CFG->quiz_penaltyscheme;
}
$fix = 0; // This will later be set to 1 if some of the variables have been fixed by the admin.
// the following are used for drop-down menus
$reviewoptions = array();
$reviewoptions[0] = get_string('reviewnever', 'quiz');
$reviewoptions[1] = get_string('reviewafter', 'quiz');
$reviewoptions[2] = get_string('reviewbefore', 'quiz');
$reviewoptions[3] = get_string('reviewalways', 'quiz');
$yesnooptions = array();
$yesnooptions[0] = get_string("no");
@@ -85,11 +109,11 @@
$attemptoptions[$i] = "$i ".strtolower(get_string("attempts", "quiz"));
}
$grades = array();
for ($i=100; $i>=1; $i--) {
$grades[$i] = $i;
}
$grades[0] = get_string("nograde");
$penaltyoptions = array();
$penaltyoptions[QUIZ_PENALTYNONE] = get_string('penaltynone', 'quiz');
$penaltyoptions[QUIZ_PENALTYMULTIPLY] = get_string('penaltymultiply', 'quiz');
$penaltyoptions[QUIZ_PENALTYSUBTRACT] = get_string('penaltysubtract', 'quiz');
?>
@@ -220,7 +244,7 @@
</td>
</tr>
<?php } else $fix=1 ?>
<!--
<?php if (!$CFG->quiz_fix_attemptonlast) { ?>
<tr valign="top">
<td align="right"><b><?php print_string("eachattemptbuildsonthelast", "quiz") ?>:</b></td>
@@ -235,7 +259,7 @@
</td>
</tr>
<?php } else $fix=1 ?>
-->
<?php if (!$CFG->quiz_fix_grademethod) { ?>
<tr valign="top">
<td align="right"><b><?php print_string("grademethod", "quiz") ?>:</b></td>
@@ -248,6 +272,30 @@
</tr>
<?php } else $fix=1 ?>
<?php if (!$CFG->quiz_fix_adaptive) { ?>
<tr valign="top">
<td align="right"><b><?php print_string("adaptive", "quiz") ?>:</b></td>
<td>
<?php
choose_from_menu($yesnooptions, "adaptive", $form->optionflags & QUIZ_ADAPTIVE, "");
helpbutton("adaptive", get_string("adaptive","quiz"), "quiz");
?>
</td>
</tr>
<?php } else $fix=1 ?>
<?php if (!$CFG->quiz_fix_penaltyscheme) { ?>
<tr valign="top">
<td align="right"><b><?php print_string("penaltyscheme", "quiz") ?>:</b></td>
<td>
<?php
choose_from_menu($penaltyoptions, "penaltyscheme", "$form->penaltyscheme", "");
helpbutton("penaltyscheme", get_string("penaltyscheme","quiz"), "quiz");
?>
</td>
</tr>
<?php } ?>
<?php if (!$CFG->quiz_fix_decimalpoints) { ?>
<tr valign="top">
<td align="right"><b><?php print_string("decimalpoints", "quiz") ?>:</b></td>
@@ -265,53 +313,11 @@
</tr>
<?php } else $fix=1 ?>
<?php if (!$CFG->quiz_fix_showfeedback) { ?>
<tr valign="top">
<td align="right"><b><?php print_string("showfeedback", "quiz") ?>:</b></td>
<td>
<?php
choose_from_menu($yesnooptions, "feedback", "$form->feedback", "");
helpbutton("feedback", get_string("showfeedback","quiz"), "quiz");
?>
</td>
</tr>
<?php } else $fix=1 ?>
<?php if (!$CFG->quiz_fix_showanswer) { ?>
<tr valign="top">
<td align="right"><b><?php print_string("showcorrectanswer", "quiz") ?>:</b></td>
<td>
<?php
choose_from_menu($yesnooptions, "correctanswers", "$form->correctanswers", "");
helpbutton("correctanswers", get_string("showcorrectanswer","quiz"), "quiz");
?>
</td>
</tr>
<?php } else $fix=1 ?>
<?php if (!$CFG->quiz_fix_allowreview) { ?>
<tr valign="top">
<td align="right"><b><?php print_string("allowreview", "quiz") ?>:</b></td>
<td>
<?php
choose_from_menu($reviewoptions, "review", "$form->review", "");
helpbutton("review2", get_string("allowreview","quiz"), "quiz");
?>
</td>
</tr>
<?php } else $fix=1 ?>
<?php if (!$CFG->quiz_fix_maximumgrade) { ?>
<tr valign="top">
<td align="right"><b><?php print_string("maximumgrade") ?>:</b></td>
<td>
<?php
choose_from_menu($grades, "grade", "$form->grade", "");
helpbutton("maxgrade", get_string("maximumgrade"), "quiz");
?>
</td>
</tr>
<?php } else $fix=1 ?>
<?php if (!$CFG->quiz_fix_review) {
echo '<tr valign="top">';
include('reviewoptions.html');
echo '</tr>';
} else $fix=1 ?>
<?php if (!$CFG->quiz_fix_popup) { ?>
<tr valign="top">
@@ -352,35 +358,6 @@
<?php if ($fix) { ?>
<!-- Some javascript to allow for the hiding of advanced options
This code was derived from mod.html in the resource module -->
<script language="javascript" type="text/javascript">
function showhide (id, set) {
divobj = document.getElementById(id);
butobj = document.getElementById(id+'button');
prefobj = document.getElementById(id+'pref');
if (set == true) {
if (prefobj.value == '1') {
divobj.style.display = 'block';
butobj.value = '<?php print_string("hideadvancedsettings") ?>';
} else {
divobj.style.display = 'none';
butobj.value = '<?php print_string("showadvancedsettings") ?>...';
}
} else {
if (prefobj.value == '1') {
divobj.style.display = 'none';
butobj.value = '<?php print_string("showadvancedsettings") ?>...';
prefobj.value = '0';
} else {
divobj.style.display = 'block';
butobj.value = '<?php print_string("hideadvancedsettings") ?>';
prefobj.value = '1';
}
}
}
</script>
<!-- Now comes a copy of the above but enclosed in a div with id optionsettings and with
the conditions reversed, so that it shows exactly the options that are usually hidden.
The visibility of this div is controlled by javascript. -->
@@ -466,7 +443,7 @@
</td>
</tr>
<?php } ?>
<!--
<?php if ($CFG->quiz_fix_attemptonlast) { ?>
<tr valign="top">
<td align="right"><b><?php print_string("eachattemptbuildsonthelast", "quiz") ?>:</b></td>
@@ -481,7 +458,7 @@
</td>
</tr>
<?php } ?>
-->
<?php if ($CFG->quiz_fix_grademethod) { ?>
<tr valign="top">
<td align="right"><b><?php print_string("grademethod", "quiz") ?>:</b></td>
@@ -494,6 +471,30 @@
</tr>
<?php } ?>
<?php if ($CFG->quiz_fix_adaptive) { $fix = 1; ?>
<tr valign="top">
<td align="right"><b><?php print_string("adaptive", "quiz") ?>:</b></td>
<td>
<?php
choose_from_menu($yesnooptions, "adaptive", $form->optionflags & QUIZ_ADAPTIVE, "");
helpbutton("adaptive", get_string("adaptive","quiz"), "quiz");
?>
</td>
</tr>
<?php } ?>
<?php if ($CFG->quiz_fix_penaltyscheme) { $fix = 1; ?>
<tr valign="top">
<td align="right"><b><?php print_string("penaltyscheme", "quiz") ?>:</b></td>
<td>
<?php
choose_from_menu($penaltyoptions, "penaltyscheme", "$form->penaltyscheme", "");
helpbutton("penaltyscheme", get_string("penaltyscheme","quiz"), "quiz");
?>
</td>
</tr>
<?php } ?>
<?php if ($CFG->quiz_fix_decimalpoints) { ?>
<tr valign="top">
<td align="right"><b><?php print_string("decimalpoints", "quiz") ?>:</b></td>
@@ -511,53 +512,11 @@
</tr>
<?php } ?>
<?php if ($CFG->quiz_fix_showfeedback) { ?>
<tr valign="top">
<td align="right"><b><?php print_string("showfeedback", "quiz") ?>:</b></td>
<td>
<?php
choose_from_menu($yesnooptions, "feedback", "$form->feedback", "");
helpbutton("feedback", get_string("showfeedback","quiz"), "quiz");
?>
</td>
</tr>
<?php } ?>
<?php if ($CFG->quiz_fix_showanswer) { ?>
<tr valign="top">
<td align="right"><b><?php print_string("showcorrectanswer", "quiz") ?>:</b></td>
<td>
<?php
choose_from_menu($yesnooptions, "correctanswers", "$form->correctanswers", "");
helpbutton("correctanswers", get_string("showcorrectanswer","quiz"), "quiz");
?>
</td>
</tr>
<?php } ?>
<?php if ($CFG->quiz_fix_allowreview) { ?>
<tr valign="top">
<td align="right"><b><?php print_string("allowreview", "quiz") ?>:</b></td>
<td>
<?php
choose_from_menu($reviewoptions, "review", "$form->review", "");
helpbutton("review", get_string("allowreview","quiz"), "quiz");
?>
</td>
</tr>
<?php } ?>
<?php if ($CFG->quiz_fix_maximumgrade) { ?>
<tr valign="top">
<td align="right"><b><?php print_string("maximumgrade") ?>:</b></td>
<td>
<?php
choose_from_menu($grades, "grade", "$form->grade", "");
helpbutton("maxgrade", get_string("maximumgrade"), "quiz");
?>
</td>
</tr>
<?php } ?>
<?php if ($CFG->quiz_fix_review) {
echo '<tr valign="top">';
include('reviewoptions.html');
echo '</tr>';
} ?>
<?php if ($CFG->quiz_fix_popup) { ?>
<tr valign="top">
@@ -591,6 +550,8 @@
</tr>
<?php } ?>
</table>
</div></td></tr>
@@ -602,6 +563,7 @@
</table>
<br />
<input type="hidden" name="grade" value="<?php echo $form->grade; ?>" />
<!-- these hidden variables are always the same -->
<input type="hidden" name="course" value="<?php p($form->course) ?>" />
-173
View File
@@ -1,173 +0,0 @@
<?php // $Id$
// A quick way to add lots of questions to a category (and a quiz)
require_once('../../config.php');
require_once('locallib.php');
$category = required_param('category');
// This script can only be called while editing a quiz
if (!isset($SESSION->modform)) {
error('You have used this page incorrectly!');
} else {
$modform = $SESSION->modform;
}
if (! $category = get_record('quiz_categories', 'id', $category)) {
error('Course ID is incorrect');
}
if (! $course = get_record('course', 'id', $category->course)) {
error('Course ID is incorrect');
}
require_login($course->id, false);
if (!isteacher($course->id)) {
error('Only teachers can use this page!');
}
/// If data submitted, then process and store.
if ($form = data_submitted() and confirm_sesskey()) {
if ($form->randomcreate > 0) {
$newquestionids = array(); // this will hold the ids of the random questions
// find existing random questions in this category
$random = RANDOM;
if ($existingquestions = get_records_select('quiz_questions', "qtype = '$random' AND category = '$category->id'")) {
// now remove the ones that are already used in this quiz
if ($questionids = explode(',', $modform->questions)) {
foreach ($questionids as $questionid) {
unset($existingquestions[$questionid]);
}
}
// now take as many of these as needed
$i = 0;
while (($existingquestion = array_pop($existingquestions)) and ($i < $form->randomcreate)) {
if ($existingquestion->questiontext == "$form->recurse") {
// this question has the right recurse property, so use it
$newquestionids[] = $existingquestion->id;
$i++;
}
}
$randomcreate = $form->randomcreate - $i; // the number of additional random questions needed.
} else {
$randomcreate = $form->randomcreate;
}
if ($randomcreate > 0) {
$question->qtype = RANDOM;
$question->category = $category->id;
$question->name = get_string('random', 'quiz') .' ('. $category->name .')';
$question->questiontext = "$form->recurse"; // we use the questiontext field to store the info
// on whether to include questions in subcategories
$question->image = '';
$question->defaultgrade = $form->randomgrade;
$question->hidden = 1;
for ($i=0; $i<$randomcreate; $i++) {
$question->stamp = make_unique_id_code(); // Set the unique code (not to be changed)
if (!$newquestionids[] = insert_record('quiz_questions', $question)) {
error('Could not insert new random question!');
}
}
}
// Add them to the quiz
if (!empty($modform->questions)) {
$questionids = explode(',', $modform->questions);
} else {
$questionids = array();
}
foreach ($newquestionids as $newquestionid) {
$modform->grades[$newquestionid] = $form->randomgrade;
$modform->sumgrades += $form->randomgrade;
}
$newquestionids = array_merge($questionids, $newquestionids);
$modform->questions = implode(',', $newquestionids);
$SESSION->modform = $modform;
if (!set_field('quiz', 'questions', $modform->questions, 'id', $modform->instance)) {
error('Could not save question list');
}
quiz_questiongrades_update($modform->grades, $modform->instance);
}
redirect('edit.php');
}
/// Otherwise print the form
/// Print headings
$strquestions = get_string('questions', 'quiz');
$strpublish = get_string('publish', 'quiz');
$strdelete = get_string('delete');
$straction = get_string('action');
$stradd = get_string('add');
$strcancel = get_string('cancel');
$strsavechanges = get_string('savechanges');
$strbacktoquiz = get_string('backtoquiz', 'quiz');
$strquizzes = get_string('modulenameplural', 'quiz');
$streditingquiz = get_string(isset($SESSION->modform->instance) ? 'editingquiz' : 'editquestions', 'quiz');
$strcreatemultiple = get_string('createmultiple', 'quiz');
print_header_simple($strcreatemultiple, $strcreatemultiple,
"<a href=\"$CFG->wwwroot/mod/quiz/index.php?id=$course->id\">$strquizzes</a>".
" -> <a href=\"edit.php\">$streditingquiz</a> -> $strcreatemultiple");
print_heading_with_help($strcreatemultiple, 'createmultiple', 'quiz');
if (!$categories = quiz_get_category_menu($course->id, false)) {
error('No categories!');
}
for ($i=1;$i<=100; $i++) {
$randomcount[$i] = $i;
}
for ($i=1;$i<=10; $i++) {
$gradecount[$i] = $i;
}
$options = array();
$options[0] = get_string('no');
$options[1] = get_string('yes');
print_simple_box_start('center');
echo '<form method="POST" action="multiple.php">';
echo "<input type=\"hidden\" name=\"sesskey\" value=\"$USER->sesskey\">";
echo '<table cellpadding="5">';
echo '<tr><td align="right">';
print_string('category', 'quiz');
echo ':</td><td>';
// choose_from_menu($categories, "category", "$category->id", "");
quiz_category_select_menu($course->id, false, true, $category->id );
echo '</tr>';
echo '<tr><td align="right">';
print_string('randomcreate', 'quiz');
echo ':</td><td>';
choose_from_menu($randomcount, 'randomcreate', '10', '');
echo '</tr>';
echo '<tr><td align="right">';
print_string('defaultgrade', 'quiz');
echo ':</td><td>';
choose_from_menu($gradecount, 'randomgrade', '1', '');
echo '</tr>';
echo '<tr><td>&nbsp;</td><td>';
echo ' <input type="hidden" name="category" value="'. $category->id .'" />';
echo ' <input type="submit" name="save" value="'. get_string('add') .'" />';
echo '</td></tr>';
echo '</table>';
echo '</form>';
print_simple_box_end();
print_footer($course);
?>
+2 -4
View File
@@ -63,9 +63,7 @@ class page_quiz extends page_generic_activity {
}
if(empty($morebreadcrumbs) && $this->user_allowed_editing()) {
$buttons = '<table><tr><td><form target="'.$CFG->framename.'" method="get" action="edit.php">'.
'<input type="hidden" name="quizid" value="'.$this->activityrecord->id.'" />'.
'<input type="submit" value="'.get_string('editquestions', 'quiz').'" /></form></td><td>'.
$buttons = '<table><tr><td>'.
update_module_button($this->modulerecord->id, $this->courserecord->id, get_string('modulename', 'quiz')).'</td>';
if(!empty($CFG->showblocksonmodpages)) {
$buttons .= '<td><form target="'.$CFG->framename.'" method="get" action="view.php">'.
@@ -87,4 +85,4 @@ class page_quiz extends page_generic_activity {
}
}
?>
?>
+209 -88
View File
@@ -1,104 +1,225 @@
<?php // $Id$
/**
* This page displays a preview of a question
*
* The preview uses the option settings from the quiz within which the question
* is previewed or the default settings if no quiz is specified. The question session
* information is stored in the session as an array of subsequent states rather
* than in the database.
* @version $Id$
* @author Alex Smith as part of the Serving Mathematics project
* {@link http://maths.york.ac.uk/serving_maths}
* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
* @package quiz
*/
require_once("../../config.php");
require_once("locallib.php");
require_once("../../config.php");
require_once("locallib.php");
require_variable($id); // question id
optional_variable($quizid, 0);
if (! $question = get_record("quiz_questions", "id", $id)) {
error("This question doesn't exist");
}
if (! $category = get_record("quiz_categories", "id", $question->category)) {
error("This question doesn't belong to a valid category!");
}
if ($quizid) {
if (! $quiz = get_record('quiz', 'id', $quizid)) {
error('Incorrect quiz id');
$id = required_param('id', PARAM_INT); // question id
// if no quiz id is specified then a dummy quiz with default options is used
$quizid = optional_param('quizid', 0, PARAM_INT);
// Test if we are continuing an attempt at a question
$continue = optional_param('continue', 0, PARAM_BOOL);
// Check for any of the submit buttons
$fillcorrect = optional_param('fillcorrect', 0, PARAM_BOOL);
$markall = optional_param('markall', 0, PARAM_BOOL);
$finishattempt = optional_param('finishattempt', 0, PARAM_BOOL);
$back = optional_param('back', 0, PARAM_BOOL);
$startagain = optional_param('startagain', 0, PARAM_BOOL);
// We are always continuing an attempt if a submit button was pressed with the
// exception of the start again button
if ($fillcorrect || $markall || $finishattempt || $back) {
$continue = true;
} else if ($startagain) {
$continue = false;
}
require_login();
if (!isteacherinanycourse()) {
error('This page is for teachers only');
}
if (!$continue) {
// Start a new attempt; delete the old session
unset($SESSION->quizpreview);
// Redirect to ourselves but with continue=1; prevents refreshing the page
// from restarting an attempt (needed so that random questions don't change)
$quizid = $quizid ? '&amp;quizid=' . $quizid : '';
redirect($CFG->wwwroot . '/mod/quiz/preview.php?id=' . $id . $quizid .
'&amp;continue=1');
}
if (empty($quizid)) {
// get a sample quiz to be used as a skeleton
// this should really be done properly by instantiating a quiz object
if (!$quiz = get_records('quiz')) {
error('You have to create at least one quiz before using this preview');
}
$quiz = array_values($quiz);
$quiz = $quiz[0];
// set everything to the default values
foreach($quiz as $field => $value) {
$quizfield = "quiz_".$field;
if(isset($CFG->$quizfield)) {
$quiz->$field = $CFG->$quizfield;
}
else {
$quiz->$field = 0;
}
}
} else if (!$quiz = get_record('quiz', 'id', $quizid)) {
error("Quiz id $quizid does not exist");
}
$quiz->id = 0; // just for safety
$quiz->questions = $id;
// Load the question information
if (!$questions = get_records('quiz_questions', 'id', $id)) {
error('Could not load question');
}
$questions[$id]->quiz = 0;
$questions[$id]->maxgrade = 1;
if (!$category = get_record("quiz_categories", "id", $questions[$id]->category)) {
error("This question doesn't belong to a valid category!");
}
if (!isteacher($category->course) and !$category->publish) {
error("You can't preview these questions!");
}
} else {
// make fake quiz
$quiz->id = 0;
$quiz->course = $category->course;
$quiz->name = "";
$quiz->intro = "";
$quiz->timeopen = 0;
$quiz->timeclose = 0;
$quiz->attempts = $CFG->quiz_attempts;
$quiz->attemptonlast = $CFG->quiz_attemptonlast;
$quiz->feedback = $CFG->quiz_showfeedback;
$quiz->correctanswers = $CFG->quiz_showanswer;
$quiz->grademethod = $CFG->quiz_grademethod;
$quiz->review = $CFG->quiz_allowreview;
$quiz->shufflequestions = $CFG->quiz_shufflequestions;
$quiz->shuffleanswers = $CFG->quiz_shuffleanswers;
$quiz->questions = "$question->id";
$quiz->sumgrades = $question->defaultgrade;
$quiz->grade = 0;
$quiz->timecreated = 0;
$quiz->timemodified = 0;
$quiz->timelimit = $CFG->quiz_timelimit;
$quiz->password = $CFG->quiz_password;
$quiz->subnet = $CFG->quiz_subnet;
$quiz->popup = $CFG->quiz_popup;
}
$qtype = $question->qtype;
require_login();
if (!isteacherinanycourse()) {
error('This page is for teachers only');
}
if (!isteacher($category->course) and !$category->publish) {
error("You can't preview these questions!");
}
print_header();
print_heading(get_string("previewquestion","quiz"));
if ($rawanswers = data_submitted()) {
$rawanswers = (array)$rawanswers;
foreach ($rawanswers as $key => $value) { // Parse input for question->response
$postedId = quiz_extract_posted_id($key);
if ($id == $postedId) $question->response[$key] = trim($value);
// Load the question type specific information
if (!quiz_get_question_options($questions)) {
error(get_string('newattemptfail', 'quiz'));
}
print_simple_box_start("center", "90%");
$resultdetails = $QUIZ_QTYPES[$question->qtype]->grade_response($question, quiz_qtype_nameprefix($question));
$question->maxgrade = 1;
$quiz->correctanswers=1;
$quiz->feedback=1;
$quiz->grade=1;
$QUIZ_QTYPES[$question->qtype]->print_question(1, $quiz, $question, true, $resultdetails);
// Create a dummy quiz attempt
$attempt = quiz_create_attempt($quiz, 0);
$attempt->id = 0;
print_simple_box_end();
echo "<p align=\"center\"><input name=\"backbtn\" type=\"button\"
onclick=\"history.back()\" value=\"".get_string("back", "quiz")."\" />\n";
echo "<input type=\"button\" onClick=\"window.close()\" value=\"" . get_string("close", "quiz") . "\" /></p>\n";
// Restore the history of question sessions from the moodle session or create
// new sessions. Make $states a reference to the states array in the moodle
// session.
if (isset($SESSION->quizpreview->states) and $SESSION->quizpreview->questionid
== $id) {
// Reload the question session history from the moodle session
$states =& $SESSION->quizpreview->states;
$historylength = count($states) - 1;
if ($back && $historylength > 0) {
// Go back one step in the history
unset($states[$historylength]);
$historylength--;
}
} else {
// Record the question id in the moodle session
$SESSION->quizpreview->questionid = $id;
// Create an empty session for the question
if (!$newstates =
quiz_restore_question_sessions($questions, $quiz, $attempt)) {
error(get_string('newattemptfail', 'quiz'));
}
$SESSION->quizpreview->states = array($newstates);
$states =& $SESSION->quizpreview->states;
$historylength = 0;
}
} else { //show question list
if (!$fillcorrect && !$back && ($form = data_submitted())) {
$form = (array)$form;
$submitted = true;
// Create a new item in the history of question states (don't simplify!)
$states[$historylength + 1] = array();
$states[$historylength + 1][$id] = clone($states[$historylength][$id]);
$historylength++;
$curstate =& $states[$historylength][$id];
// Process the responses
unset($form['id']);
unset($form['quizid']);
unset($form['continue']);
unset($form['markall']);
unset($form['finishattempt']);
unset($form['back']);
unset($form['startagain']);
$event = $finishattempt ? QUIZ_EVENTCLOSE : ($markall ? QUIZ_EVENTGRADE : QUIZ_EVENTSAVE);
if ($actions = quiz_extract_responses($questions, $form, $event)) {
quiz_process_responses($questions[$id], $states[$historylength][$id], $actions[$id], $quiz, $attempt);
if (QUIZ_EVENTGRADE != $curstate->event && QUIZ_EVENTCLOSE != $curstate->event) {
// Update the current state rather than creating a new one
$historylength--;
unset($states[$historylength]);
$states = array_values($states);
$curstate =& $states[$historylength][$id];
}
}
} else {
$submitted = false;
$curstate =& $states[$historylength][$id];
}
$options = quiz_get_renderoptions($quiz, $curstate);
// Fill in the correct responses (unless the question is in readonly mode)
if ($fillcorrect && !$options->readonly) {
$curstate->responses = $QUIZ_QTYPES[$questions[$id]->qtype]
->get_correct_responses($questions[$id], $curstate);
}
$strpreview = get_string('previewquestion', 'quiz');
print_header($strpreview);
print_heading($strpreview);
echo '<p align="center">' . get_string('modulename', 'quiz') . ': ';
if (empty($quizid)) {
echo '[' . get_string('default', 'quiz') . ']';
} else {
p($quiz->name);
}
echo '<br />'.get_string('question', 'quiz').': ';
p($questions[$id]->name);
echo "</p>\n";
$number = 1;
echo "<form method=\"post\" action=\"preview.php\">\n";
echo "<input type=\"hidden\" name=\"id\" value=\"$id\" />\n";
echo "<input type=\"hidden\" name=\"quizid\" value=\"$quizid\" />\n";
echo "<input type=\"hidden\" name=\"continue\" value=\"1\" />\n";
print_simple_box_start("center", "90%");
$readonly = false;
$resultdetails = NULL;
$QUIZ_QTYPES[$question->qtype]->print_question(1, $quiz, $question, $readonly, $resultdetails);
print_simple_box_end();
quiz_print_quiz_question($questions[$id], $curstate, $number, $quiz, $options);
echo "<br />";
echo "<center>";
echo "<input name=\"submit\" type=\"submit\" value=\"".get_string("checkanswer", "quiz")."\" />\n";
echo "<input type=\"button\" onClick=\"window.close()\" value=\"" . get_string("close", "quiz") . "\" />";
echo "</center>";
echo "</form>";
}
print_footer();
echo '<br />';
echo '<center>';
// Print the mark and finish attempt buttons
echo '<input name="markall" type="submit" value="' . get_string('markall',
'quiz') . "\" />\n";
echo '<input name="finishattempt" type="submit" value="' .
get_string('finishattempt', 'quiz') . "\" />\n";
echo '<br />';
echo '<br />';
// Print the fill correct button (unless the question is in readonly mode)
if (!$options->readonly) {
echo '<input name="fillcorrect" type="submit" value="' .
get_string('fillcorrect', 'quiz') . "\" />\n";
}
// Print the navigation buttons
if ($historylength > 0) {
echo '<input name="back" type="submit" value="' . get_string('previous',
'quiz') . "\" />\n";
}
// Print the start again button
echo '<input name="startagain" type="submit" value="' .
get_string('startagain', 'quiz') . "\" />\n";
// Print the close window button
echo '<input type="button" onclick="window.close()" value="' .
get_string('closepreview', 'quiz') . "\" />";
echo '</center>';
echo '</form>';
print_footer();
?>
+81 -55
View File
@@ -1,14 +1,22 @@
<?php // $Id$
/* Responsibilities of question.php
* - create new question (category, qtype)
* - edit question (id, contextquiz (o))
* - delete question from quiz (delete, sesskey)
* - delete question (in two steps)
* - if question is in use: display this conflict (allow to hide the question?)
* - else: confirm deletion and delete from database (sesskey, id, delete, confirm)
* - cancel (cancel)
* (o) = optional
*/
/*
* Page for editing questions
*
* This page shows the question editing form or processes the following actions:
* - create new question (category, qtype)
* - edit question (id, contextquiz (optional))
* - delete question from quiz (delete, sesskey)
* - delete question (in two steps)
* - if question is in use: display this conflict (allow to hide the question?)
* - else: confirm deletion and delete from database (sesskey, id, delete, confirm)
* - cancel (cancel)
* @version $Id$
* @author Martin Dougiamas and many others. This has recently been extensively
* rewritten by members of the Serving Mathematics project
* {@link http://maths.york.ac.uk/serving_maths}
* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
* @package quiz
*/
require_once("../../config.php");
require_once("locallib.php");
@@ -18,7 +26,7 @@
$qtype = optional_param('qtype');
$category = optional_param('category');
$contextquiz = optional_param('contextquiz'); // the quiz from which this question is being edited
if (isset($_REQUEST['cancel'])) {
@@ -93,9 +101,8 @@
if (isset($_REQUEST['delete'])) {
if (isset($confirm) and confirm_sesskey()) {
if ($confirm == md5($delete)) {
if (record_exists('quiz_responses', 'question', $question->id) or
record_exists('quiz_responses', 'originalquestion', $question->id) or
record_exists('quiz_question_grades', 'question', $question->id)) {
if (record_exists('quiz_question_instances', 'question', $question->id) or
record_exists('quiz_states', 'originalquestion', $question->id)) {
if (!set_field('quiz_questions', 'hidden', 1, 'id', $delete)) {
error('Was not able to hide question');
}
@@ -103,20 +110,23 @@
if (!delete_records("quiz_questions", "id", $question->id)) {
error("An error occurred trying to delete question (id $question->id)");
}
if (!delete_records("quiz_questions", "parent", $question->id)) {
error("An error occurred trying to delete question (id $question->id)");
}
}
redirect("edit.php");
} else {
error("Confirmation string was incorrect");
}
} else {
if ($quiznames = quizzes_question_used($id)) {
$a->questionname = $question->name;
$a->quiznames = implode(', ', $quiznames);
notify(get_string('questioninuse', 'quiz', $a));
}
notice_yesno(get_string("deletequestioncheck", "quiz", $question->name),
notice_yesno(get_string("deletequestioncheck", "quiz", $question->name),
"question.php?sesskey=$USER->sesskey&amp;id=$question->id&amp;delete=$delete&amp;confirm=".md5($delete), "edit.php");
}
print_footer($course);
@@ -124,12 +134,13 @@
}
if ($form = data_submitted() and confirm_sesskey()) {
if (isset($form->versioning)) { // use new code that
// handles whether to overwrite or copy a question and keeps
// track of the versions in the quiz_question_versions table
// $replaceinquiz is an array with the ids of all quizzes in which the teacher has chosen to replace the old version
if (isset($form->versioning) && isset($question->id)) {
// use new code that handles whether to overwrite or copy a question
// and keeps track of the versions in the quiz_question_version table
// $replaceinquiz is an array with the ids of all quizzes in which
// the teacher has chosen to replace the old version
$replaceinquiz = array();
foreach($form as $key => $val) {
if ($tmp = quiz_parse_fieldname($key, 'q')) {
@@ -142,23 +153,28 @@
// $quizlist is an array with the ids of quizzes which use this question
$quizlist = array();
if ($grades = get_records('quiz_question_grades', 'question', $form->id)) {
foreach($grades as $grade) {
$quizlist[$grade->quiz] = $grade->quiz;
if ($instances = get_records('quiz_question_instances', 'question', $question->id)) {
foreach($instances as $instance) {
$quizlist[$instance->quiz] = $instance->quiz;
}
}
if (isset($form->makecopy)) { // explicitly requested copies should be unhidden
$question->hidden = 0;
}
// Logic to determine whether old version should be overwritten
$makecopy = isset($form->makecopy) || (!$form->id); unset($form->makecopy);
$noresponses = $makecopy || !(record_exists('quiz_responses', 'question', $form->id) or
record_exists('quiz_responses', 'originalquestion', $form->id)); // Should be improved once the preview flag exists so that teacher preview responses are not taken into account
$notused = $makecopy || !record_exists('quiz_question_grades', 'question', $form->id);
$replaceinall = ($quizlist == $replaceinquiz); // question is being replaced in all quizzes
$replaceold = (!$makecopy && $noresponses && ($notused || $replaceinall));
$makecopy = isset($form->makecopy) || (!$form->id); unset($form->makecopy);
if ($makecopy) {
$replaceold = false;
} else {
// this should be improved to exclude teacher preview responses and empty responses
// the current code leaves many unneeded questions in the database
$hasresponses = record_exists('quiz_states', 'question', $form->id) or
record_exists('quiz_states', 'originalquestion', $form->id);
$replaceinall = ($quizlist == $replaceinquiz); // question is being replaced in all quizzes
$replaceold = !$hasresponses && $replaceinall;
}
if (!$replaceold) { // create a new question
$oldquestionid = $question->id;
@@ -169,10 +185,8 @@
}
unset($question->id);
}
unset($makecopy, $noresponses, $notused, $replaceinall, $replaceold);
unset($makecopy, $hasresponses, $replaceinall, $replaceold);
$question = $QUIZ_QTYPES[$qtype]->save_question($question, $form, $course);
if(!isset($question->id)) {
error("Failed to save the question!");
}
@@ -182,7 +196,7 @@
$version = new object();
$version->oldquestion = $oldquestionid;
$version->newquestion = $question->id;
$version->userid = $USER->id; // field still needs to be added to table
$version->userid = $USER->id;
$version->timestamp = time();
foreach($replaceinquiz as $qid) {
@@ -193,11 +207,10 @@
}
/// now update the question references in the quizzes
$quizzes = implode(',', $replaceinquiz);
if (empty($quizzes) || !$quizzes = get_records_list("quiz", "id", $quizzes)) {
if (empty($replaceinquiz) || !$quizzes = get_records_list("quiz", "id", implode(',', $replaceinquiz))) {
$quizzes = array();
}
foreach($quizzes as $quiz) {
$questionlist = ",$quiz->questions,"; // a little hack with the commas here. not nice but effective
$questionlist = str_replace(",$oldquestionid,", ",$question->id,", $questionlist);
@@ -205,9 +218,10 @@
if (!set_field("quiz", 'questions', $questionlist, 'id', $quiz->id)) {
error("Could not update questionlist in quiz $quiz->id!");
}
// the quiz_question_grades table needs to be updated too (aah, the joys of duplication :)
if (!set_field('quiz_question_grades', 'question', $question->id, 'quiz', $quiz->id, 'question', $oldquestionid)) {
error("Could not update question grade!");
// the quiz_question_instances table needs to be updated too (aah, the joys of duplication :)
if (!set_field('quiz_question_instances', 'question', $question->id, 'quiz', $quiz->id, 'question', $oldquestionid)) {
error("Could not update question instance!");
}
if (isset($SESSION->modform) && (int)$SESSION->modform->instance === (int)$quiz->id) {
$SESSION->modform->questions = $questionlist;
@@ -215,24 +229,29 @@
unset($SESSION->modform->grades[$oldquestionid]);
}
}
// fix responses
// set originalquestion in states
if ($attempts = get_records_list('quiz_attempts', 'quiz', implode(',', $replaceinquiz))) {
foreach ($attempts as $attempt) {
if (!set_field('quiz_responses', 'originalquestion', $oldquestionid, 'attempt', $attempt->id, 'question', $oldquestionid)
or !set_field('quiz_responses', 'question', $question->id, 'attempt', $attempt->id, 'question', $oldquestionid)) {
error("Could not point responses to new question");
}
set_field('quiz_states', 'originalquestion', $oldquestionid, 'attempt', $attempt->id, 'question', $question->id, 'originalquestion', '0');
}
}
}
redirect("edit.php");
} else { // use the old code which simply overwrites old versions
} else {
// use the old code which simply overwrites old versions
// it is also used for creating new questions
$question = $QUIZ_QTYPES[$qtype]->save_question($question, $form, $course);
$quizlist = array();
}
if (empty($question->errors) &&
$QUIZ_QTYPES[$qtype]->finished_edit_wizard($form)) {
$QUIZ_QTYPES[$question->qtype]->get_question_options($question);
// Automagically regrade all attempts (and states) in the affected quizzes
quiz_regrade_question_in_quizzes($question, $quizlist);
redirect("edit.php");
}
}
}
$grades = array(1,0.9,0.8,0.75,0.70,0.66666,0.60,0.50,0.40,0.33333,0.30,0.25,0.20,0.16666,0.142857,0.10,0.05,0);
foreach ($grades as $grade) {
@@ -274,6 +293,9 @@
if (empty($question->image)) {
$question->image = "";
}
if (!isset($question->penalty)) {
$question->penalty = 0.1;
}
// Set up some Richtext editing if necessary
if ($usehtmleditor = can_use_richtext_editor()) {
@@ -281,13 +303,17 @@
} else {
$defaultformat = FORMAT_MOODLE;
}
if (isset($question->errors)) {
$err = $question->errors;
}
echo '<br />';
print_simple_box_start('center');
require('questiontypes/'.$QUIZ_QTYPES[$qtype]->name().'/editquestion.php');
print_simple_box_end();
if ($usehtmleditor) {
if ($usehtmleditor) {
use_html_editor('questiontext');
}
@@ -25,14 +25,14 @@ if ($form) {
or $answers[0]->answer
and $calculatedmessages[] = $formulaerrors
or $calculatedmesages[] = get_string('missingformula', 'quiz');
$answers[0]->tolerance = array_shift($form->tolerance)
or $answers[0]->tolerance = 0.0;
is_numeric($answers[0]->tolerance)
or $calculatedmessages[] = get_string('tolerancemustbenumeric', 'quiz');
$answers[0]->feedback = array_shift($form->feedback);
// Let's trust the drop down menus.
$answers[0]->tolerancetype = array_shift($form->tolerancetype);
@@ -80,7 +80,7 @@ if ($form) {
$units[0]->unit = array_shift($form->unit);
array_shift($form->multiplier); // In case it is not 1.0
$units[0]->multiplier = 1.0; // Must!
// Accept other units if they have legal multipliers
$i = 1;
foreach ($form->multiplier as $key => $multiplier) {
@@ -108,7 +108,7 @@ if ($form) {
require("$CFG->dirroot/mod/quiz/questiontypes/datasetdependent/datasetitems.php");
exit();
}
/***** Now continue by preparing for the second page ******
***** in the question wizard: "questiondatasets.html" ******/
$datasetmessage = '';
@@ -138,7 +138,7 @@ if ($form) {
$hiddeninputnames[] = 'multiplier[]';
$hiddeninputvalues[] = $unit->multiplier;
}
/*** Determine possible and mandatory datasets... ***/
$possibledatasets = $qtypeobj->find_dataset_names($question->questiontext);
$mandatorydatasets = array();
@@ -154,7 +154,7 @@ if ($form) {
require("$CFG->dirroot/mod/quiz/questiontypes/datasetdependent/questiondatasets.html");
exit();
}
} else {
/*********************************************************/
/***** First page in question wizard - calculated.html! **/
@@ -163,7 +163,7 @@ if ($form) {
// The layout of the editing page will only support
// one formula alternative for calculated questions.
// However, the code behind supports up to six formulas
// and the database store and attempt/review framework
// and the database store and attempt/review framework
// does not have any limit.
$answers= array();
for ($i=0; $i<6; $i++) {
@@ -177,7 +177,8 @@ if ($form) {
$answers[$i]->correctanswerformat = "1"; // ... decimals
}
if (!empty($question->id)) {
$answersraw = $qtypeobj->get_answers($question);
$QUIZ_QTYPES[$question->qtype]->get_question_options($question);
$answersraw = $question->options->answers;
if (!empty($answersraw)) {
/*** Overwrite the default valued answer slots ***
*** with correct values from database ***/
@@ -233,7 +234,6 @@ if ($form) {
}
}
}
print_heading_with_help(get_string("editingcalculated", "quiz"), "calculated", "quiz");
require("$CFG->dirroot/mod/quiz/questiontypes/calculated/calculated.html");
@@ -1,38 +0,0 @@
<?php // $Id$
///////////////////////////////
/// CALCULATED HELPER CLASS ///
///////////////////////////////
/// OVERRIDDEN EDITION OF THE CLASS FOR QUESTION TYPE NUMERICAL ///
class quiz_calculated_qtype_numerical_helper extends quiz_numerical_qtype {
/// A question with qtype=CALCULATED will appear as a NUMERICAL
/// question in a quiz and it is therefore desirable to reuse
/// most of the grading and printing framework.
/// However, the CALCULATED functions will be fed with data
/// that differs from what the NUMERICAL qtype can handle.
/// Therefore the CALCULATED qtype often act by modifying the data
/// it has been fed and then pass it on to the NUMERICAL equivalent.
/// The NUMERICAL equivalent are called through an instance of this class,
/// for which the method get_answers has been modified so that its
/// caller will be fed with data fed to the qtype CALCULATED and then
/// modified to fit qtype NUMERICAL.
// This solution assumes a single-threaded environment
// for each instance...
var $answers = false;
function get_answers($question, $addedcondition='') {
return $this->answers;
}
function set_answers($calculatedanswers) {
$this->answers = $calculatedanswers;
}
}
//// END OF CLASS ////
?>
+190 -209
View File
@@ -11,23 +11,153 @@ class quiz_calculated_qtype extends quiz_dataset_dependent_questiontype {
// Used by the function custom_generator_tools:
var $calcgenerateidhasbeenadded = false;
function get_answers($question) {
global $CFG;
return get_records_sql(
"SELECT a.*, c.tolerance, c.tolerancetype, c.correctanswerlength, c.correctanswerformat, c.id as calcid
FROM {$CFG->prefix}quiz_answers a,
{$CFG->prefix}quiz_calculated c
WHERE c.question = $question->id AND a.id = c.answer");
}
function name() {
return 'calculated';
}
function get_question_options(&$question) {
// First get the datasets and default options
if(false === parent::get_question_options($question)) {
return false;
}
if (!$options = get_record('quiz_calculated', 'question', $question->id)) {
notify("No options were found for calculated question
#{$question->id}! Proceeding with defaults.");
$options = new stdClass;
$options->tolerance = 0.01;
$options->tolerancetype = 1; // relative
$options->correctanswerlength = 2;
$options->correctanswerformat = 1; // decimals
}
$question->options->tolerance = $options->tolerance;
$question->options->tolerancetype = $options->tolerancetype;
$question->options->correctanswerlength = $options->correctanswerlength;
$question->options->correctanswerformat = $options->correctanswerformat;
// For historic reasons we also need these fields in the answer objects.
// This should eventually be removed and related code changed to use
// the values in $question->options instead.
foreach ($question->options->answers as $key => $answer) {
$answer = &$question->options->answers[$key]; // for PHP 4.x
$answer->calcid = $options->id;
$answer->tolerance = $options->tolerance;
$answer->tolerancetype = $options->tolerancetype;
$answer->correctanswerlength = $options->correctanswerlength;
$answer->correctanswerformat = $options->correctanswerformat;
}
$this->get_numerical_units($question);
return true;
}
function get_numerical_units(&$question) {
$virtualqtype = $this->get_virtual_qtype();
return $virtualqtype->get_numerical_units($question);
}
function save_question_options($question, $options) {
// Get old answers:
$oldanswers = array();
if ($this->get_question_options($question)) {
$oldanswers = $question->options->answers;
}
// Update with new answers
$answerrec->question = $calcrec->question = $question->id;
foreach ($options->answers as $newanswer) {
$answerrec->answer = $newanswer->answer;
$answerrec->fraction = $newanswer->fraction;
$answerrec->feedback = $newanswer->feedback;
$calcrec->tolerance = $newanswer->tolerance;
$calcrec->tolerancetype = $newanswer->tolerancetype;
$calcrec->correctanswerlength = $newanswer->correctanswerlength;
$calcrec->correctanswerformat = $newanswer->correctanswerformat;
if ($oldanswer = array_shift($oldanswers)) {
// Reuse old record:
$calcrec->answer = $answerrec->id = $oldanswer->id;
$calcrec->id = $oldanswer->calcid;
if (!update_record('quiz_answers', $answerrec)) {
error("Unable to update answer for calculated question #{$question->id}!");
} else {
// notify("Answer updated successfully for calculated question $question->name");
}
if (!update_record('quiz_calculated', $calcrec)) {
error("Unable to update options for calculated question #{$question->id}!");
} else {
// notify("Options updated successfully for calculated question $question->name");
}
} else {
unset($answerrec->id);
unset($calcrec->id);
if (!($calcrec->answer = insert_record('quiz_answers',
$answerrec))) {
error("Unable to insert answer for calculated question $question->name");
} else {
// notify("Answer inserted successfully for calculated question $question->name");
}
if (!insert_record('quiz_calculated', $calcrec)) {
error("Unable to insert options calculared question $question->name");
} else {
// notify("Options inserted successfully for calculated question $question->name");
}
}
}
// Delete excessive records:
foreach ($oldanswers as $oldanswer) {
if (!delete_records('quiz_answers', 'id', $oldanswer->id)) {
error("Unable to delete old answers for calculated question $question->name");
} else {
// notify("Old answers deleted successfully for calculated question $question->name");
}
if (!delete_records('quiz_calculated', 'id', $oldanswer->calcid)) {
error("Unable to delete old options for calculated question $question->name");
} else {
// notify("Old options deleted successfully for calculated question $question->name");
}
}
$virtualqtype = $this->get_virtual_qtype();
$virtualqtype->save_numerical_units($question);
return true;
}
function print_question_formulation_and_controls(&$question, &$state, $quiz,
$options) {
// Substitute variables in questiontext before giving the data to the
// virtual type for printing
$virtualqtype = $this->get_virtual_qtype();
$unit = $virtualqtype->get_default_numerical_unit($question);
foreach ($question->options->answers as $key => $answer) {
$answer = &$question->options->answers[$key]; // for PHP 4.x
$answer->answer = $this->substitute_variables($answer->answer,
$state->options->dataset);
// apply_unit
}
$question->questiontext = parent::substitute_variables(
$question->questiontext, $state->options->dataset);
$virtualqtype->print_question_formulation_and_controls($question,
$state, $quiz, $options);
}
function grade_responses(&$question, &$state, $quiz) {
// Forward the grading to the virtual qtype
foreach ($question->options->answers as $key => $answer) {
$answer = &$question->options->answers[$key]; // for PHP 4.x
$answer->answer = $this->substitute_variables($answer->answer,
$state->options->dataset);
}
return parent::grade_responses($question, $state, $quiz);
}
function create_virtual_qtype() {
require('modifiednumericalqtype.php');
return new quiz_calculated_qtype_numerical_helper();
global $CFG;
require_once("$CFG->dirroot/mod/quiz/questiontypes/numerical/questiontype.php");
return new quiz_numerical_qtype();
}
function supports_dataset_item_generation() {
@@ -116,7 +246,7 @@ class quiz_calculated_qtype extends quiz_dataset_dependent_questiontype {
// Reformat according to the precision $regs[4]:
// Determine the format 0.[1-9][0-9]* for the nbr...
// Determine the format 0.[1-9][0-9]* for the nbr...
$p10 = 0;
while ($nbr < 1) {
--$p10;
@@ -157,7 +287,7 @@ class quiz_calculated_qtype extends quiz_dataset_dependent_questiontype {
}
}
// The larger of the values decide the sign in case the
// The larger of the values decide the sign in case the
// have equal different signs (which they really must not have)
if ($regs[2] + $regs[3] > 0) {
return $nbr;
@@ -172,7 +302,8 @@ class quiz_calculated_qtype extends quiz_dataset_dependent_questiontype {
}
function comment_header($question) {
$answers = $this->get_answers($question);
$this->get_question_options($question);
$answers = $question->options->answers;
$strheader = '';
$delimiter = '';
foreach ($answers as $answer) {
@@ -193,17 +324,25 @@ class quiz_calculated_qtype extends quiz_dataset_dependent_questiontype {
}
// Get answers
$answers = $this->get_answers($question);
$answers = $question->options->answers;
$stranswers = get_string('answer', 'quiz');
$strmin = get_string('min', 'quiz');
$strmax = get_string('max', 'quiz');
$errors = '';
$delimiter = ': ';
$state = new stdClass;
$state->responses = array();
$state->options = new stdClass;
$virtualqtype = $this->get_virtual_qtype();
foreach ($answers as $answer) {
$calculated = quiz_qtype_calculated_calculate_answer(
$answer->answer, $data, $answer->tolerance,
$answer->tolerancetype, $answer->correctanswerlength,
$answer->correctanswerformat, $unit);
$state->responses[''] = $calculated->answer;
$virtualqtype->get_tolerance_interval($question, $state);
$calculated->min = $state->options->min;
$calculated->max = $state->options->max;
if ($calculated->min === '') {
// This should mean that something is wrong
$errors .= " -$calculated->answer";
@@ -213,6 +352,7 @@ class quiz_calculated_qtype extends quiz_dataset_dependent_questiontype {
}
$strmin .= $delimiter.$calculated->min;
$strmax .= $delimiter.$calculated->max;
$delimiter = ', ';
}
return "$stranswers<br/>$strmin<br/>$strmax<br/>$errors";
@@ -224,104 +364,6 @@ class quiz_calculated_qtype extends quiz_dataset_dependent_questiontype {
'3' => get_string('geometric', 'quiz'));
}
function save_question_options($question, $options) {
// Get old answers:
$oldanswers = $this->get_answers($question)
or $oldanswers = array(); // if there are none
// Update with new answers
$answerrec->question = $calcrec->question = $question->id;
foreach ($options->answers as $newanswer) {
$answerrec->answer = $newanswer->answer;
$answerrec->fraction = $newanswer->fraction;
$answerrec->feedback = $newanswer->feedback;
$calcrec->tolerance = $newanswer->tolerance;
$calcrec->tolerancetype = $newanswer->tolerancetype;
$calcrec->correctanswerlength = $newanswer->correctanswerlength;
$calcrec->correctanswerformat = $newanswer->correctanswerformat;
if ($oldanswer = array_shift($oldanswers)) {
// Reuse old record:
$calcrec->answer = $answerrec->id = $oldanswer->id;
$calcrec->id = $oldanswer->calcid;
if (!update_record('quiz_answers', $answerrec)) {
error("Unable to update answer for calculated question $question->name");
} else {
// notify("Answer updated successfully for calculated question $question->name");
}
if (!update_record('quiz_calculated', $calcrec)) {
error("Unable to update options calculared question $question->name");
} else {
// notify("Options updated successfully for calculated question $question->name");
}
} else {
unset($answerrec->id);
unset($calcrec->id);
if (!($calcrec->answer = insert_record('quiz_answers',
$answerrec))) {
error("Unable to insert answer for calculated question $question->name");
} else {
// notify("Answer inserted successfully for calculated question $question->name");
}
if (!insert_record('quiz_calculated', $calcrec)) {
error("Unable to insert options calculared question $question->name");
} else {
// notify("Options inserted successfully for calculated question $question->name");
}
}
}
// Delete excessive records:
foreach ($oldanswers as $oldanswer) {
if (!delete_records('quiz_answers', 'id', $oldanswer->id)) {
error("Unable to delete old answers for calculated question $question->name");
} else {
// notify("Old answers deleted successfully for calculated question $question->name");
}
if (!delete_records('quiz_calculated', 'id', $oldanswer->calcid)) {
error("Unable to delete old options for calculated question $question->name");
} else {
// notify("Old options deleted successfully for calculated question $question->name");
}
}
// Get old units (just like for numerical questions)
$oldunits = get_records('quiz_numerical_units',
'question', $question->id)
or $oldunits = array(); // if there are none
if (1 == count($options->units) && !$options->units[0]->unit) {
/// Only default unit and it is empty, so drop it:
$options->units = array();
}
foreach ($options->units as $newunit) {
$newunit->question = $question->id;
if ($oldunit = array_shift($oldunits)) {
$newunit->id = $oldunit->id;
if (!update_record('quiz_numerical_units', $newunit)) {
error("Unable to update unit $newunit->unit for $question->name");
} else {
// notify("Unit $newunit->unit was updated successfully for $question->name");
}
} else {
if (!insert_record('quiz_numerical_units', $newunit)) {
error("Unable to insert unit $newunit->unit for $question->name");
} else {
// notify("Unit $newunit->unit was inserted successfully for question $question->name");
}
}
}
// Delete excessive unit records
foreach ($oldunits as $oldunit) {
if (!delete_records('quiz_numerical_units', 'id', $oldunit->id)) {
error("Unable to delete old unit $oldunit->unit for question $question->name");
} else {
notify("Deleted old unit $oldunit->unit successfully for question $question->name");
}
}
return true;
}
function dataset_options($question, $name, $renameabledatasets=false) {
// Takes datasets from the parent implementation but
// filters options that are currently not accepted by calculated
@@ -361,48 +403,34 @@ class quiz_calculated_qtype extends quiz_dataset_dependent_questiontype {
return $datasetmenus;
}
function grade_response($question, $nameprefix) {
/// Determines the answers and then lets the
/// NUMERICAL question type take care of the
/// grading...
list($datasetnumber, $individualdata) =
$this->parse_datasetinput($question->response[$nameprefix]);
// find the raw answer material
global $CFG;
if (!($answers = $this->get_answers($question))) {
notify("Error no answers found for question $question->id");
}
/// Find a default unit:
if ($unit = get_record('quiz_numerical_units',
'question', $question->id, 'multiplier', 1.0)) {
$unit = $unit->unit;
} else {
$unit = '';
}
// Construct answers for the numerical question type
foreach ($answers as $aid => $answer) {
$answernumerical = quiz_qtype_calculated_calculate_answer(
$answer->answer, $individualdata,
function get_correct_responses(&$question, &$state) {
foreach ($question->options->answers as $key => $answer) {
if (((float) $answer->fraction) === 1.0) {
$virtualqtype = $this->get_virtual_qtype();
$unit = $virtualqtype->get_default_numerical_unit($question);
$answernumerical = quiz_qtype_calculated_calculate_answer(
$answer->answer, $state->options->dataset,
$answer->tolerance, $answer->tolerancetype,
$answer->correctanswerlength,
$answer->correctanswerformat, $unit);
$answers[$aid]->answer = $answernumerical->answer;
$answers[$aid]->min = $answernumerical->min;
$answers[$aid]->max = $answernumerical->max;
return array('' => $answernumerical->answer);
}
}
return null;
}
// Forward the grading to the virtual qtype
$virtualnameprefix = $this->create_virtual_nameprefix(
$nameprefix, $question->response[$nameprefix]);
unset($question->response[$nameprefix]);
$virtualqtype = $this->get_virtual_qtype();
$virtualqtype->set_answers($answers);
return $virtualqtype->grade_response($question,
$virtualnameprefix);
function substitute_variables($str, $dataset) {
$formula = parent::substitute_variables($str, $dataset);
if ($error = quiz_qtype_calculated_find_formula_errors($formula)) {
return $error;
}
/// Calculate the correct answer
eval('$str = '.$formula.';');
return $str;
}
function print_question_grading_details(&$question, &$state, $quiz, $options) {
// do nothing
}
}
//// END OF CLASS ////
@@ -418,57 +446,10 @@ function quiz_qtype_calculated_calculate_answer($formula, $individualdata,
/// ->answer the correct answer
/// ->min the lower bound for an acceptable response
/// ->max the upper bound for an accetpable response
/// Exchange formula variables with the correct values...
foreach ($individualdata as $name => $value) {
$formula = str_replace('{'.$name.'}', "($value)", $formula);
}
if (ereg('\\{([^}]*)\\}', $formula, $regs)) {
// This is the normal case for a recently added question.
// Return a notification about it
$calculated->answer = $calculated->min = $calculated->max = '';
return $calculated;
} else if ($error = quiz_qtype_calculated_find_formula_errors($formula)) {
$calculated->answer = $error;
$calculated->min = $calculated->max = '';
return $calculated;
}
/// Calculate the correct answer
eval('$answer = '.$formula.';');
/// Calculate min and max
switch ($tolerancetype) {
case '1': case 'relative':
/// Recalculate the tolerance and fall through
/// to the nominal case:
$tolerance = $answer * $tolerance;
// Falls through to the nominal case -
case '2': case 'nominal':
$tolerance = abs($tolerance); // important
$max = $answer + $tolerance;
$min = $answer - $tolerance;
break;
case '3': case 'geometric':
$quotient = 1 + abs($tolerance);
if ($answer >= 0) {
$max = $answer * $quotient;
$min = $answer / $quotient;
} else {
$min = $answer * $quotient;
$max = $answer / $quotient;
}
break;
default:
error("Unknown tolerance type $tolerancetype");
}
$calculated->min = $min;
$calculated->max = $max;
global $QUIZ_QTYPES;
$answer = $QUIZ_QTYPES[CALCULATED]->substitute_variables($formula, $individualdata);
if ('1' == $answerformat) { /* Answer is to have $answerlength decimals */
/*** Adjust to the correct number of decimals ***/
@@ -476,7 +457,7 @@ function quiz_qtype_calculated_calculate_answer($formula, $individualdata,
if ($answerlength) {
/* Try to include missing zeros at the end */
if (ereg('^(.*\\.)(.*)$', $calculated->answer, $regs)) {
$calculated->answer = $regs[1] . substr(
$regs[2] . '00000000000000000000000000000000000000000x',
@@ -502,7 +483,7 @@ function quiz_qtype_calculated_calculate_answer($formula, $individualdata,
$sign = '';
}
// Determine the format 0.[1-9][0-9]* for the answer...
// Determine the format 0.[1-9][0-9]* for the answer...
$p10 = 0;
while ($answer < 1) {
--$p10;
@@ -547,7 +528,7 @@ function quiz_qtype_calculated_calculate_answer($formula, $individualdata,
} else {
$calculated->answer = 0.0;
}
/// Return the result
return $calculated;
}
@@ -566,7 +547,7 @@ function quiz_qtype_calculated_find_formula_errors($formula) {
// Strip away empty space and lowercase it
$formula = strtolower(str_replace(' ', '', $formula));
$safeoperatorchar = '-+/*%>:^~<?=&|!';
$safeoperatorchar = '-+/*%>:^~<?=&|!'; /* */
$operatorornumber = "[$safeoperatorchar.0-9eE]";
@@ -580,7 +561,7 @@ function quiz_qtype_calculated_find_formula_errors($formula) {
return get_string('illegalformulasyntax', 'quiz', $regs[0]);
}
break;
// Zero argument functions
case 'pi':
if ($regs[3]) {
@@ -607,7 +588,7 @@ function quiz_qtype_calculated_find_formula_errors($formula) {
return get_string('functiontakesoneortwoargs','quiz',$regs[2]);
}
break;
// Functions that must have two arguments
case 'atan2': case 'fmod': case 'pow':
if ($regs[5] || empty($regs[4])) {
@@ -636,7 +617,7 @@ function quiz_qtype_calculated_find_formula_errors($formula) {
$formula = ereg_replace("^$regs[2]\\([^)]*\\)", '1', $formula);
}
}
if (ereg("[^$safeoperatorchar.0-9eE]+", $formula, $regs)) {
return get_string('illegalformulasyntax', 'quiz', $regs[0]);
} else {
@@ -12,16 +12,115 @@ define("LINK", "3");
class quiz_dataset_dependent_questiontype extends quiz_default_questiontype {
var $virtualqtype= false;
// Contains picked dataset numbers by category. The idea is to
// reuse dataset number for each category within a quiz.
var $datasetnumbers = array();
var $virtualqtype = false;
function name() {
return 'datasetdependent';
}
function save_question_options($question, $options) {
// Default does nothing...
return true;
}
function create_session_and_responses(&$question, &$state, $quiz, $attempt) {
// Find out how many datasets are available
global $CFG;
if(!$maxnumber = (int)get_field_sql(
"SELECT MAX(a.itemcount)
FROM {$CFG->prefix}quiz_dataset_definitions a,
{$CFG->prefix}quiz_question_datasets b
WHERE b.question = $question->id
AND a.id = b.datasetdefinition")) {
error("Couldn't get the specified dataset for a calculated " .
"question! (question: {$question->id}, " .
"datasetnumber: {$datasetnumber})");
}
// Choose a random dataset
$state->options->datasetnumber = rand(1, $maxnumber);
$state->options->dataset =
$this->pick_question_dataset($question,$state->options->datasetnumber);
$state->responses = array('' => '');
return true;
}
function restore_session_and_responses(&$question, &$state) {
if (!ereg('^dataset([0-9]+)[^-]*-(.*)$',
$state->responses[''], $regs)) {
notify ("Wrongly formatted raw response answer " .
"{$state->responses['']}! Could not restore session for " .
" question #{$question->id}.");
$state->options->datasetnumber = 1;
$state->options->dataset = array();
$state->responses = array('' => '');
return false;
}
// Restore the chosen dataset
$state->options->datasetnumber = $regs[1];
$state->options->dataset =
$this->pick_question_dataset($question,$state->options->datasetnumber);
$state->responses = array('' => $regs[2]);
return true;
}
function save_session_and_responses(&$question, &$state) {
$responses = 'dataset'.$state->options->datasetnumber.'-'.
$state->responses[''];
// Set the legacy answer field
if (!set_field('quiz_states', 'answer', $responses, 'id',
$state->id)) {
return false;
}
return true;
}
function print_question_formulation_and_controls(&$question, &$state, $quiz,
$options) {
// Substitute variables in questiontext before giving the data to the
// virtual type for printing
$virtualqtype = $this->get_virtual_qtype();
$unit = $virtualqtype->get_default_numerical_unit($question);
foreach ($question->options->answers as $answer) {
$answer->answer = $this->substitute_variables($answer->answer,
$state->options->dataset);
}
$question->questiontext = $this->substitute_variables(
$question->questiontext, $state->options->dataset);
$virtualqtype->print_question_formulation_and_controls($question,
$state, $quiz, $options);
}
function get_correct_responses(&$question, &$state) {
foreach ($question->options->answers as $answer) {
$answer->answer = $this->substitute_variables(
$answer->answer, $state->options->dataset);
if (((float) $answer->fraction) === 1.0) {
return array('' => $answer->answer);
}
}
return null;
}
function grade_responses(&$question, &$state, $quiz) {
// Forward the grading to the virtual qtype
foreach ($question->options->answers as $answer) {
$answer->answer = $this->substitute_variables($answer->answer,
$state->options->dataset);
}
$virtualqtype = $this->get_virtual_qtype();
return $virtualqtype->grade_responses($question, $state, $quiz) ;
}
function substitute_variables($str, $dataset) {
foreach ($dataset as $name => $value) {
$str = str_replace('{'.$name.'}', $value, $str);
}
return $str;
}
function uses_quizfile($question, $relativefilepath) {
// Check whether the specified file is available by any
// dataset item on this question...
@@ -41,7 +140,165 @@ class quiz_dataset_dependent_questiontype extends quiz_default_questiontype {
return parent::uses_quizfile($question, $relativefilepath);
}
}
function finished_edit_wizard(&$form) {
return isset($form->backtoquiz);
}
function save_question($question, $form, $course, $subtypeoptions=false) {
// For dataset dependent questions a wizard is used for editing
// questions. Therefore calls from question.php are ignored.
// Instead questions are saved when this method is called by
// editquestion.php
if ($subtypeoptions) {
// Let's save the question
// We need to save the question first (in order to get the id)
// We then save the dataset definitions and finally we
// save the subtype options...
// Save question
if (!empty($question->id)) { // Question already exists
$question->version++; // Update version number of question
if (!update_record("quiz_questions", $question)) {
error("Could not update question!");
}
} else { // Question is a new one
// Set the unique code (not to be changed)
$question->stamp = make_unique_id_code();
$question->version = 1;
if (!$question->id=insert_record("quiz_questions", $question)) {
error("Could not insert new question!");
}
}
// Save datasets
global $CFG;
$datasetdefinitions = get_records_sql( // Indexed by name...
"SELECT a.name, a.id, a.type, a.category
FROM {$CFG->prefix}quiz_dataset_definitions a,
{$CFG->prefix}quiz_question_datasets b
WHERE a.id = b.datasetdefinition
AND b.question = $question->id");
foreach ($form->dataset as $dataset) {
if (!$dataset) {
continue; // The no dataset case...
}
list($type, $category, $name) = explode('-', $dataset, 3);
if (isset($datasetdefinitions[$name])
and $datasetdefinitions[$name]->type == $type
and $datasetdefinitions[$name]->category == $category) {
// Keep this dataset as it already fulfills our dreams
// by preventing it from being deleted
unset($datasetdefinitions[$name]);
continue;
}
// We need to create a new datasetdefinition
unset ($datasetdef);
$datasetdef->type = $type;
$datasetdef->name = $name;
$datasetdef->category = $category;
if (!$datasetdef->id = insert_record(
'quiz_dataset_definitions', $datasetdef)) {
error("Unable to create dataset $name");
}
if ($category) {
// We need to look for already existing
// datasets in the category.
// By first creating the datasetdefinition above we
// can manage to automatically take care of
// some possible realtime concurrence
while ($olderdatasetdef = get_record_select(
'quiz_dataset_definitions',
" type = '$type' AND name = '$name'
AND category = '$category'
AND id < $datasetdef->id ")) {
// Use older dataset instead:
delete_records('quiz_dataset_definitions',
'id', $datasetdef->id);
$datasetdef = $olderdatasetdef;
}
}
// Create relation to this dataset:
unset($questiondataset);
$questiondataset->question = $question->id;
$questiondataset->datasetdefinition = $datasetdef->id;
if (!insert_record('quiz_question_datasets',
$questiondataset)) {
error("Unable to create relation to dataset $name");
}
}
// Remove local obsolete datasets as well as relations
// to datasets in other categories:
if (!empty($datasetdefinitions)) {
foreach ($datasetdefinitions as $def) {
delete_records('quiz_question_datasets',
'question', $question->id,
'datasetdefinition', $def->id);
if ($def->category == 0) { // Question local dataset
delete_records('quiz_dataset_definitions', 'id', $def->id);
delete_records('quiz_dataset_items',
'definition', $def->id);
}
}
}
// Save subtype options
$this->save_question_options($question, $subtypeoptions);
return $question;
} else if (empty($form->editdatasets)) {
// Parse for common question entries and
// continue with editquestion.php by returning the question
$question->name = $form->name;
$question->questiontext = $form->questiontext;
$question->questiontextformat = $form->questiontextformat;
if (empty($form->image)) {
$question->image = "";
} else {
$question->image = $form->image;
}
if (isset($form->defaultgrade)) {
$question->defaultgrade = $form->defaultgrade;
}
return $question;
} else {
return $question;
}
}
/// Dataset functionality
function pick_question_dataset($question, $datasetnumber) {
// Select a dataset in the following format:
// An array indexed by the variable names (d.name) pointing to the value
// to be substituted
global $CFG;
if (!$dataset = get_records_sql(
"SELECT d.name, i.value
FROM {$CFG->prefix}quiz_dataset_definitions d,
{$CFG->prefix}quiz_dataset_items i,
{$CFG->prefix}quiz_question_datasets q
WHERE q.question = $question->id
AND q.datasetdefinition = d.id
AND d.id = i.definition
AND i.number = $datasetnumber")) {
error("Couldn't get the specified dataset for a dataset dependent " .
"question! (question: {$question->id}, " .
"datasetnumber: {$datasetnumber})");
}
array_walk($dataset, create_function('&$val', '$val = $val->value;'));
return $dataset;
}
function create_virtual_qtype() {
error("No vitrual question type for question type ".$this->name());
}
@@ -92,10 +349,10 @@ class quiz_dataset_dependent_questiontype extends quiz_default_questiontype {
}
function dataset_options($question, $name) {
// First options - it is not a dataset...
$options['0'] = get_string('nodataset', 'quiz');
// Construct question local options
global $CFG;
$currentdatasetdef = get_record_sql(
@@ -105,7 +362,7 @@ class quiz_dataset_dependent_questiontype extends quiz_default_questiontype {
WHERE a.id = b.datasetdefinition
AND b.question = '$question->id'
AND a.name = '$name'")
or $currentdatasetdef->type = '0';
or $currentdatasetdef->type = '0';
foreach (array( LITERAL, FILE, LINK) as $type) {
$key = "$type-0-$name";
if ($currentdatasetdef->type == $type
@@ -115,7 +372,7 @@ class quiz_dataset_dependent_questiontype extends quiz_default_questiontype {
$options[$key] = get_string("newlocal$type", 'quiz');
}
}
// Construct question category options
$categorydatasetdefs = get_records_sql(
"SELECT a.type, a.id
@@ -145,142 +402,6 @@ class quiz_dataset_dependent_questiontype extends quiz_default_questiontype {
: '');
}
function save_question_options($question, $options) {
// Default does nothing...
return true;
}
function save_question($question, $form, $course, $subtypeoptions=false) {
// For dataset dependent questions a wizard is used for editing
// questions. Therefore calls from question.php are ignored.
// Instead questions are saved when this method is called by
// editquestion.php
if ($subtypeoptions) {
// Let's save the question
// We need to save the question first (in order to get the id)
// We then save the dataset definitions and finally we
// save the subtype options...
// Save question
if (!empty($question->id)) { // Question already exists
$question->version++; // Update version number of question
if (!update_record("quiz_questions", $question)) {
error("Could not update question!");
}
} else { // Question is a new one
// Set the unique code (not to be changed)
$question->stamp = make_unique_id_code();
$question->version = 1;
if (!$question->id=insert_record("quiz_questions", $question)) {
error("Could not insert new question!");
}
}
// Save datasets
global $CFG;
$datasetdefinitions = get_records_sql( // Indexed by name...
"SELECT a.name, a.id, a.type, a.category
FROM {$CFG->prefix}quiz_dataset_definitions a,
{$CFG->prefix}quiz_question_datasets b
WHERE a.id = b.datasetdefinition
AND b.question = $question->id");
foreach ($form->dataset as $dataset) {
if (!$dataset) {
continue; // The no dataset case...
}
list($type, $category, $name) = explode('-', $dataset, 3);
if (isset($datasetdefinitions[$name])
and $datasetdefinitions[$name]->type == $type
and $datasetdefinitions[$name]->category == $category) {
// Keep this dataset as it already fulfills our dreams
// by preventing it from being deleted
unset($datasetdefinitions[$name]);
continue;
}
// We need to create a new datasetdefinition
unset ($datasetdef);
$datasetdef->type = $type;
$datasetdef->name = $name;
$datasetdef->category = $category;
if (!$datasetdef->id = insert_record(
'quiz_dataset_definitions', $datasetdef)) {
error("Unable to create dataset $name");
}
if ($category) {
// We need to look for already existing
// datasets in the category.
// By first creating the datasetdefinition above we
// can manage to automatically take care of
// some possible realtime concurrence
while ($olderdatasetdef = get_record_select(
'quiz_dataset_definitions',
" type = '$type' AND name = '$name'
AND category = '$category'
AND id < $datasetdef->id ")) {
// Use older dataset instead:
delete_records('quiz_dataset_definitions',
'id', $datasetdef->id);
$datasetdef = $olderdatasetdef;
}
}
// Create relation to this dataset:
unset($questiondataset);
$questiondataset->question = $question->id;
$questiondataset->datasetdefinition = $datasetdef->id;
if (!insert_record('quiz_question_datasets',
$questiondataset)) {
error("Unable to create relation to dataset $name");
}
}
// Remove local obsolete datasets as well as relations
// to datasets in other categories:
if (!empty($datasetdefinitions)) {
foreach ($datasetdefinitions as $def) {
delete_records('quiz_question_datasets',
'question', $question->id,
'datasetdefinition', $def->id);
if ($def->category == 0) { // Question local dataset
delete_records('quiz_dataset_definitions', 'id', $def->id);
delete_records('quiz_dataset_items',
'definition', $def->id);
}
}
}
// Save subtype options
$this->save_question_options($question, $subtypeoptions);
return $question;
} else if (empty($form->editdatasets)) {
// Parse for common question entries and
// continue with editquestion.php by returning the question
$question->name = $form->name;
$question->questiontext = $form->questiontext;
$question->questiontextformat = $form->questiontextformat;
if (empty($form->image)) {
$question->image = "";
} else {
$question->image = $form->image;
}
if (isset($form->defaultgrade)) {
$question->defaultgrade = $form->defaultgrade;
}
return $question;
} else {
return $question;
}
}
function find_dataset_names($text) {
/// Returns the possible dataset names found in the text as an array
/// The array has the dataset name for both key and value
@@ -292,114 +413,8 @@ class quiz_dataset_dependent_questiontype extends quiz_default_questiontype {
return $datasetnames;
}
function convert_to_response_answer_field($questionresponse) {
// It does not look like all platforms support the ksort strategi
// so gotta try something else...
foreach ($questionresponse as $key => $response) {
if (!isset($shortestkey)
|| strlen($shortestkey) > strlen($key)) {
$shortestkey = $key;
}
}
$dataset = $questionresponse[$shortestkey];
unset($questionresponse[$shortestkey]);
$virtualqtype = $this->get_virtual_qtype();
return "dataset$dataset-" . $virtualqtype
->convert_to_response_answer_field($questionresponse);
}
function create_response($question, $nameprefix, $questionsinuse) {
/// This method must pick a dataset and have its number and
/// data injected in the response keys
// First we retrieve the dataset definitions for this questions
// and check how many datasets we have available ($maxnumber)
global $CFG;
$datasetdefinitions = get_records_sql(
"SELECT a.* FROM {$CFG->prefix}quiz_dataset_definitions a,
{$CFG->prefix}quiz_question_datasets b
WHERE a.id = b.datasetdefinition
AND b.question = $question->id");
$definitionids = $delimiter = '';
foreach ($datasetdefinitions as $datasetdef) {
$definitionids .= $delimiter.$datasetdef->id;
$delimiter = ',';
if (!isset($maxnumber) || $datasetdef->itemcount < $maxnumber) {
$maxnumber = $datasetdef->itemcount;
}
}
// We then pick dataset number and retrieve the datasetitems
if (!isset($maxnumber) || 0 == $maxnumber) {
notify("Error: Question $question->id does not
have items for its datasets");
$datasetinput = 0;
$datasetitems = array();
} else {
isset($this->datasetnumbers[$question->category])
and $this->datasetnumbers[$question->category] <= $maxnumber
or $this->datasetnumbers[$question->category] =
quiz_qtype_dataset_pick_new($question->category,
$maxnumber);
$datasetinput = $this->datasetnumbers[$question->category];
$datasetitems = get_records_select('quiz_dataset_items',
"definition in ($definitionids)
AND number = $datasetinput");
}
// Build the rest of $datasetinput
foreach ($datasetitems as $item) {
$datasetdef = $datasetdefinitions[$item->definition];
// We here need to pay attention to whether the
// data item is a link or an ordinary literal
if ($datasetdef->type == LITERAL) {
// The ordinary simple case
$value = $item->value;
} else {
$icon = "<img src=\"$CFG->wwwroot/pix/f/"
. mimeinfo('icon', $item->value)
. '" height="16" width="16" border="0" alt="File" />';
if (substr(strtolower($item->value), 0, 7)=='http://') {
$link = $item->value;
} else {
global $quiz; // Try to reach this info globally
if ($CFG->slasharguments) {
// Use this method if possible for better caching
$link = "quizfile.php/$quiz->id/$question->id/$item->value";
} else {
$link = "quizfile.php?file=/$quiz->id/$question->id/$item->value";
}
}
if ($datasetdef->type == FILE
and ereg('/([^/]+)$', $item->value, $regs)) {
$linktext = $regs[1];
} else {
$linktext = $item->value;
}
$value = '<a target="_blank" href="' . $link
. "\" title=\"$datasetdef->name\">$icon$linktext</a>";
}
$datasetinput .= ';' . base64_encode($datasetdef->name)
. ':' . base64_encode($value);
}
// Use the virtual question type and have it ->create_response:
$virtualqtype = $this->get_virtual_qtype();
$response = $virtualqtype->create_response($question,
$this->create_virtual_nameprefix($nameprefix, $datasetinput),
$questionsinuse);
$response[$nameprefix] = $datasetinput;
return $response;
}
/// The following functions are needed for database upgrade script only
// This is used by extract_responses, so it needs to stay
function create_virtual_nameprefix($nameprefix, $datasetinput) {
// This default implementation is sometimes overridden
if (!ereg('([0-9]+)' . $this->name() . '$', $nameprefix, $regs)) {
@@ -414,147 +429,16 @@ class quiz_dataset_dependent_questiontype extends quiz_default_questiontype {
$rawresponse->answer, $regs)) {
error ("Wrongly formatted raw response answer $rawresponse->answer");
}
// Truncate raw response to fit the virtual qtype
$rawresponse->answer = $regs[2];
$virtualqtype = $this->get_virtual_qtype();
$response = $virtualqtype->extract_response($rawresponse,
$this->create_virtual_nameprefix($nameprefix, $regs[1]));
$response[$nameprefix] = $regs[1];
return $response;
}
function print_question_formulation_and_controls($question,
$quiz, $readonly, $answers, $correctanswers, $nameprefix) {
// Replace wild-cards with dataset items
$datasetinput = $question->response[$nameprefix];
list($datasetnumber, $data) =
$this->parse_datasetinput($datasetinput);
foreach ($data as $name => $value) {
$question->questiontext = str_replace
('{'.$name.'}', $value, $question->questiontext);
}
// Print hidden field with dataset info
echo '<input type="hidden" name="' . $nameprefix
. '" value="' . $datasetinput . '" />';
// Forward to the virtual qtype
unset($question->response[$nameprefix]);
$virtualqtype = $this->get_virtual_qtype();
$virtualqtype->print_question_formulation_and_controls(
$question, $quiz, $readonly, $answers, $correctanswers,
$this->create_virtual_nameprefix($nameprefix, $datasetinput));
}
function parse_datasetinput($datasetinput) {
/// Returns an array consisting of three pieces of information
/// In [0] there is the dataset number
/// In [1] there is an array where the data items are mapped by name
/// The dataset related part of the response key
/// follows right after the response key and end with :
/// In order to avoid any conflict that can occur whenever anyone
/// wish to use : in the data, the data dependent part
/// has been converted to base64 in two steps
$rawdata = split('[:;]', $datasetinput);
$rawlength = count($rawdata);
$i = 0;
$data = array();
while (++$i < $rawlength) {
$data[base64_decode($rawdata[$i])] =
base64_decode($rawdata[++$i]);
}
return array($rawdata[0], $data);
}
}
//// END OF CLASS ////
function quiz_qtype_dataset_pick_new($category, $maxnumber) {
//// Function used by ->create_response
//// It takes care of picking a new datasetnumber for
//// the user in the specified question category
// We need to know whether the attempt builds on the last or
// not. It can not be determined by the function args to
// ->create_response.
// Instead of adding that argument to all implementations of
// create_response we try to reach $quiz globally. That
// should work because this function is only used by
// attempt.php when starting an attempt.
global $quiz; //// PATTERN VIOLATION ////
if ($quiz->attemptonlast) {
// Dataset numbers for attemptonlast quizes are stored
// in quiz_attemptonlast_datasets
global $USER;
if (!($attemptonlastdataset = get_record(
'quiz_attemptonlast_datasets',
'userid', $USER->id, 'category', $category))
or $attemptonlastdataset->datasetnumber > $maxnumber) {
// No suitable $attemptonlastdataset
if ($attemptonlastdataset) {
// Remove the unsuitable:
delete_records('quiz_attemptonlast_datasets',
'id', $attemptonlastdataset->id);
unset($attemptonlastdataset->id);
unset($attemptonlastdataset->datasetnumber);
} else {
$attemptonlastdataset->userid = $USER->id;
$attemptonlastdataset->category = $category;
}
// Create without setting datasetnumber
// so that this user gets its id
$attemptonlastdataset->id = insert_record(
'quiz_attemptonlast_datasets', $attemptonlastdataset);
// Pick the datasetnumber in a thread safe way
// so that this can be done without having
// concurrent users get the same datasetnumber!
// The chosen pattern relies on
// synchronization for autoincrement on the id
// when the previous insert_record statement
// was executed.
if (!($latestdatasetpick = get_record_select(
'quiz_attemptonlast_datasets',
"category = $category AND datasetnumber > 0
AND id < $attemptonlastdataset->id",
' max(id) id '))) {
// Smells like the current user is first:
$latestdatasetpick->id = 0;
}
$latestattemptonlasts = get_records_select(
'quiz_attemptonlast_datasets',
"$latestdatasetpick->id <= id
AND id < $attemptonlastdataset->id
AND category = $category");
$attemptonlastdataset->datasetnumber =
(count($latestattemptonlasts)
+ (isset($latestattemptonlasts[$latestdatasetpick->id])
? $latestattemptonlasts[$latestdatasetpick->id]
->datasetnumber - 1
: 0))
% $maxnumber + 1;
if (!update_record('quiz_attemptonlast_datasets',
$attemptonlastdataset)) {
notify("Error unable to save the picked datasetnumber in
quiz_attemptonlast_datasets for user $USER-id");
}
}
return $attemptonlastdataset->datasetnumber;
} else {
// When it is not an attemptonlast
// we pick the dataset number randomly
return rand ( 1 , $maxnumber );
}
}
?>
@@ -17,27 +17,23 @@ class quiz_description_qtype extends quiz_default_questiontype {
return 'description';
}
function get_question_options(&$question) {
// No options to be restored for this question type
return true;
}
function save_question_options($question) {
/// No options to be saved for this question type:
return true;
}
function create_response($question, $nameprefix, $questionsinuse) {
/// This question type does never have any responses,
/// so do not return any...
return array();
}
function print_question($currentnumber, $quiz, $question,
$readonly, $resultdetails) {
echo '<p align="center">';
function print_question(&$question, &$state, &$number, $quiz, $options) {
print_simple_box_start('center', '90%');
echo format_text($question->questiontext,
$question->questiontextformat,
NULL, $quiz->course);
quiz_print_possible_question_image($quiz->id, $question);
echo '</p>';
return $currentnumber;
print_simple_box_end('center', '90%');
}
function actual_number_of_questions($question) {
@@ -49,12 +45,11 @@ class quiz_description_qtype extends quiz_default_questiontype {
return 0;
}
function grade_response($question, $nameprefix) {
$result->grade = 0.0;
$result->answers = array();
$result->correctanswers = array();
return $result;
function grade_responses(&$question, &$state, $quiz) {
$state->raw_grade = 0;
$state->penalty = 0;
}
}
//// END OF CLASS ////
+151 -99
View File
@@ -11,6 +11,10 @@ class quiz_match_qtype extends quiz_default_questiontype {
return 'match';
}
function get_question_options(&$question) {
return true;
}
function save_question_options($question) {
if (!$oldsubquestions = get_records("quiz_match_sub", "question", $question->id, "id ASC")) {
@@ -81,21 +85,104 @@ class quiz_match_qtype extends quiz_default_questiontype {
return true;
}
function convert_to_response_answer_field($questionresponse) {
/// This method, together with extract_response, should be
/// obsolete as soon as we get a better response storage
function create_session_and_responses(&$question, &$state, $quiz, $attempt) {
if (!$state->options->subquestions = get_records('quiz_match_sub',
'question', $question->id)) {
notify('Error: Missing subquestions!');
return false;
}
$delimiter = '';
$responseanswerfield = '';
foreach ($questionresponse as $key => $value) {
if ($matchid = $this->extract_response_id($key)) {
$responseanswerfield .= "$delimiter$matchid-$value";
$delimiter = ',';
} else {
notify("Error: Illegal match key $key detected");
foreach ($state->options->subquestions as $key => $subquestion) {
// This seems rather over complicated, but it is useful for the
// randomsamatch questiontype, which can then inherit the print
// and grading functions. This way it is possible to define multiple
// answers per question, each with different marks and feedback.
$answer = new stdClass();
$answer->id = $subquestion->id;
$answer->answer = $subquestion->answertext;
$answer->fraction = 1.0;
$state->options->subquestions[$key]->options
->answers[$subquestion->id] = clone($answer);
}
// Shuffle the answers if required
$subquestionids = array_values(array_map(create_function('$val',
'return $val->id;'), $state->options->subquestions));
if ($quiz->shuffleanswers) {
$subquestionids = swapshuffle($subquestionids);
}
$state->options->order = $subquestionids;
// Create empty responses
foreach ($subquestionids as $val) {
$state->responses[$val] = '';
}
return true;
}
function restore_session_and_responses(&$question, &$state) {
// The serialized format for matching questions is a comma separated
// list of question answer pairs (e.g. 1-1,2-3,3-2), where the ids of
// both refer to the id in the table quiz_match_sub.
$responses = explode(',', $state->responses['']);
$responses = array_map(create_function('$val',
'return explode("-", $val);'), $responses);
// Restore the previous responses
$state->responses = array();
foreach ($responses as $response) {
$state->responses[$response[0]] = $response[1];
}
if (!$state->options->subquestions = get_records('quiz_match_sub',
'question', $question->id)) {
notify('Error: Missing subquestions!');
return false;
}
foreach ($state->options->subquestions as $key => $subquestion) {
// This seems rather over complicated, but it is useful for the
// randomsamatch questiontype, which can then inherit the print
// and grading functions. This way it is possible to define multiple
// answers per question, each with different marks and feedback.
$answer = new stdClass();
$answer->id = $subquestion->id;
$answer->answer = $subquestion->answertext;
$answer->fraction = 1.0;
$state->options->subquestions[$key]->options
->answers[$subquestion->id] = clone($answer);
}
return true;
}
function save_session_and_responses(&$question, &$state) {
// Serialize responses
$responses = array();
foreach ($state->responses as $key => $val) {
if ($key != '') {
$responses[] = "$key-$val";
}
}
return $responseanswerfield;
$responses = implode(',', $responses);
// Set the legacy answer field
if (!set_field('quiz_states', 'answer', $responses, 'id',
$state->id)) {
return false;
}
return true;
}
function get_correct_responses(&$question, &$state) {
$responses = array();
foreach ($state->options->subquestions as $sub) {
foreach ($sub->options->answers as $answer) {
if (1 == $answer->fraction) {
$responses[$sub->id] = $answer->id;
}
}
}
return empty($responses) ? null : $responses;
}
function extract_response($rawresponse, $nameprefix) {
@@ -114,8 +201,19 @@ class quiz_match_qtype extends quiz_default_questiontype {
return $response;
}
function print_question_formulation_and_controls($question,
$quiz, $readonly, $answers, $correctanswers, $nameprefix) {
function print_question_formulation_and_controls(&$question, &$state, $quiz, $options) {
$subquestions = $state->options->subquestions;
$correctanswers = $this->get_correct_responses($question, $state);
$nameprefix = $question->name_prefix;
$answers = array();
foreach ($subquestions as $subquestion) {
foreach ($subquestion->options->answers as $sub) {
$answers[$sub->id] = $sub->answer;
}
}
// Shuffle the answers
$answers = draw_rand_array($answers, count($answers));
// Print question text and possible image
if (!empty($question->questiontext)) {
@@ -125,37 +223,7 @@ class quiz_match_qtype extends quiz_default_questiontype {
}
quiz_print_possible_question_image($quiz->id, $question);
// It so happens to be that $correctanswers for this question type also
// contains the subqustions, which we need to make sure we have:
if (empty($correctanswers)) {
$options = get_record('quiz_match', 'question', $question->id)
and $subquestions = get_records_list('quiz_match_sub', 'id',
$options->subquestions);
} else {
$subquestions = $correctanswers;
}
/// Check whether everything turned out alright:
if (empty($subquestions)) {
notify("Error: Missing subquestions for this question!");
} else {
/// Everything is fine -
/// Set up $subquestions and $answers and do the shuffling:
if ($quiz->shuffleanswers) {
$subquestions = draw_rand_array($subquestions,
count($subquestions));
}
foreach ($subquestions as $key => $subquestion) {
unset($answers[$key]);
$answers[$subquestion->id] = $subquestion->answertext;
}
$answers = draw_rand_array($answers, count($answers));
}
///// Print the input controls //////
echo '<table border="0" cellpadding="10" align="right">';
foreach ($subquestions as $subquestion) {
@@ -167,76 +235,60 @@ class quiz_match_qtype extends quiz_default_questiontype {
/// Drop-down list:
$menuname = $nameprefix.$subquestion->id;
$response = isset($question->response[$menuname])
? $question->response[$menuname] : '0';
if ($readonly
and $quiz->correctanswers
and isset($correctanswers[$menuname])
and ($correctanswers[$menuname]->id == $response)) {
$response = isset($state->responses[$subquestion->id])
? $state->responses[$subquestion->id] : '0';
if ($options->readonly
and $options->correct_responses
and isset($correctanswers[$subquestion->id])
and ($correctanswers[$subquestion->id] == $response)) {
$class = ' class="highlight" ';
} else {
$class = '';
}
echo "<td align=\"right\" valign=\"top\" $class>";
choose_from_menu($answers, $menuname, $response);
if ($quiz->feedback && isset($answers[$menuname])
&& $answers[$menuname]->feedback) {
quiz_print_comment($answers[$menuname]->feedback);
choose_from_menu($answers, $menuname, $response, 'choose', '', 0,
false, $options->readonly);
/* There is currently no feedback for this questiontype.
if ($quiz->feedback && isset($subquestion->feedback)) {
quiz_print_comment($subquestion->feedback);
}
*/
echo '</td></tr>';
}
echo '</table>';
}
function grade_response($question, $nameprefix) {
/// This question type does not use the table quiz_answers
/// but we will take some measures to emulate that record anyway.
function grade_responses(&$question, &$state, $quiz) {
$subquestions = $state->options->subquestions;
$responses = &$state->responses;
$result->grade = 0.0;
$result->answers = array();
$result->correctanswers = array();
if (!($options = get_record('quiz_match', 'question', $question->id)
and $subquestions = get_records_list('quiz_match_sub',
'id', $options->subquestions))) {
notify("Error: Cannot find match options and subquestions
for question $question->id");
return $result;
}
$fraction = 1.0 / count($subquestions);
/// Populate correctanswers arrays:
$answers = array();
foreach ($subquestions as $subquestion) {
$subquestion->fraction = $fraction;
$subquestion->answer = $subquestion->answertext;
$subquestion->feedback = '';
$result->correctanswers[$nameprefix.$subquestion->id] =
$subquestion;
}
foreach ($question->response as $responsekey => $answerid) {
if ($answerid and $answer =
$result->correctanswers[$nameprefix.$answerid]) {
if ($result->correctanswers[$responsekey]->answer
== $answer->answer) {
/// The response was correct!
$result->answers[$responsekey] =
$result->correctanswers[$responsekey];
$result->grade += $fraction;
} else {
/// The response was incorrect:
$answer->fraction = 0.0;
$result->answers[$responsekey] = $answer;
}
foreach ($subquestion->options->answers as $sub) {
$answers[$sub->id] = $sub->answer;
}
}
return $result;
$sumgrade = 0;
foreach ($subquestions as $key => $sub) {
if (isset($sub->options->answers[$responses[$key]])) {
$sumgrade += $sub->options->answers[$responses[$key]]->fraction;
}
}
$state->raw_grade = $sumgrade/count($subquestions);
if (empty($state->raw_grade)) {
$state->raw_grade = 0;
}
// Make sure we don't assign negative or too high marks
$state->raw_grade = min(max((float) $state->raw_grade,
0.0), 1.0) * $question->maxgrade;
$state->penalty = 0;
// Only allow one attempt at the question
$state->event = QUIZ_EVENTCLOSE;
return true;
}
}
//// END OF CLASS ////
@@ -1,41 +1,54 @@
<?php // $Id$
if ($question->questiontext and $question->id) {
$answers = quiz_get_answers($question);
if ($question->questiontext and $question->id and $question->qtype) {
global $QUIZ_QTYPES;
$QUIZ_QTYPES[$question->qtype]->get_question_options($question);
foreach ($answers as $multianswer) {
$parsableanswerdef = '{' . $multianswer->norm . ':';
switch ($multianswer->answertype) {
case MULTICHOICE:
$parsableanswerdef .= 'MULTICHOICE:';
break;
case SHORTANSWER:
$parsableanswerdef .= 'SHORTANSWER:';
break;
case NUMERICAL:
$parsableanswerdef .= 'NUMERICAL:';
break;
default:
error("answertype $multianswer->answertype not recognized");
}
$separator= '';
foreach ($multianswer->subanswers as $subanswer) {
$parsableanswerdef .= $separator
. '%' . round(100*$subanswer->fraction) . '%';
$parsableanswerdef .= $subanswer->answer;
if (isset($subanswer->min) && isset($subanswer->max)
and $subanswer->min || $subanswer->max) {
// Special for numerical answers:
$errormargin = $subanswer->answer - $subanswer->min;
$parsableanswerdef .= ":$errormargin";
foreach ($question->options->questions as $key => $wrapped) {
// The old way of restoring the definitions is kept to gradually
// update all multianswer questions
if (empty($wrapped->questiontext)) {
$parsableanswerdef = '{' . $wrapped->defaultgrade . ':';
switch ($wrapped->qtype) {
case MULTICHOICE:
$parsableanswerdef .= 'MULTICHOICE:';
break;
case SHORTANSWER:
$parsableanswerdef .= 'SHORTANSWER:';
break;
case NUMERICAL:
$parsableanswerdef .= 'NUMERICAL:';
break;
default:
error("questiontype $wrapped->qtype not recognized");
}
if ($subanswer->feedback) {
$parsableanswerdef .= "#$subanswer->feedback";
$separator= '';
foreach ($wrapped->options->answers as $subanswer) {
$parsableanswerdef .= $separator
. '%' . round(100*$subanswer->fraction) . '%';
$parsableanswerdef .= $subanswer->answer;
if (!empty($wrapped->options->tolerance)) {
// Special for numerical answers:
$parsableanswerdef .= ":{$wrapped->options->tolerance}";
// We only want tolerance for the first alternative, it will
// be applied to all of the alternatives.
unset($wrapped->options->tolerance);
}
if ($subanswer->feedback) {
$parsableanswerdef .= "#$subanswer->feedback";
}
$separator = '~';
}
$separator = '~';
$parsableanswerdef .= '}';
// Fix the questiontext fields of old questions
set_field('quiz_questions', 'questiontext', $parsableanswerdef,
'id', $wrapped->id);
} else {
$parsableanswerdef = $wrapped->questiontext;
}
$parsableanswerdef .= '}';
//echo "{#$key}<br />";
//echo $parsableanswerdef.'<br />';
$question->questiontext = str_replace
("{#$multianswer->positionkey}", $parsableanswerdef,
("{#$key}", $parsableanswerdef,
$question->questiontext);
}
}
@@ -15,99 +15,81 @@
/// QUESTION TYPE CLASS //////////////////
class quiz_embedded_cloze_qtype extends quiz_default_questiontype {
function get_answers($question) {
// This function is not used by any other function within this class.
// It is possible that it is used by some report that uses the
// function quiz_get_answers in lib.php
/// The returned answers includes subanswers...
// As this question type embedds some other question types,
// it is necessary to have access to those:
global $QUIZ_QTYPES;
$answers = array();
$virtualquestion->id = $question->id;
if ($multianswers = get_records('quiz_multianswers', 'question', $question->id)) {
foreach ($multianswers as $multianswer) {
$virtualquestion->qtype = $multianswer->answertype;
// Call to other question type for subanswers
$addedcondition = " AND a.id IN ($multianswer->answers) ";
$multianswer->subanswers =
$QUIZ_QTYPES[$multianswer->answertype]
->get_answers($virtualquestion, $addedcondition);
$answers[] = $multianswer;
}
}
return $answers;
}
function get_position_multianswer($questionid, $positionkey) {
// As a separate function in order to make it overridable
return get_record('quiz_multianswers', 'question', $questionid,
'positionkey', $positionkey);
}
function get_multianswers($questionid) {
// As a separate function in order to make it overridable
return get_records('quiz_multianswers', 'question', $questionid);
}
function name() {
return 'multianswer';
}
function get_question_options(&$question) {
// Get relevant data indexed by positionkey from the multianswers table
if (!$sequence = get_field('quiz_multianswers', 'sequence', 'question',
$question->id)) {
notify('Error: Missing question options!');
return false;
}
global $QUIZ_QTYPES;
// Temporary solution to be replaced with commented out version after
// redundant wrapped questions are deleted when necessary.
// $wrappedquestions = get_records('quiz_questions', 'parent', $question->id);
$wrappedquestions = get_records_list('quiz_questions', 'id', $sequence);
// We want an array with question ids as index and the positions as values
$sequence = array_flip(explode(',', $sequence));
array_walk($sequence, create_function('&$val', '$val++;'));
foreach ($wrappedquestions as $wrapped) {
if (!$QUIZ_QTYPES[$wrapped->qtype]
->get_question_options($wrapped)) {
error("Unable to recognized questiontype " .
$wrapped->qtype);
}
$wrapped->maxgrade = $wrapped->defaultgrade;
$question->options->questions[$sequence[$wrapped->id]] = clone($wrapped);
}
return true;
}
function save_question_options($question) {
if (!$oldmultianswers = get_records("quiz_multianswers", "question", $question->id, "id ASC")) {
$oldmultianswers = array();
global $QUIZ_QTYPES;
if (!$oldwrappedids =
get_records('quiz_questions', 'parent', $question->id, '', 'id, id')) {
// We need to select 'id, id' because the first one is consumed by
// get_records.
$oldwrappedids = array();
}
if (empty($question->name)) {
$result->notice = get_string("missingname", "quiz");
return $result;
$oldwrappedids = array_keys($oldwrappedids);
$sequence = array();
foreach($question->options->questions as $wrapped) {
if ($oldwrappedid = array_shift($oldwrappedids)) {
$wrapped->id = $oldwrappedid;
}
$wrapped->name = $question->name;
$wrapped->parent = $question->id;
$wrapped->category = $question->category;
$wrapped->version = $question->version;
$wrapped = $QUIZ_QTYPES[$wrapped->qtype]->save_question($wrapped,
$wrapped, $question->course);
$sequence[] = $wrapped->id;
}
if (!$question->answers) {
$result->notice = get_string('noquestionintext', 'quiz');
return $result;
}
// Insert all the new multi answers
foreach ($question->answers as $dataanswer) {
if ($oldmultianswer = array_shift($oldmultianswers)) { // Existing answer, so reuse it
$multianswer = $oldmultianswer;
$multianswer->positionkey = $dataanswer->positionkey;
$multianswer->norm = $dataanswer->norm;
$multianswer->answertype = $dataanswer->answertype;
if (! $multianswer->answers =
quiz_qtype_multianswer_save_alternatives
($question->id, $dataanswer->answertype,
$dataanswer->alternatives, $oldmultianswer->answers))
{
$result->error = "Could not update multianswer alternatives! (id=$multianswer->id)";
return $result;
}
// Delete redundant wrapped questions
$oldwrappedids = implode(',', $oldwrappedids);
delete_records_select('quiz_questions', "id IN ($oldwrappedids)");
if (!empty($sequence)) {
$multianswer = new stdClass;
$multianswer->question = $question->id;
$multianswer->sequence = implode(',', $sequence);
if ($oldid =
get_field('quiz_multianswers', 'id', 'question', $question->id)) {
$multianswer->id = $oldid;
if (!update_record("quiz_multianswers", $multianswer)) {
$result->error = "Could not update quiz multianswer! (id=$multianswer->id)";
return $result;
}
} else { // This is a completely new answer
unset($multianswer);
$multianswer->question = $question->id;
$multianswer->positionkey = $dataanswer->positionkey;
$multianswer->norm = $dataanswer->norm;
$multianswer->answertype = $dataanswer->answertype;
if (! $multianswer->answers =
quiz_qtype_multianswer_save_alternatives
($question->id, $dataanswer->answertype,
$dataanswer->alternatives))
{
$result->error = "Could not insert multianswer alternatives! (questionid=$question->id)";
$result->error = "Could not update quiz multianswer! " .
"(id=$multianswer->id)";
return $result;
}
} else {
if (!insert_record("quiz_multianswers", $multianswer)) {
$result->error = "Could not insert quiz multianswer!";
return $result;
@@ -115,75 +97,66 @@ class quiz_embedded_cloze_qtype extends quiz_default_questiontype {
}
}
}
function save_question($authorizedquestion, $form, $course) {
$question = quiz_qtype_multianswer_extract_question
($form->questiontext);
function save_question($authorizedquestion, $form, $course) {
$question =
quiz_qtype_multianswer_extract_question ($form->questiontext);
if (isset($authorizedquestion->id)) {
$question->id = $authorizedquestion->id;
}
$question->qtype = $authorizedquestion->qtype;
$question->category = $authorizedquestion->category;
$question->name = $form->name;
if (empty($form->image)) {
$question->image = "";
$question->version = $form->version = $authorizedquestion->version;
} else {
$question->image = $form->image;
$question->version = $form->version = 1;
}
if (!empty($question->id)) { // Question already exists
if (!update_record("quiz_questions", $question)) {
error("Could not update question!");
}
} else { // Question is a new one
$question->stamp = make_unique_id_code(); // Set the unique code (not to be changed)
if (!$question->id = insert_record("quiz_questions", $question)) {
error("Could not insert new question!");
}
}
// Now to save all the answers and type-specific options
$form->id = $question->id;
$form->qtype = $question->qtype;
$form->category = $question->category;
$result = $this->save_question_options($question);
if (!empty($result->error)) {
error($result->error);
}
if (!empty($result->notice)) {
notice($result->notice, "question.php?id=$question->id");
}
if (!empty($result->noticeyesno)) {
notice_yesno($result->noticeyesno, "question.php?id=$question->id", "edit.php");
print_footer($course);
exit;
}
return $question;
$question->category = $authorizedquestion->category;
$form->course = $course; // To pass the course object to
// save_question_options, where it is
// needed to call type specific
// save_question methods.
$form->defaultgrade = $question->defaultgrade;
$form->questiontext = $question->questiontext;
$form->questiontextformat = 0;
$form->options = clone($question->options);
unset($question->options);
return parent::save_question($question, $form, $course);
}
function convert_to_response_answer_field($questionresponse) {
/// This method, together with extract_response, should be
/// obsolete as soon as we get a better response storage
$delimiter = '';
$responseanswerfield = '';
foreach ($questionresponse as $key => $value) {
if ($multianswerid = $this->extract_response_id($key)) {
$responseanswerfield .= "$delimiter$multianswerid-$value";
$delimiter = ',';
} else {
notify("Error: Illegal match key $key detected");
}
function create_session_and_responses(&$question, &$state, $quiz, $attempt) {
$state->responses = array();
foreach ($question->options->questions as $key => $wrapped) {
$state->responses[$key] = '';
}
return $responseanswerfield;
return true;
}
function restore_session_and_responses(&$question, &$state) {
$responses = explode(',', $state->responses['']);
$state->responses = array();
foreach ($responses as $response) {
$tmp = explode("-", $response);
// restore encoded characters
$state->responses[$tmp[0]] =
str_replace(array("&#0044;", "&#0045;"), array(",", "-"), $tmp[1]);
}
return true;
}
function save_session_and_responses(&$question, &$state) {
$responses = $state->responses;
array_walk($responses, create_function('&$val, $key',
// encode - (hyphen) and , (comma) to &#0045; because they are used as
// delimiters
'$val = str_replace(array(",", "-"), array("&#0044;", "&#0045;"), $val);
$val = "$key-$val";'));
$responses = implode(',', $responses);
// Set the legacy answer field
if (!set_field('quiz_states', 'answer', $responses, 'id',
$state->id)) {
return false;
}
return true;
}
function extract_response($rawresponse, $nameprefix) {
@@ -204,8 +177,22 @@ class quiz_embedded_cloze_qtype extends quiz_default_questiontype {
return $response;
}
function print_question_formulation_and_controls($question,
$quiz, $readonly, $answers, $correctanswers, $nameprefix) {
function get_correct_responses(&$question, &$state) {
global $QUIZ_QTYPES;
$responses = array();
foreach($question->options->questions as $key => $wrapped) {
$correct = $QUIZ_QTYPES[$wrapped->qtype]
->get_correct_responses($wrapped, $state);
$responses[$key] = $correct[''];
}
return $responses;
}
function print_question_formulation_and_controls(&$question, &$state, $quiz,
$options) {
global $QUIZ_QTYPES;
$readonly = empty($options->readonly) ? '' : 'disabled="disabled"';
$nameprefix = $question->name_prefix;
// For this question type, we better print the image on top:
quiz_print_possible_question_image($quiz->id, $question);
@@ -224,45 +211,77 @@ class quiz_embedded_cloze_qtype extends quiz_default_questiontype {
echo $qtextsplits[0];
$qtextremaining = $qtextsplits[1];
$multianswer = $this->get_position_multianswer($question->id, $regs[1]);
$positionkey = $regs[1];
$wrapped = &$question->options->questions[$positionkey];
$answers = &$wrapped->options->answers;
$correctanswers = $QUIZ_QTYPES[$wrapped->qtype]
->get_correct_responses($wrapped, $state);
$inputname = $nameprefix.$multianswer->id;
$response = isset($question->response[$inputname])
? $question->response[$inputname] : '';
$inputname = $nameprefix.$positionkey;
$response = isset($state->responses[$positionkey])
? $state->responses[$positionkey] : null;
/// Determine style
if (!empty($correctanswers) && '' !== $response) {
if (!isset($answers[$inputname])
|| $answers[$inputname]->fraction <= 0.0) {
// The response must have been totally wrong:
$style = ' style="background-color:red" ';
} else if ($answers[$inputname]->fraction >= 1.0) {
// The response must was correct!!
$style = 'style="background-color:lime"';
} else {
// This response did at least give some credit:
$style = 'style="background-color:yellow"';
}
} else {
// No colorish feedback is to be used
$style = '';
}
// Determine feedback popup if any
if ($quiz->feedback && isset($answers[$inputname])
&& '' !== $answers[$inputname]->feedback) {
$title = str_replace("'", "\\'", $answers[$inputname]->feedback);
$popup = " onmouseover=\"return overlib('$title', CAPTION, '$strfeedback', FGCOLOR);\" ".
" onmouseout=\"return nd();\" ";
} else {
$popup = '';
$popup = '';
$style = '';
if ($options->feedback) {
$chosenanswer = null;
switch ($wrapped->qtype) {
case NUMERICAL:
case SHORTANSWER:
$testedstate = clone($state);
$testedstate->responses[''] = $response;
$teststate = clone($state);
$raw_grade = 0;
foreach ($answers as $answer) {
$teststate->responses[''] = trim($answer->answer);
if($QUIZ_QTYPES[$wrapped->qtype]
->compare_responses($wrapped, $testedstate, $teststate)) {
if (empty($raw_grade) || $raw_grade < $answer->fraction) {
//var_dump($teststate->responses['']);
//var_dump($testedstate->responses['']);
$chosenanswer = clone($answer);
}
}
}
break;
case MULTICHOICE:
if (isset($answers[$response])) {
$chosenanswer = clone($answers[$response]);
}
break;
default:
break;
}
if (!empty($chosenanswer->feedback)) {
$feedback = str_replace("'", "\\'", $chosenanswer->feedback);
$popup = " onmouseover=\"return overlib('$feedback', CAPTION, '$strfeedback', FGCOLOR);\" ".
" onmouseout=\"return nd();\" ";
}
/// Determine style
if (!empty($chosenanswer)) {
if (!isset($chosenanswer->fraction)
|| $chosenanswer->fraction <= 0.0) {
// The response must have been totally wrong:
$style = ' style="background-color:red" ';
} else if ($chosenanswer->fraction >= 1.0) {
// The response was correct!!
$style = 'style="background-color:lime"';
} else {
// This response did at least give some credit:
$style = 'style="background-color:yellow"';
}
}
}
// Print the input control
switch ($multianswer->answertype) {
switch ($wrapped->qtype) {
case SHORTANSWER:
case NUMERICAL:
echo " <input $style $readonly $popup name=\"$inputname\"
@@ -270,13 +289,12 @@ class quiz_embedded_cloze_qtype extends quiz_default_questiontype {
break;
case MULTICHOICE:
$outputoptions = '<option></option>'; // Default empty option
$mcanswers = get_records_list("quiz_answers", "id", $multianswer->answers);
foreach ($mcanswers as $mcanswer) {
foreach ($answers as $mcanswer) {
$selected = $response == $mcanswer->id
? ' selected="selected" ' : '';
$outputoptions .= "<option value=\"$mcanswer->id\" $selected>$mcanswer->answer</option>";
}
echo "<select $popup $style name=\"$inputname\">";
echo "<select $popup $readonly $style name=\"$inputname\">";
echo $outputoptions;
echo '</select>';
break;
@@ -290,56 +308,30 @@ class quiz_embedded_cloze_qtype extends quiz_default_questiontype {
echo $qtextremaining;
}
function grade_response($question, $nameprefix) {
function grade_responses(&$question, &$state, $quiz) {
global $QUIZ_QTYPES;
$result->grade = 0.0;
$result->answers = array();
$result->correctanswers = array();
$multianswers = $this->get_multianswers($question->id);
// Default settings:
$subquestion->id = $question->id;
$normsum = 0;
// Grade each multianswer
foreach ($multianswers as $multianswer) {
$name = $nameprefix.$multianswer->id;
$subquestion->response[$nameprefix] =
isset($question->response[$name])
? $question->response[$name] : '';
$subresult = $QUIZ_QTYPES[$multianswer->answertype]
->grade_response($subquestion, $nameprefix,
" AND a.id IN ($multianswer->answers) ");
// Summarize subquestion results:
if (isset($subresult->answers[$nameprefix])) {
/// Answer was found:
$result->answers[$name] = $subresult->answers[$nameprefix];
if ($result->answers[$name]->fraction >= 1.0) {
// This is also the correct answer:
$result->correctanswers[$name] = $result->answers[$name];
}
$teststate = clone($state);
$state->raw_grade = 0;
foreach($question->options->questions as $key => $wrapped) {
$teststate->responses = array('' => $state->responses[$key]);
$teststate->raw_grade = 0;
if (false === $QUIZ_QTYPES[$wrapped->qtype]
->grade_responses($wrapped, $teststate, $quiz)) {
return false;
}
if (!isset($result->correctanswers[$name])) {
// Pick the first correctanswer:
foreach ($subresult->correctanswers as $correctanswer) {
$result->correctanswers[$name] = $correctanswer;
break;
}
}
$result->grade += $multianswer->norm * $subresult->grade;
$normsum += $multianswer->norm;
$state->raw_grade += $teststate->raw_grade;
}
$result->grade /= $normsum;
$state->raw_grade /= $question->defaultgrade;
$state->raw_grade = min(max((float) $state->raw_grade, 0.0), 1.0)
* $question->maxgrade;
return $result;
if (empty($state->raw_grade)) {
$state->raw_grade = 0.0;
}
$state->penalty = 0;
// Only allow one attempt at the question
$state->event = QUIZ_EVENTCLOSE;
return true;
}
}
//// END OF CLASS ////
@@ -360,6 +352,7 @@ $QUIZ_QTYPES[MULTIANSWER]= new quiz_embedded_cloze_qtype();
//// likely to be subject for overriding.
/////////////////////////////////////////////////////////////
function quiz_qtype_multianswer_extract_question($text) {
////////////////////////////////////////////////
@@ -428,216 +421,77 @@ function quiz_qtype_multianswer_extract_question($text) {
//// Start of the actual function
////////////////////////////////////////
$question = NULL;
$question = new stdClass;
$question->qtype= MULTIANSWER;
$question->questiontext= $text;
$question->answers= array();
$question->options->questions = array();
$question->defaultgrade = 0; // Will be increased for each answer norm
for ($positionkey=1
; ereg(ANSWER_REGEX, $question->questiontext, $answerregs)
; ++$positionkey )
{
unset($multianswer);
$multianswer->positionkey = $positionkey;
$multianswer->norm = $answerregs[ANSWER_REGEX_NORM]
or $multianswer->norm = '1';
; ++$positionkey ) {
$wrapped = new stdClass;
$wrapped->defaultgrade = $answerregs[ANSWER_REGEX_NORM]
or $wrapped->defaultgrade = '1';
if ($answerregs[ANSWER_REGEX_ANSWER_TYPE_NUMERICAL]) {
$multianswer->answertype = NUMERICAL;
$wrapped->qtype = NUMERICAL;
$wrapped->multiplier = array();
$wrapped->units = array();
} else if($answerregs[ANSWER_REGEX_ANSWER_TYPE_SHORTANSWER]) {
$multianswer->answertype = SHORTANSWER;
$wrapped->qtype = SHORTANSWER;
$wrapped->usecase = 0;
} else if($answerregs[ANSWER_REGEX_ANSWER_TYPE_MULTICHOICE]){
$multianswer->answertype = MULTICHOICE;
$wrapped->qtype = MULTICHOICE;
$wrapped->single = 1;
} else {
error("Cannot identify answertype $answerregs[2]");
error("Cannot identify qtype $answerregs[2]");
return false;
}
$multianswer->alternatives= array();
// Each $wrapped simulates a $form that can be processed by the
// respective save_question and save_question_options methods of the
// wrapped questiontypes
$wrapped->answer = array();
$wrapped->fraction = array();
$wrapped->feedback = array();
$wrapped->questiontext = $answerregs[0];
$wrapped->questiontextformat = 0;
$remainingalts = $answerregs[ANSWER_REGEX_ALTERNATIVES];
while (ereg(ANSWER_ALTERNATIVE_REGEX, $remainingalts, $altregs)) {
unset($alternative);
if ('=' == $altregs[ANSWER_ALTERNATIVE_REGEX_FRACTION]) {
$alternative->fraction = '1';
$wrapped->fraction[] = '1';
} else if ($percentile =
$altregs[ANSWER_ALTERNATIVE_REGEX_PERCENTILE_FRACTION]){
$wrapped->fraction[] = .01 * $percentile;
} else {
$alternative->fraction = .01 *
$altregs[ANSWER_ALTERNATIVE_REGEX_PERCENTILE_FRACTION]
or $alternative->fraction = '0';
$wrapped->fraction[] = '0';
}
$alternative->feedback = $altregs[ANSWER_ALTERNATIVE_REGEX_FEEDBACK];
$wrapped->feedback[] = $altregs[ANSWER_ALTERNATIVE_REGEX_FEEDBACK];
if ($answerregs[ANSWER_REGEX_ANSWER_TYPE_NUMERICAL]
&& ereg(NUMERICAL_ALTERNATIVE_REGEX,
$altregs[ANSWER_ALTERNATIVE_REGEX_ANSWER],
$numregs) )
{
$alternative->answer = $numregs[NUMERICAL_CORRECT_ANSWER];
$wrapped->answer[] = $numregs[NUMERICAL_CORRECT_ANSWER];
if ($numregs[NUMERICAL_ABS_ERROR_MARGIN]) {
$alternative->min = $numregs[NUMERICAL_CORRECT_ANSWER]
- $numregs[NUMERICAL_ABS_ERROR_MARGIN];
$alternative->max = $numregs[NUMERICAL_CORRECT_ANSWER]
+ $numregs[NUMERICAL_ABS_ERROR_MARGIN];
$wrapped->tolerance = $numregs[NUMERICAL_ABS_ERROR_MARGIN];
} else {
$alternative->min = $numregs[NUMERICAL_CORRECT_ANSWER];
$alternative->max = $numregs[NUMERICAL_CORRECT_ANSWER];
$wrapped->tolerance = 0;
}
} else { // Min and max must stay undefined...
$alternative->answer =
$altregs[ANSWER_ALTERNATIVE_REGEX_ANSWER];
} else { // Tolerance can stay undefined for non numerical questions
$wrapped->answer[] = $altregs[ANSWER_ALTERNATIVE_REGEX_ANSWER];
}
$multianswer->alternatives[] = $alternative;
$tmp = explode($altregs[0], $remainingalts, 2);
$remainingalts = $tmp[1];
}
$question->defaultgrade += $multianswer->norm;
$question->answers[] = $multianswer;
$question->defaultgrade += $wrapped->defaultgrade;
$question->options->questions[$positionkey] = clone($wrapped);
$question->questiontext = implode("{#$positionkey}",
explode($answerregs[0], $question->questiontext, 2));
}
return $question;
}
function quiz_qtype_multianswer_save_alternatives($questionid,
$answertype, $alternatives, $oldalternativeids= NULL) {
// Returns false if something goes wrong,
// otherwise the ids of the answers.
if (empty($oldalternativeids)
or !($oldalternatives =
get_records_list('quiz_answers', 'id', $oldalternativeids)))
{
$oldalternatives = array();
}
$alternativeids = array();
foreach ($alternatives as $altdata) {
if ($altold = array_shift($oldalternatives)) { // Use existing one...
$alt = $altold;
$alt->answer = $altdata->answer;
$alt->fraction = $altdata->fraction;
$alt->feedback = $altdata->feedback;
if (!update_record("quiz_answers", $alt)) {
return false;
}
} else { // Completely new one
unset($alt);
$alt->question= $questionid;
$alt->answer = $altdata->answer;
$alt->fraction = $altdata->fraction;
$alt->feedback = $altdata->feedback;
if (!($alt->id = insert_record("quiz_answers", $alt))) {
return false;
}
}
// For the answer type numerical, each alternative has individual options:
if ($answertype == NUMERICAL) {
if ($numericaloptions =
get_record('quiz_numerical', 'answer', $alt->id))
{
// Reuse existing numerical options
$numericaloptions->min = $altdata->min;
$numericaloptions->max = $altdata->max;
if (!update_record('quiz_numerical', $numericaloptions)) {
return false;
}
} else {
// New numerical options
$numericaloptions->answer = $alt->id;
$numericaloptions->question = $questionid;
$numericaloptions->min = $altdata->min;
$numericaloptions->max = $altdata->max;
if (!insert_record("quiz_numerical", $numericaloptions)) {
return false;
}
}
} else { // Delete obsolete numerical options
delete_records('quiz_numerical', 'answer', $alt->id);
} // end if NUMERICAL
$alternativeids[] = $alt->id;
} // end foreach $alternatives
$answers = implode(',', $alternativeids);
// Removal of obsolete alternatives from answers and quiz_numerical:
while ($altobsolete = array_shift($oldalternatives)) {
delete_records("quiz_answers", "id", $altobsolete->id);
// Possibly obsolute numerical options are also to be deleted:
delete_records("quiz_numerical", 'answer', $altobsolete->id);
}
// Common alternative options and removal of obsolete options
switch ($answertype) {
case NUMERICAL:
if (!empty($oldalternativeids)) {
delete_records('quiz_shortanswer', 'answers',
$oldalternativeids);
delete_records('quiz_multichoice', 'answers',
$oldalternativeids);
}
break;
case SHORTANSWER:
if (!empty($oldalternativeids)) {
delete_records('quiz_multichoice', 'answers',
$oldalternativeids);
$options = get_record('quiz_shortanswer',
'answers', $oldalternativeids);
} else {
unset($options);
}
if (empty($options)) {
// Create new shortanswer options
$options->question = $questionid;
$options->usecase = 0;
$options->answers = $answers;
if (!insert_record('quiz_shortanswer', $options)) {
return false;
}
} else if ($answers != $oldalternativeids) {
// Shortanswer options needs update:
$options->answers = $answers;
if (!update_record('quiz_shortanswer', $options)) {
return false;
}
}
break;
case MULTICHOICE:
if (!empty($oldalternativeids)) {
delete_records('quiz_shortanswer', 'answers',
$oldalternativeids);
$options = get_record('quiz_multichoice',
'answers', $oldalternativeids);
} else {
unset($options);
}
if (empty($options)) {
// Create new multichoice options
$options->question = $questionid;
$options->layout = 0;
$options->single = 1;
$options->answers = $answers;
if (!insert_record('quiz_multichoice', $options)) {
return false;
}
} else if ($answers != $oldalternativeids) {
// Multichoice options needs update:
$options->answers = $answers;
if (!update_record('quiz_multichoice', $options)) {
return false;
}
}
break;
default:
return false;
}
return $answers;
}
?>
@@ -13,28 +13,30 @@
class quiz_multichoice_qtype extends quiz_default_questiontype {
function get_answers($question, $addedcondition= '') {
// The added condition is one addition that has been added
// to the behaviour of this question type in order to make
// it embeddable within a multianswer (embedded cloze) question
global $CFG;
// There should be multiple answers
return get_records_sql("SELECT a.*, mc.single
FROM {$CFG->prefix}quiz_multichoice mc,
{$CFG->prefix}quiz_answers a
WHERE mc.question = '$question->id'
AND mc.question = a.question "
. $addedcondition);
}
function name() {
return 'multichoice';
}
function get_question_options(&$question) {
// Get additional information from database
// and attach it to the question object
if (!$question->options = get_record('quiz_multichoice', 'question',
$question->id)) {
notify('Error: Missing question options!');
return false;
}
if (!$question->options->answers = get_records('quiz_answers', 'question',
$question->id)) {
notify('Error: Missing question answers!');
return false;
}
return true;
}
function save_question_options($question) {
if (!$oldanswers = get_records("quiz_answers", "question",
$question->id, "id ASC")) {
$oldanswers = array();
@@ -130,6 +132,85 @@ class quiz_multichoice_qtype extends quiz_default_questiontype {
return true;
}
function create_session_and_responses(&$question, &$state, $quiz, $attempt) {
// Shuffle the answers if required
$answerids = array_values(array_map(create_function('$val',
'return $val->id;'), $question->options->answers));
if ($quiz->shuffleanswers) {
$answerids = swapshuffle($answerids);
}
$state->options->order = $answerids;
// Create empty responses
if ($question->options->single) {
$state->responses = array('' => '');
} else {
$state->responses = array();
}
return true;
}
function restore_session_and_responses(&$question, &$state) {
// The serialized format for multiple choice quetsions
// is (optionally) a comma separated list of answer ids
// followed by a colon, followed by another comma separated
// list of answer ids, which are the radio/checkboxes that were
// ticked.
// E.g. 1,3,2,4:2,4 means that the answers were shown in the order
// 1, 3, 2 and then 4 and the answers 2 and 4 were checked.
$pos = strpos($state->responses[''], ':');
if (false === $pos) { // No order of answers is given, so use the default
$state->options->order = array_keys($question->options->answers);
} else { // Restore the order of the answers
$state->options->order = explode(',', substr($state->responses[''], 0,
$pos));
$state->responses[''] = substr($state->responses[''], $pos + 1);
}
// Restore the responses
if (empty($state->responses[''])) { // No previous responses
if ($question->options->single) {
$state->responses = array('' => '');
} else {
$state->responses = array();
}
} else {
if ($question->options->single) {
$state->responses = array('' => $state->responses['']);
} else {
// Get array of answer ids
$state->responses = explode(',', $state->responses);
// Create an array indexed by these answer ids
$state->responses = array_flip($state->responses);
// Set the value of each element to be equal to the index
array_walk($state->responses, create_function('&$a, $b',
'$a = $b;'));
}
}
return true;
}
function save_session_and_responses(&$question, &$state) {
// Bundle the answer order and the responses into the legacy answer
// field.
// The serialized format for multiple choice quetsions
// is (optionally) a comma separated list of answer ids
// followed by a colon, followed by another comma separated
// list of answer ids, which are the radio/checkboxes that were
// ticked.
// E.g. 1,3,2,4:2,4 means that the answers were shown in the order
// 1, 3, 2 and then 4 and the answers 2 and 4 were checked.
$responses = implode(',', $state->options->order) . ':';
$responses .= implode(',', $state->responses);
// Set the legacy answer field
if (!set_field('quiz_states', 'answer', $responses, 'id',
$state->id)) {
return false;
}
return true;
}
function extract_response($rawresponse, $nameprefix) {
// Fetch additional details from the database...
if (!$options = get_record("quiz_multichoice",
@@ -153,16 +234,33 @@ class quiz_multichoice_qtype extends quiz_default_questiontype {
}
}
function print_question_formulation_and_controls($question,
$quiz, $readonly, $answers, $correctanswers, $nameprefix) {
function get_correct_responses(&$question, &$state) {
if ($question->options->single) {
foreach ($question->options->answers as $answer) {
if (((int) $answer->fraction) === 1) {
return array('' => $answer->id);
}
}
return null;
} else {
$responses = array();
foreach ($question->options->answers as $answer) {
if (((float) $answer->fraction) > 0.0) {
$responses[$answer->id] = (string) $answer->id;
}
}
return empty($responses) ? null : $responses;
}
}
// Fetch additional details from the database...
if (!$options = get_record("quiz_multichoice", "question", $question->id)) {
notify("Error: Missing question options!");
}
if (!$answers = get_records_list("quiz_answers", "id", $options->answers)) {
notify("Error: Missing question answers!");
}
function print_question_formulation_and_controls(&$question, &$state, $quiz, $options) {
$answers = &$question->options->answers;
$correctanswers = $this->get_correct_responses($question, $state);
$readonly = empty($options->readonly) ? '' : 'disabled="disabled"';
$formatoptions = new stdClass;
$formatoptions->para = false;
// Print formulation
echo format_text($question->questiontext,
@@ -171,97 +269,106 @@ class quiz_multichoice_qtype extends quiz_default_questiontype {
quiz_print_possible_question_image($quiz->id, $question);
// Print input controls and alternatives
echo "<table align=\"right\">";
$stranswer = get_string("answer", "quiz");
echo '<table align="right">';
$stranswer = get_string('answer', 'quiz');
echo "<tr><td valign=\"top\">$stranswer:&nbsp;&nbsp;</td><td>";
echo "<table>";
$answerids = explode(",", $options->answers);
echo '<table>';
if ($quiz->shuffleanswers) {
$answerids = swapshuffle($answerids);
}
// Handle the case of unanswered single-choice questions:
if ($options->single) {
$singleresponse = isset($question->response[$nameprefix])
? $question->response[$nameprefix] : '0';
}
foreach ($answerids as $key => $aid) {
$answer = $answers[$aid];
// Print each answer in a separate row
foreach ($state->options->order as $key => $aid) {
$answer = &$answers[$aid];
$qnumchar = chr(ord('a') + $key);
echo '<tr><td valign="top">';
if ($options->single) {
$type = ' type="radio" ';
$name = " name=\"$nameprefix\" ";
$checked = $singleresponse == $aid
? ' checked="checked" ' : '';
if ($question->options->single) {
$type = 'type="radio"';
$name = "name=\"{$question->name_prefix}\"";
$checked = $aid == $state->responses['']
? 'checked="checked"' : '';
} else {
$type = ' type="checkbox" ';
$name = " name=\"$nameprefix$aid\" ";
$checked = !empty($question->response[$nameprefix.$aid])
? ' checked="checked" ' : '';
$name = "name=\"{$question->name_prefix}$aid\"";
$checked = isset($state->responses[$aid])
? 'checked="checked"' : '';
}
echo "<input $readonly $name $checked $type value=\"$answer->id\" alt=\"".s($answer->answer)."\" />";
echo "</td>";
/// remove para tags - not needed
$options->para = false;
if ($readonly and $quiz->correctanswers and !empty($correctanswers[$nameprefix.$aid])) {
echo '<td valign="top" class="highlight">'.format_text("$qnumchar. $answer->answer", FORMAT_MOODLE , $options).'</td>';
// Print the control
echo "<input $readonly $name $checked $type value=\"$aid\" alt=\""
. s($answer->answer) . '" />';
echo '</td>';
// Print the text by the control highlighting if correct responses
// should be shown and the current answer is the correct answer in
// the single selection case or has a positive score in the multiple
// selection case
if ($options->readonly && $options->correct_responses &&
in_array($aid, $correctanswers)) {
echo '<td valign="top" class="highlight">' .
format_text("$qnumchar. $answer->answer", FORMAT_MOODLE ,
$formatoptions) . '</td>';
} else {
echo '<td valign="top">'.format_text("$qnumchar. $answer->answer", FORMAT_MOODLE, $options).'</td>';
echo '<td valign="top">' .
format_text("$qnumchar. $answer->answer", FORMAT_MOODLE,
$formatoptions) . '</td>';
}
if ($quiz->feedback) {
echo "<td valign=\"top\">&nbsp;";
if ($checked) { // Simpliest condition to use here
quiz_print_comment($answer->feedback);
}
echo "</td>";
// Print feedback by selected options if feedback is on
if (($options->feedback || $options->correct_responses) &&
$checked) {
echo '<td valign="top">&nbsp;';
quiz_print_comment($answer->feedback);
echo '</td>';
}
echo "</tr>";
echo '</tr>';
}
echo "</table>";
echo "</td></tr></table>";
echo '</table>';
echo '</td></tr></table>';
}
function grade_response($question, $nameprefix, $addedanswercondition='') {
function grade_responses(&$question, &$state, $quiz) {
$answers = $question->options->answers;
$testedstate = clone($state);
$teststate = clone($state);
$result->correctanswers = array();
$result->answers = array();
$result->grade = 0.0;
$answers = $this->get_answers($question, $addedanswercondition);
/// Set ->answers[] and ->grade
if (!empty($question->response)) {
foreach ($question->response as $name => $response) {
if (isset($answers[$response])) {
$result->answers[$name] = $answers[$response];
$result->grade += $answers[$response]->fraction;
foreach($answers as $answer) {
$teststate->responses[''] = $answer->id;
if($question->options->single) {
if($this->compare_responses($question, $testedstate,
$teststate)) {
$state->raw_grade = $answer->fraction;
break;
}
}
}
/// Set ->correctanswers[]
foreach ($answers as $answer) {
if ($answer->single) {
$result->correctanswers =
quiz_extract_correctanswers($answers, $nameprefix);
break;
} else {
if ($answer->fraction > 0.0) {
$result->correctanswers[$nameprefix.$answer->id] = $answer;
foreach($state->responses as $response) {
$testedstate->responses = array('' => $response);
if($this->compare_responses($question, $testedstate,
$teststate)) {
$state->raw_grade += $answer->fraction;
break;
}
}
}
}
return $result;
if (empty($state->raw_grade)) {
$state->raw_grade = 0;
}
// Make sure we don't assign negative or too high marks
$state->raw_grade = min(max((float) $state->raw_grade,
0.0), 1.0) * $question->maxgrade;
// Apply the penalty for this attempt
$state->penalty = 0;
// Only allow one attempt at the question
$state->event = QUIZ_EVENTCLOSE;
return true;
}
function print_question_grading_details(&$question, &$state, $quiz, $options) {
// do nothing
}
}
//// END OF CLASS ////
@@ -1,83 +1,24 @@
<?php // $Id$
// This will only support one answer of the type NUMERICAL
// However, lib.php has support for multiple answers
if (!empty($question->id)) {
$answersraw= quiz_get_answers($question);
}
$answers= array();
for ($i=0; $i<6; $i++) {
$answers[$i]->answer = ""; // Make answer slots, default as blank...
$answers[$i]->min = "";
$answers[$i]->max = "";
$answers[$i]->feedback = "";
}
if (!empty($answersraw)) {
$i=0;
foreach ($answersraw as $answer) {
$answers[$i] = $answer;
$i++;
}
}
$units = array();
for ($i=0 ; $i<6 ; $i++) {
$units[$i]->multiplier = ''; // Make unit slots, default as blank...
$units[$i]->unit = '';
}
if (!empty($question->id) and $unitsraw = get_records(
'quiz_numerical_units', 'question', $question->id)) {
/// Find default unit and have it put in the zero slot
/// This procedure might be overridden later when
/// the unit is stripped form an answer...
foreach ($unitsraw as $key => $unit) {
if (1.0 == $unit->multiplier) {
/// Default unit found:
$units[0] = $unit;
unset($unitsraw[$key]);
break;
}
}
/// Fill remaining answer slots with whatsever left
if (!empty($unitsraw)) {
$i = 1; // The zero slot got the default unit...
foreach ($unitsraw as $unit) {
$units[$i] = $unit;
$i++;
}
}
// Get all the extra information if we're editing
if (!empty($question->id) && isset($question->qtype) &&
$QUIZ_QTYPES[$question->qtype]->get_question_options($question)) {
$answer = array_values($question->options->answers);
usort($answer, create_function('$a, $b',
'if ($a->fraction == $a->fraction) { return 0; }' .
'else { return $a->fraction < $b->fraction ? -1 : 1; }'));
$answer = $answer[0]; // Get the answer with the highest fraction (i.e. 1)
$units = array_values($question->options->units);
usort($units, create_function('$a, $b', // make sure the default unit is at index 0
'if (1.0 === (float)$a->multiplier) { return -1; } else '.
'if (1.0 === (float)$b->multiplier) { return 1; } else { return 0; }'));
$tolerance = $question->options->tolerance;
} else {
$units[0]->multiplier = 1.0;
}
// Strip unit from answers, if they have any:
foreach ($units as $key => $unit) {
if (1.0 == $unit->multiplier && $unit->unit) {
$ukey = $key;
// Possible default unit found:
foreach ($answers as $i => $answer) {
if (($answer->min || $answer->max) && ereg(
"^(.*)$unit->unit$", $answer->answer, $numreg)) {
$answers[$i]->answer = $numreg[1];
if (0!=$ukey) {
// Make unit default by putting it first:
$units[$ukey] = $units[0];
$units[0] = $unit;
$ukey = 0;
}
}
}
}
}
// Strip trailing zeros from multipliers
foreach ($units as $i => $unit) {
if (ereg('^(.*\\..(.*[^0])?)0+$', $unit->multiplier, $regs1)) {
if (ereg('^(.+)\\.0$', $regs1[1], $regs2)) {
$units[$i]->multiplier = $regs2[1];
} else {
$units[$i]->multiplier = $regs1[1];
}
}
$answer = new stdClass;
$answer->answer = '';
$answer->feedback = '';
$units = array();
$tolerance = '';
}
print_heading_with_help(get_string("editingnumerical", "quiz"), "numerical", "quiz");
+28 -71
View File
@@ -31,7 +31,7 @@
</td>
<td>
<?php if (isset($err["questiontext"])) {
formerr($err["questiontext"]);
formerr($err["questiontext"]);
echo "<br />";
}
@@ -67,44 +67,36 @@
<tr valign="top">
<td align="right"><b><?php print_string("correctanswer", "quiz") ?>:</b></td>
<?php
// Even thou the rest of the module can handle up to six numerical answers,
// this form will limit the number of numerical answers to one only.
if (is_numeric($answers[0]->min) && is_numeric($answers[0]->answer)) {
$acceptederror = (float)($answers[0]->answer)
- (float)($answers[0]->min);
} else {
$acceptederror = "";
}
?>
<td>
<input align="left" type="text" id="correct0" name="answer[]" size="20" value="<?php p($answers[0]->answer) ?>" alt="<?php print_string("correctanswer", "quiz") ?>" />&nbsp;&nbsp;
<input align="left" type="text" id="correct0" name="answer[]" size="10" value="<?php p($answer->answer) ?>" alt="<?php print_string("correctanswer", "quiz") ?>" />
</td>
</tr>
<tr valign="top">
<td align="right"><b><?php print_string("acceptederror", "quiz"); ?>:</b></td>
<td>
<input align="left" type="text" id="acceptederror0" name="acceptederror[]" size="15" value="<?php p($acceptederror) ?>" alt="<?php print_string("acceptederror", "quiz"); ?>" />&plusmn;
<!-- Values max and min will be determined when the form is submitted -->
<input type="hidden" id="min0" name="min[]" value="" />
<input type="hidden" id="max0" name="max[]" value="" />
<input align="left" type="text" id="acceptederror0" name="tolerance" size="10" value="<?php p($tolerance); ?>" alt="<?php print_string("acceptederror", "quiz"); ?>" />&plusmn;
<input type="hidden" name="fraction[]" value="1" />
<br/>
</td>
</tr>
<tr valign="top">
<td align="right"><b><?php print_string("feedback", "quiz") ?>:</b></td>
<td>
<textarea name="feedback[]" rows="2" cols="50"><?php p($answers[0]->feedback) ?></textarea>
<textarea name="feedback[]" rows="2" cols="50"><?php p($answer->feedback); ?></textarea>
</td>
</tr>
<tr valign="top">
<td align="right"><b><?php print_string("unit", "quiz") ?>:</b></td>
<td>
<input type="hidden" name="multiplier[]" value="1.0" />
<input align="left" type="text" id="defaultunit" name="unit[]"
size="5" value="<?php p($units[0]->unit) ?>" alt="<?php print_string("unit", "quiz") ?>" />
<b>(<?php print_string("optional", "quiz") ?>)</b>
<td><?php
if (isset($units[0]) && 1.0 === (float)$units[0]->multiplier) {
$unit = $units[0]->unit;
} else {
$unit = '';
}
$multiplier = '1.0';
?>
<input type="hidden" name="multiplier[]" value="<?php p($multiplier) ?>" />
<input align="left" type="text" id="defaultunit" name="unit[]" size="10" value="<?php p($unit) ?>" alt="<?php print_string("unit", "quiz") ?>" />
<b>(<?php print_string("optional", "quiz") ?>)</b>
</td>
</tr>
<tr valign="top">
@@ -113,66 +105,31 @@
<td></td>
</tr>
<?php
for ($i=1; $i<count($units); $i++) {
$unit=$units[$i];
for ($i = 1; $i < 6; $i++) {
if (isset($units[$i])) {
$multiplier = (float)$units[$i]->multiplier;
$unit = $units[$i]->unit;
} else {
$multiplier = '';
$unit = '';
}
?>
<tr valign="top">
<td></td>
<td align="left">
<b><?php print_string("multiplier", "quiz") ?>:</b>
<input type="text" id="<?php p("multiplier$i") ?>" size="10"
align="right" name="multiplier[]"
value="<?php p($unit->multiplier) ?>"
alt="<?php print_string("multiplier", "quiz") ?>" />
<b>&nbsp;&nbsp;&nbsp;<?php print_string("unit", "quiz") ?>:</b>
<input align="left" type="text" id="<?php p("unit$i") ?>"
name="unit[]"
size="5" value="<?php p($unit->unit) ?>" />
<input type="text" size="10" align="right" name="multiplier[]" value="<?php p($multiplier) ?>"
alt="<?php print_string("multiplier", "quiz") ?>" />
<b>&nbsp;&nbsp;&nbsp;<?php p("unit", "quiz") ?>:</b>
<input align="left" type="text" name="unit[]" size="5" value="<?php p($unit) ?>" />
</td>
</tr>
<?php
} /// END for
$QUIZ_QTYPES[$question->qtype]->print_replacement_options($question, $course, $contextquiz);
$QUIZ_QTYPES[$question->qtype]->print_question_form_end($question, 'onclick="return determineMinAndMax();"');
$QUIZ_QTYPES[$question->qtype]->print_question_form_end($question);
?>
</table>
</center>
</form>
<script language="javascript" type="text/javascript">
function determineMinAndMax() {
// This client-side script will determine the values for min and max
// based on the input for answer and acceptederror.
with(document.theform) {
if (correct0.value=='') {
alert('<?php print_string("missingcorrectanswer","quiz") ?>');
return false;
} else if (isNaN(correct0.value) && defaultunit.value) {
alert('<?php print_string("unitonlyworksfornumericanswers","quiz") ?>');
return false;
} else if (''!=defaultunit.value && !isNaN(defaultunit.value)) {
alert('<?php print_string("unitmustnotbenumeric","quiz") ?>');
return false;
} else if (acceptederror0.value=='') {
var correct= parseFloat(correct0.value);
if (!isNaN(correct)) {
min0.value= correct;
max0.value= correct;
correct0.value += defaultunit.value;
}
return true;
} else if (isNaN(acceptederror0.value) || isNaN(correct0.value)) {
alert('<?php print_string("answerswithacceptederrormarginmustbenumeric", "quiz") ?>');
return false;
} else {
var correct= parseFloat(correct0.value);
var error= Math.abs(acceptederror0.value);
min0.value= correct-error;
max0.value= correct+error;
correct0.value += defaultunit.value;
return true;
}
}
}
</script>
+231 -176
View File
@@ -18,223 +18,278 @@ require_once("$CFG->dirroot/mod/quiz/questiontypes/shortanswer/questiontype.php"
class quiz_numerical_qtype extends quiz_shortanswer_qtype {
function get_answers($question, $addedcondition='') {
// The added condition is one addition that has been added
// to the behaviour of this question type in order to make
// it embeddable within a multianswer (embedded cloze) question
global $CFG;
// There can be multiple answers
return get_records_sql("SELECT a.*, n.min, n.max
FROM {$CFG->prefix}quiz_numerical n,
{$CFG->prefix}quiz_answers a
WHERE a.question = '$question->id'
AND n.answer = a.id "
. $addedcondition);
}
function name() {
return 'numerical';
}
function save_question_options($question) {
function get_question_options(&$question) {
if (!$question->options->answers = get_records('quiz_answers',
'question', $question->id)) {
notify('Error: Missing question answer!');
return false;
}
if (!$oldanswers = get_records("quiz_answers", "question", $question->id, "id ASC")) {
if (false === ($question->options->tolerance = get_field('quiz_numerical',
'tolerance', 'question', $question->id))) {
$question->options->tolerance = 0;
}
$this->get_numerical_units($question);
return true;
}
function get_numerical_units(&$question) {
if ($question->options->units = get_records('quiz_numerical_units',
'question', $question->id, 'id ASC')) {
$question->options->units = array_values($question->options->units);
usort($question->options->units, create_function('$a, $b', // make sure the default unit is at index 0
'if (1.0 === (float)$a->multiplier) { return -1; } else '.
'if (1.0 === (float)$b->multiplier) { return 1; } else { return 0; }'));
array_walk($question->options->units, create_function('$val',
'$val->multiplier = (float)$val->multiplier;'));
} else {
$question->options->units = array();
}
return true;
}
function get_default_numerical_unit(&$question) {
if (!isset($question->options->units[0])) {
return '';
} else if (1.0 === (float)$question->options->units[0]->multiplier) {
return $question->options->units[0];
} else {
foreach ($question->options->units as $unit) {
if (1.0 === (float)$unit->multiplier) {
return $unit;
}
}
}
}
function save_question_options($question) {
// save_question_options supports the definition of multiple answers
// for numerical questions. This is not currently used by the editing
// interface, but the GIFT format supports it. The multianswer qtype,
// for example can make use of this feature.
// Get old versions of the objects
if (!$oldanswers = get_records("quiz_answers", "question", $question->id)) {
$oldanswers = array();
}
$answers = array();
$maxfraction = -1;
if (!$oldoptions = get_record("quiz_numerical", "question", $question->id)) {
$oldoptions = new stdClass;
}
$result = $this->save_numerical_units($question);
if (isset($result->error)) {
return $result;
} else {
$units = &$result->units;
}
$answerids = array();
// Insert all the new answers
foreach ($question->answer as $key => $dataanswer) {
if ($dataanswer != "") {
$answer = new stdClass;
$answer->question = $question->id;
$answer->answer = trim($dataanswer);
$answer->fraction = $question->fraction[$key];
$answer->feedback = trim($question->feedback[$key]);
if (!is_numeric($answer->answer)) {
$result->error = "The answer '$answer->answer' is not numeric!";
return $result;
}
if ($oldanswer = array_shift($oldanswers)) { // Existing answer, so reuse it
$answer = $oldanswer;
$answer->answer = $dataanswer;
$answer->fraction = $question->fraction[$key];
$answer->feedback = $question->feedback[$key];
if (!update_record("quiz_answers", $answer)) {
$answer->id = $oldanswer->id;
if (! update_record("quiz_answers", $answer)) {
$result->error = "Could not update quiz answer! (id=$answer->id)";
return $result;
}
} else { // This is a completely new answer
unset($answer);
$answer->answer = $dataanswer;
$answer->question = $question->id;
$answer->fraction = $question->fraction[$key];
$answer->feedback = $question->feedback[$key];
if (!$answer->id = insert_record("quiz_answers", $answer)) {
if (! $answer->id = insert_record("quiz_answers", $answer)) {
$result->error = "Could not insert quiz answer!";
return $result;
}
}
$answers[] = $answer->id;
if ($question->fraction[$key] > $maxfraction) {
$maxfraction = $question->fraction[$key];
}
if ($options = get_record("quiz_numerical", "answer", $answer->id)) {
$options->min= $question->min[$key];
$options->max= $question->max[$key];
if (!update_record("quiz_numerical", $options)) {
$result->error = "Could not update quiz numerical options! (id=$options->id)";
return $result;
}
} else { // completely new answer
unset($options);
$options->question = $question->id;
$options->answer = $answer->id;
$options->min = $question->min[$key];
$options->max = $question->max[$key];
if (!insert_record("quiz_numerical", $options)) {
$result->error = "Could not insert quiz numerical options!";
return $result;
}
}
$answerids[] = $answer->id;
}
}
/// Save units
/// Make unit records
$newunits = array();
unset($tmpunit);
$tmpunit->question = $question->id;
foreach ($question->multiplier as $key => $multiplier) {
if ($multiplier && is_numeric($multiplier)) {
$tmpunit->multiplier = $multiplier;
$tmpunit->unit = trim($question->unit[$key]);
$newunits[] = $tmpunit;
// Set up the options object
$options = clone($oldoptions);
$options->question = $question->id;
$options->answers = implode(',', $answerids);
$options->tolerance = $this->apply_unit($question->tolerance, $units);
// Save options
if (isset($oldoptions->id)) { // options have changed
$options->id = $oldoptions->id;
if (! update_record('quiz_numerical', $options)) {
$result->error = "Could not update quiz numerical options! (id=$options->id)";
return $result;
}
}
if (1 == count($newunits) && !$newunits[0]->unit) {
/// Only default unit and it is empty, so drop it:
$newunits = array();
}
if ($oldunits = get_records('quiz_numerical_units',
'question', $question->id)) {
foreach ($oldunits as $unit) {
if ($newunit = array_shift($newunits)) {
$unit->multiplier = $newunit->multiplier;
$unit->unit = $newunit->unit;
if (!update_record('quiz_numerical_units', $unit)) {
$result->error = "Could not update quiz_numerical_unit $unit->unit";
return $result;
}
} else {
delete_records('quiz_numerical_units', 'id', $unit->id);
}
}
}
foreach ($newunits as $newunit) {
// Create new records for the remaining units:
if (!insert_record('quiz_numerical_units', $newunit)) {
$result->error = "Unable to insert new unit $newunit->unit";
} else { // new options
if (! insert_record('quiz_numerical', $options)) {
$result->error = "Could not insert quiz numerical options!";
return $result;
}
}
/// Perform sanity checks on fractional grades
if ($maxfraction != 1) {
$maxfraction = $maxfraction * 100;
$result->noticeyesno = get_string("fractionsnomax", "quiz", $maxfraction);
return $result;
} else {
return true;
}
}
function grade_response($question, $nameprefix, $addedanswercondition='') {
$result->answers = array();
$units = get_records('quiz_numerical_units',
'question', $question->id);
if (isset($question->response[$nameprefix])) {
$response = trim(stripslashes($question->response[$nameprefix]));
// Arrays with 'wild cards':
$search = array(' ',',');
$replace = array('','.');
$responsenum = str_replace($search, $replace, $response);
if (empty($units)) {
if ('' !== $responsenum && is_numeric($responsenum)) {
$responsenum = (float)$responsenum;
} else {
unset($responsenum); // Answer is not numeric
}
} else if (ereg(
'^([+-]?([0-9]+(\\.[0-9]*)?|[.][0-9]+)([eE][-+]?[0-9]+)?)([^0-9].*)?$',
$responsenum, $responseparts)) {
$responsenum = (float)$responseparts[1];
if ($responseparts[5]) {
$responseunit = $responseparts[5];
} else {
$responseunit = '';
}
$unitidentified = false;
foreach ($units as $unit) {
if (str_replace($search, $replace, $unit->unit)
== $responseunit) {
$unitidentified = true;
$responsenum /= $unit->multiplier;
break;
function save_numerical_units($question) {
if (!$oldunits = get_records("quiz_numerical_units", "question", $question->id)) {
$oldunits = array();
}
// Set the units
$units = array();
$keys = array();
$oldunits = array_values($oldunits);
usort($oldunits, create_function('$a, $b', // make sure the default unit is at index 0
'if (1.0 === (float)$a->multiplier) { return -1; } else '.
'if (1.0 === (float)$b->multiplier) { return 1; } else { return 0; }'));
foreach ($oldunits as $unit) {
$units[] = clone($unit);
}
$n = isset($question->multiplier) ? count($question->multiplier) : 0;
for ($i = 0; $i < $n; $i++) {
// Discard any unit which doesn't specify the unit or the multiplier
if (!empty($question->multiplier[$i]) && !empty($question->unit[$i])) {
$units[$i]->question = $question->id;
$units[$i]->multiplier =
$this->apply_unit($question->multiplier[$i], array());
$units[$i]->unit = $question->unit[$i];
} else {
unset($units[$i]);
}
}
unset($question->multiplier, $question->unit);
/// Save units
for ($i = 0; $i < $n; $i++) {
if (!isset($units[$i]) && isset($oldunits[$i])) { // Delete if it hasn't been resubmitted
delete_records('quiz_numerical_units', 'id', $oldunits[$i]->id);
} else if ($oldunits != $units) { // answer has changed or is new
if (isset($oldunits[$i]->id)) { // answer has changed
$units[$i]->id = $oldunits[$i]->id;
if (! update_record('quiz_numerical_units', $units[$i])) {
$result->error = "Could not update quiz_numerical_unit $units[$i]->unit";
return $result;
}
} else if (isset($units[$i])) { // answer is new
if (! insert_record('quiz_numerical_units', $units[$i])) {
$result->error = "Unable to insert new unit $units[$i]->unit";
return $result;
}
}
if (!$unitidentified) {
unset($responsenum); // No unit OK
}
} else {
unset($responsenum); // Answer is not numeric
}
} else {
}
$result->units = &$units;
return $result;
}
function compare_responses(&$question, &$state, &$teststate) {
if (isset($state->responses[''])) {
$response = $this->apply_unit(stripslashes($state->responses['']),
$question->options->units);
} else {
$response = '';
}
$answers = $this->get_answers($question, $addedanswercondition);
foreach ($answers as $answer) {
/// Check if response matches answer...
if ('' !== $response and empty($result->answers)
|| $answer->fraction
> $result->answers[$nameprefix]->fraction
and strtolower($response) == strtolower($answer->answer)
|| '' != trim($answer->min) && isset($responsenum)
&& $responsenum >= (float)$answer->min
&& $responsenum <= (float)$answer->max) {
$result->answers[$nameprefix] = $answer;
}
if (isset($teststate->responses[''])) {
$testresponse =
$this->apply_unit(stripslashes($teststate->responses['']),
$question->options->units);
} else {
$testresponse = '';
}
$result->grade = isset($result->answers[$nameprefix])
? $result->answers[$nameprefix]->fraction
: 0.0;
$result->correctanswers = quiz_extract_correctanswers($answers,
$nameprefix);
$this->get_tolerance_interval($question, $teststate);
// We need to add a tiny fraction (0.00000000000000001) to make the
// comparison work correctly. Otherwise seemingly equal values can yield
// false.
$tolerance = (float)$question->options->tolerance + 0.00000000000000001;
/////////////////////////////////////////////////
// For numerical answer we have the policy to
// set feedback for any response, even if the
// response does not entitle the student to it.
/////////////////////////////////////////////////
if ('' !== $response and empty($result->answers)
|| empty($result->answers[$nameprefix]->feedback)) {
// Look for just any feedback:
foreach ($result->correctanswers as $correctanswer) {
if ($correctanswer->feedback) {
$result->answers[$nameprefix]->feedback =
$correctanswer->feedback;
if (empty($result->answers[$nameprefix]->id)) {
// Better fake an answer as well:
$result->answers[$nameprefix]->id = 0;
$result->answers[$nameprefix]->answer = $response;
$result->answers[$nameprefix]->fraction = 0.0;
$result->answers[$nameprefix]->question = $question->id;
}
break;
}
if (is_numeric($response) && is_numeric($testresponse)) {
if ($teststate->options->min <= $response
&& $teststate->options->max >= $response) {
return true;
}
}
return false;
}
return $result;
function get_tolerance_interval(&$question, &$state) {
$answer = (float)$state->responses[''];
if (empty($question->options->tolerance)) {
$state->options->min = $state->options->max = $answer;
return true;
}
if (!isset($question->options->tolerancetype)) {
$question->options->tolerancetype = 2; // nominal
}
$tolerance = (float)$question->options->tolerance;
switch ($question->options->tolerancetype) {
case '1': case 'relative':
/// Recalculate the tolerance and fall through
/// to the nominal case:
$tolerance = $answer * $tolerance;
// Falls through to the nominal case -
case '2': case 'nominal':
$tolerance = abs($tolerance); // important - otherwise min and max are swapped
$max = $answer + $tolerance;
$min = $answer - $tolerance;
break;
case '3': case 'geometric':
$quotient = 1 + abs($tolerance);
$max = $answer * $quotient;
$min = $answer / $quotient;
break;
default:
error("Unknown tolerance type $question->options->tolerancetype");
}
$state->options->min = $min;
$state->options->max = $max;
return true;
}
/**
* Checks if the $rawresponse has a unit and applys it if appropriate.
*
* @param string $rawresponse The response string to be converted to a float.
* @param array $units An array with the defined units, where the
* unit is the key and the multiplier the value.
* @return float The rawresponse with the unit taken into
* account as a float.
*/
function apply_unit($rawresponse, $units) {
if (!is_numeric($rawresponse)) {
return $rawresponse;
}
// Make units more useful
$tmpunits = array();
foreach ($units as $unit) {
$tmpunits[$unit->unit] = $unit->multiplier;
}
$search = array(' ', ',');
$replace = array('', '.');
$rawresponse = str_replace($search, $replace, $rawresponse); // remove spaces
if (ereg(
'^([+-]?([0-9]+(\\.[0-9]*)?|[.][0-9]+)([eE][-+]?[0-9]+)?)([^0-9].*)?$',
$rawresponse, $responseparts)) {
$responsenum = (float)$responseparts[1];
if (isset($tmpunits[$responseparts[5]])) {
return (float)$responseparts[1]*$tmpunits[$responseparts[5]];
} else {
return (float)$responseparts[1];
}
}
return (float)$rawresponse;
}
}
//// END OF CLASS ////
+268 -117
View File
@@ -23,31 +23,215 @@ class quiz_random_qtype extends quiz_default_questiontype {
return 'random';
}
function save_question_options($question) {
/// No options to be saved for this question type:
function get_question_options(&$question) {
// Don't do anything here, because the random question has no options.
// Everything is handled by the create- or restore_session_and_responses
// functions.
return true;
}
function wrapped_questions($question) {
global $QUIZ_QTYPES;
foreach ($question->response as $key => $response) {
if (ereg('[^0-9][0-9]+random$', $key)) {
$randomquestion = get_record('quiz_questions',
'id', $response);
$randomquestion->response = $question->response;
unset($randomquestion->response[$key]);
if ($subwrapped = $QUIZ_QTYPES[$randomquestion->qtype]
->wrapped_questions($randomquestion)) {
return "$response,$subwrapped";
} else {
return $response;
}
function save_question_options($question) {
// No options, but we use the parent field to hide random questions.
// To avoid problems we set the parent field to the question id.
return (set_field('quiz_questions', 'parent', $question->id, 'id',
$question->id) ? true : false);
}
function create_session_and_responses(&$question, &$state, $quiz, $attempt) {
// Choose a random question from the category:
// We need to make sure that no question is used more than once in the
// quiz. Therfore the following need to be excluded:
// 1. All questions that are explicitly assigned to the quiz
// 2. All random questions
// 3. All questions that are already chosen by an other random question
if (!isset($quiz->questionsinuse)) {
$quiz->questionsinuse = $quiz->questions;
}
if (!isset($this->catrandoms[$question->category])) {
// Need to fetch random questions from category $question->category"
// (Note: $this refers to the questiontype, not the question.)
global $CFG;
$possiblerandomqtypes = "'"
. implode("','", $this->possiblerandomqtypes) . "'";
if ($question->questiontext == "1") {
// recurse into subcategories
$categorylist = quiz_categorylist($question->category);
} else {
$categorylist = $question->category;
}
$this->catrandoms[$question->category] = get_records_sql
("SELECT * FROM {$CFG->prefix}quiz_questions
WHERE category IN ($categorylist)
AND parent = '0'
AND id NOT IN ($quiz->questionsinuse)
AND qtype IN ($possiblerandomqtypes)");
$this->catrandoms[$question->category] =
draw_rand_array($this->catrandoms[$question->category],
count($this->catrandoms[$question->category])); // from bug 1889
}
while ($wrappedquestion =
array_pop($this->catrandoms[$question->category])) {
if (!ereg("(^|,)$wrappedquestion->id(,|$)", $quiz->questionsinuse)) {
/// $randomquestion is not in use and will therefore be used
/// as the randomquestion here...
global $QUIZ_QTYPES;
$QUIZ_QTYPES[$wrappedquestion->qtype]
->get_question_options($wrappedquestion);
$QUIZ_QTYPES[$wrappedquestion->qtype]
->create_session_and_responses($wrappedquestion, $state, $quiz,
$attempt);
$wrappedquestion->name_prefix = $question->name_prefix;
$wrappedquestion->maxgrade = $question->maxgrade;
$quiz->questionsinuse .= ",$wrappedquestion->id";
$state->options->question = &$wrappedquestion;
return true;
}
}
notify(get_string('toomanyrandom', 'quiz', $question->category));
return false;
}
function restore_session_and_responses(&$question, &$state) {
global $QUIZ_QTYPES;
if (!ereg('^random([0-9]+)-(.*)$', $state->responses[''], $answerregs)) {
notify("The answer value '{$state->responses['']}' for the state with "
."id=$state->id to the random question "
."$question->id is malformated."
." - No response can be extracted!");
return false;
}
$state->responses[''] = $answerregs[2];
if (!$wrappedquestion = get_record('quiz_questions', 'id', $answerregs[1])) {
return false;
}
if (!$QUIZ_QTYPES[$wrappedquestion->qtype]
->get_question_options($wrappedquestion)) {
return false;
}
if (!$QUIZ_QTYPES[$wrappedquestion->qtype]
->restore_session_and_responses($wrappedquestion, $state)) {
return false;
}
$wrappedquestion->name_prefix = $question->name_prefix;
$wrappedquestion->maxgrade = $question->maxgrade;
$state->options->question = &$wrappedquestion;
return true;
}
function save_session_and_responses(&$question, &$state) {
global $QUIZ_QTYPES;
$wrappedquestion = &$state->options->question;
// Trick the wrapped question into pretending to be the random one.
$realqid = $wrappedquestion->id;
$wrappedquestion->id = $question->id;
$QUIZ_QTYPES[$wrappedquestion->qtype]
->save_session_and_responses($wrappedquestion, $state);
// Read what the wrapped question has just set the answer field to
// (if anything)
$response = get_field('quiz_states', 'answer', 'id', $state->id);
if(false === $response) {
return false;
}
// Prefix the answer field...
$response = "random$realqid-$response";
// ... and save it again.
if (!set_field('quiz_states', 'answer', $response, 'id', $state->id)) {
return false;
}
// Restore the real id
$wrappedquestion->id = $realqid;
return true;
}
function get_correct_responses(&$question, &$state) {
global $QUIZ_QTYPES;
$wrappedquestion = &$state->options->question;
return $QUIZ_QTYPES[$wrappedquestion->qtype]
->get_correct_responses($wrappedquestion, $state);
}
function print_question(&$question, &$state, &$number, $quiz, $options) {
global $QUIZ_QTYPES;
$wrappedquestion = &$state->options->question;
$QUIZ_QTYPES[$wrappedquestion->qtype]
->print_question($wrappedquestion, $state, $number, $quiz, $options);
}
/*
function print_question_grading_details(&$question, &$state, $quiz,
$options) {
global $QUIZ_QTYPES;
$wrappedquestion = &$state->options->question;
$QUIZ_QTYPES[$wrappedquestion->qtype]
->print_question_grading_details($wrappedquestion, $state, $quiz,
$options);
}
function print_question_formulation_and_controls(&$question, &$state, $quiz,
$options) {
global $QUIZ_QTYPES;
$wrappedquestion = &$state->options->question;
$QUIZ_QTYPES[$wrappedquestion->qtype]
->print_question_formulation_and_controls($wrappedquestion, $state,
$quiz, $options);
}
function print_question_submit_buttons(&$question, &$state, $quiz,
$options) {
global $QUIZ_QTYPES;
$wrappedquestion = &$state->options->question;
$QUIZ_QTYPES[$wrappedquestion->qtype]
->print_question_submit_buttons($wrappedquestion, $state, $quiz,
$options);
}
*/
function grade_responses(&$question, &$state, $quiz) {
global $QUIZ_QTYPES;
$wrappedquestion = &$state->options->question;
return $QUIZ_QTYPES[$wrappedquestion->qtype]
->grade_responses($wrappedquestion, $state, $quiz);
}
function get_texsource(&$question, &$state, $quiz, $type) {
global $QUIZ_QTYPES;
$wrappedquestion = &$state->options->question;
return $QUIZ_QTYPES[$wrappedquestion->qtype]
->get_texsource($wrappedquestion, $state, $quiz, $type);
}
function compare_responses(&$question, $state, $teststate) {
global $QUIZ_QTYPES;
$wrappedquestion = &$state->options->question;
return $QUIZ_QTYPES[$wrappedquestion->qtype]
->compare_responses($wrappedquestion, $state, $teststate);
}
function print_replacement_options($question, $course, $quizid='0') {
global $QUIZ_QTYPES;
$wrappedquestion = &$state->options->question;
return $QUIZ_QTYPES[$wrappedquestion->qtype]
->print_replacement_options($wrappedquestion, $state, $quizid);
}
function print_question_form_end($question, $submitscript='') {
global $QUIZ_QTYPES;
$wrappedquestion = &$state->options->question;
return $QUIZ_QTYPES[$wrappedquestion->qtype]
->print_question_form_end($wrappedquestion, $state, $quizid);
}
/*
function convert_to_response_answer_field($questionresponse) {
global $QUIZ_QTYPES;
@@ -64,52 +248,69 @@ class quiz_random_qtype extends quiz_default_questiontype {
return '';
}
function create_response($question, $nameprefix, $questionsinuse) {
// It's for question types like RANDOMSAMATCH and RANDOM that
// the true power of the pattern with this function comes to the surface.
global $CFG;
if (!isset($this->catrandoms[$question->category])) {
//Need to fetch random questions from category $question->category"
$possiblerandomqtypes = "'"
. implode("','", $this->possiblerandomqtypes) . "'";
if ($question->questiontext == "1") {
// recurse into subcategories
$categorylist = quiz_categorylist($question->category);
} else {
$categorylist = $question->category;
}
$this->catrandoms[$question->category] = get_records_sql
("SELECT * FROM {$CFG->prefix}quiz_questions
WHERE category IN ($categorylist)
AND id NOT IN ($questionsinuse)
AND qtype IN ($possiblerandomqtypes)");
$this->catrandoms[$question->category] =
draw_rand_array($this->catrandoms[$question->category],
count($this->catrandoms[$question->category])); // from bug 1889
}
while ($randomquestion =
array_pop($this->catrandoms[$question->category])) {
if (!ereg("(^|,)$randomquestion->id(,|$)", $questionsinuse)) {
/// $randomquestion is not in use and will therefore be used
/// as the randomquestion here...
global $QUIZ_QTYPES;
$response = $QUIZ_QTYPES[$randomquestion->qtype]
->create_response($randomquestion,
quiz_qtype_nameprefix($randomquestion, $nameprefix),
"$questionsinuse,$randomquestion->id");
$response[$nameprefix] = $randomquestion->id;
return $response;
}
}
notify(get_string('toomanyrandom', 'quiz', $question->category));
return array();
}
*/
/*
function print_question_formulation_and_controls($question,
$quiz, $readonly, $answers, $correctanswers, $nameprefix) {
global $QUIZ_QTYPES;
// Get the wrapped question...
if ($actualquestion = $this->get_wrapped_question($question,
$nameprefix)) {
echo '<input type="hidden" name="' . $nameprefix
. '" value="' . $actualquestion->id . '" />';
return $QUIZ_QTYPES[$actualquestion->qtype]
->print_question_formulation_and_controls($actualquestion,
$quiz, $readonly, $answers, $correctanswers,
quiz_qtype_nameprefix($actualquestion, $nameprefix));
} else {
echo '<p>' . get_string('random', 'quiz') . '</p>';
}
}
function get_wrapped_question($question, $nameprefix) {
if (!empty($question->response[$nameprefix])
and $actualquestion = get_record('quiz_questions',
'id', $question->response[$nameprefix])) {
$actualquestion->response = $question->response;
unset($actualquestion->response[$nameprefix]);
$actualquestion->maxgrade = $question->maxgrade;
return $actualquestion;
} else {
return false;
}
}
function grade_response($question, $nameprefix) {
global $QUIZ_QTYPES;
// Get the wrapped question...
if ($actualquestion = $this->get_wrapped_question($question,
$nameprefix)) {
return $QUIZ_QTYPES[$actualquestion->qtype]->grade_response(
$actualquestion,
quiz_qtype_nameprefix($actualquestion, $nameprefix));
} else {
$result->grade = 0.0;
$result->answers = array();
$result->correctanswers = array();
return $result;
}
}
*/
function extract_response($rawresponse, $nameprefix) {
global $QUIZ_QTYPES;
@@ -137,8 +338,8 @@ class quiz_random_qtype extends quiz_default_questiontype {
/// function is now able to handle both types of response record.
/*** Pick random question id from the answer field in a way that ***/
/*** works for both formats: ***/
// Pick random question id from the answer field in a way that
/// works for both formats:
if (!ereg('^(random)?([0-9]+)(-(.*))?$', $rawresponse->answer, $answerregs)) {
error("The answer value '$rawresponse->answer' for the response with "
."id=$rawresponse->id to the random question "
@@ -151,8 +352,8 @@ class quiz_random_qtype extends quiz_default_questiontype {
'id', $randomquestionid)) {
if ($answerregs[1] && $answerregs[3]) {
/**** The raw response is formatted according to
/**** Moodle version 1.5 or later ****/
// The raw response is formatted according to
// Moodle version 1.5 or later
$randomresponse = $rawresponse;
$randomresponse->question = $randomquestionid;
$randomresponse->answer = $answerregs[4];
@@ -160,22 +361,19 @@ class quiz_random_qtype extends quiz_default_questiontype {
} else if ($randomresponse = get_record
('quiz_responses', 'question', $rawresponse->answer,
'attempt', $rawresponse->attempt)) {
/*** The response was stored by an older version of Moodle */
/*** :-) ***/
// The response was stored by an older version of Moodle
// :-)
} else {
notify("Error: Cannot find response to random question $randomquestionid");
unset($randomresponse);
}
if (isset($randomresponse)) {
/// The prefered case:
/// There is a random question and a response field, from
/// There is a random question and a response field, from
/// which the response array can be extracted:
$response = $QUIZ_QTYPES[$randomquestion->qtype]
->extract_response($randomresponse,
quiz_qtype_nameprefix($randomquestion, $nameprefix));
} else {
@@ -191,7 +389,8 @@ class quiz_random_qtype extends quiz_default_questiontype {
// good chances to execute properly anyway.)
}
$response[$nameprefix] = $randomquestionid;
return $response;
//return $response;
return '';
} else {
notify("Error: Unable to find random question $rawresponse->question");
/// No new random question is picked as this is probably
@@ -199,54 +398,6 @@ class quiz_random_qtype extends quiz_default_questiontype {
return array();
}
}
function print_question_formulation_and_controls($question,
$quiz, $readonly, $answers, $correctanswers, $nameprefix) {
global $QUIZ_QTYPES;
// Get the wrapped question...
if ($actualquestion = $this->get_wrapped_question($question,
$nameprefix)) {
echo '<input type="hidden" name="' . $nameprefix
. '" value="' . $actualquestion->id . '" />';
return $QUIZ_QTYPES[$actualquestion->qtype]
->print_question_formulation_and_controls($actualquestion,
$quiz, $readonly, $answers, $correctanswers,
quiz_qtype_nameprefix($actualquestion, $nameprefix));
} else {
echo '<p>' . get_string('random', 'quiz') . '</p>';
}
}
function get_wrapped_question($question, $nameprefix) {
if (!empty($question->response[$nameprefix])
and $actualquestion = get_record('quiz_questions',
'id', $question->response[$nameprefix])) {
$actualquestion->response = $question->response;
unset($actualquestion->response[$nameprefix]);
$actualquestion->maxgrade = $question->maxgrade;
return $actualquestion;
} else {
return false;
}
}
function grade_response($question, $nameprefix) {
global $QUIZ_QTYPES;
// Get the wrapped question...
if ($actualquestion = $this->get_wrapped_question($question,
$nameprefix)) {
return $QUIZ_QTYPES[$actualquestion->qtype]->grade_response(
$actualquestion,
quiz_qtype_nameprefix($actualquestion, $nameprefix));
} else {
$result->grade = 0.0;
$result->answers = array();
$result->correctanswers = array();
return $result;
}
}
}
//// END OF CLASS ////
@@ -4,7 +4,9 @@
} else {
$options->choose = "";
}
$numberavailable = count_records("quiz_questions", "category", $category->id, "qtype", SHORTANSWER);
$saquestions = $QUIZ_QTYPES[RANDOMSAMATCH]->get_sa_candidates($category->id);
$numberavailable = count($saquestions);
unset($saquestions);
print_heading_with_help(get_string("editingrandomsamatch", "quiz"), "randomsamatch", "quiz");
require("$CFG->dirroot/mod/quiz/questiontypes/randomsamatch/randomsamatch.html");
@@ -13,18 +13,34 @@
class quiz_randomsamatch_qtype extends quiz_match_qtype {
/// Extends MATCH as there are quite a few simularities...
// $catrandoms carries question ids for shortanswer questions
// available as random questios.
// They are sorted by category.
var $catrandoms = array();
function name() {
return 'randomsamatch';
}
function get_question_options(&$question) {
if (!$question->options->choose = get_field('quiz_randomsamatch',
'choose', 'question', $question->id)) {
notify('Error: Missing question options!');
return false;
}
// This could be included as a flag in the database. It's already
// supported by the code.
// Recurse subcategories: 0 = no recursion, 1 = recursion
$question->options->subcats = 1;
return true;
}
function save_question_options($question) {
$options->question = $question->id;
$options->choose = $question->choose;
if (2 > $question->choose) {
$result->error = "At least two shortanswer questions need to be chosen!";
return $result;
}
if ($existing = get_record("quiz_randomsamatch",
"question", $options->question)) {
$options->id = $existing->id;
@@ -40,66 +56,117 @@ class quiz_randomsamatch_qtype extends quiz_match_qtype {
}
return true;
}
function wrapped_questions($question) {
if (empty($question->response)) {
return false;
} else {
$wrapped = '';
$delimiter = '';
foreach ($question->response as $rkey => $response) {
$wrapped .= $delimiter.$this->extract_response_id($rkey);
$delimiter = ',';
}
return $wrapped;
function create_session_and_responses(&$question, &$state, $quiz, $attempt) {
// Choose a random shortanswer question from the category:
// We need to make sure that no question is used more than once in the
// quiz. Therfore the following need to be excluded:
// 1. All questions that are explicitly assigned to the quiz
// 2. All random questions
// 3. All questions that are already chosen by an other random question
global $QUIZ_QTYPES;
if (!isset($quiz->questionsinuse)) {
$quiz->questionsinuse = $quiz->questions;
}
if ($question->options->subcats) {
// recurse into subcategories
$categorylist = quiz_categorylist($question->category);
} else {
$categorylist = $question->category;
}
$saquestions = $this->get_sa_candidates($categorylist, $quiz->questionsinuse);
$count = count($saquestions);
$wanted = $question->options->choose;
if ($count < $wanted && isteacherinanycourse()) {
notify("Error: could not get enough Short-Answer questions!
Got $count Short-Answer questions, but wanted $wanted.
Reducing number to choose from to $count!");
$question->options->choose = $count;
}
$saquestions =
draw_rand_array($saquestions, $question->options->choose); // from bug 1889
foreach ($saquestions as $key => $wrappedquestion) {
if (!$QUIZ_QTYPES[$wrappedquestion->qtype]
->get_question_options($wrappedquestion)) {
return false;
}
// Now we overwrite the $question->options->answers field to only
// *one* (the first) correct answer. This loop can be deleted to
// take all answers into account (i.e. put them all into the
// drop-down menu.
$foundcorrect = false;
foreach ($wrappedquestion->options->answers as $answer) {
if ($foundcorrect || $answer->fraction != 1.0) {
unset($wrappedquestion->options->answers[$answer->id]);
} else if (!$foundcorrect) {
$foundcorrect = true;
}
}
if (!$QUIZ_QTYPES[$wrappedquestion->qtype]
->create_session_and_responses($wrappedquestion, $state, $quiz,
$attempt)) {
return false;
}
$wrappedquestion->name_prefix = $question->name_prefix;
$wrappedquestion->maxgrade = $question->maxgrade;
$quiz->questionsinuse .= ",$wrappedquestion->id";
$state->options->subquestions[$key] = clone($wrappedquestion);
}
// Shuffle the answers if required
$subquestionids = array_values(array_map(create_function('$val',
'return $val->id;'), $state->options->subquestions));
if ($quiz->shuffleanswers) {
$subquestionids = swapshuffle($subquestionids);
}
$state->options->order = $subquestionids;
// Create empty responses
foreach ($subquestionids as $val) {
$state->responses[$val] = '';
}
return true;
}
function create_response($question, $nameprefix, $questionsinuse) {
// It's for question types like RANDOMSAMATCH and RANDOM that
// the true power of the pattern with this function comes to the surface.
// This implementation will stand even after a possible exclusion of
// the funtions extract_response and convert_to_response_answer_field
function restore_session_and_responses(&$question, &$state) {
global $QUIZ_QTYPES;
$responses = explode(',', $state->responses['']);
$responses = array_map(create_function('$val',
'return explode("-", $val);'), $responses);
if (!isset($this->catrandoms[$question->category])) {
/// Need to fetch the shortanswer question ids for the category:
$saquestions = get_records_select('quiz_questions',
" category='$question->category'
AND qtype='".SHORTANSWER."'
AND id NOT IN ($questionsinuse) ");
$this->catrandoms[$question->category] = array_keys($saquestions);
shuffle($this->catrandoms[$question->category]);
}
/// Access question options to find out how many short-answer
/// questions we are supposed to pick...
if ($options = get_record('quiz_randomsamatch',
'question', $question->id)) {
$questionstopick = $options->choose;
} else {
notify("Error: Missing question options! - Try to pick two shortanswer questions anyway");
$questionstopick = 2;
}
/// Pick the short-answer question ids and create the $response array
$response = array();
while ($questionstopick) {
$said = array_pop($this->catrandoms[$question->category]);
if (!ereg("(^|,)$said(,|$)", $questionsinuse)) {
$response[$nameprefix.$said] = '0';
--$questionstopick;
// Restore the previous responses
$state->responses = array();
foreach ($responses as $response) {
$state->responses[$response[0]] = $response[1];
$state->options->order[] = $response[0];
if (!$wrappedquestion = get_record('quiz_questions', 'id',
$response[0])) {
notify("Couldn't get question (id=$response[0])!");
return false;
}
}
if (!$QUIZ_QTYPES[$wrappedquestion->qtype]
->get_question_options($wrappedquestion)) {
notify("Couldn't get question options (id=$response[0])!");
return false;
}
if (!$QUIZ_QTYPES[$wrappedquestion->qtype]
->restore_session_and_responses($wrappedquestion, $state)) {
notify("Couldn't restore session of question (id=$response[0])!");
return false;
}
$wrappedquestion->name_prefix = $question->name_prefix;
$wrappedquestion->maxgrade = $question->maxgrade;
if ($questionstopick) {
notify("Error: could not get enough Short-Answer questions!");
$count = count($response);
$wanted = $count + $questionstopick;
notify("Got $count Short-Answer questions, but wanted $wanted.");
$state->options->subquestions[$wrappedquestion->id] =
clone($wrappedquestion);
}
return $response;
return true;
}
function extract_response($rawresponse, $nameprefix) {
@@ -115,112 +182,16 @@ class quiz_randomsamatch_qtype extends quiz_match_qtype {
return $response;
}
function print_question_formulation_and_controls($question,
$quiz, $readonly, $answers, $correctanswers, $nameprefix) {
// Print question formulation
echo format_text($question->questiontext,
$question->questiontextformat, NULL, $quiz->course);
quiz_print_possible_question_image($quiz->id, $question);
// Summarize shortanswer questions answer alternatives:
if (empty($correctanswers)) {
// Get them using the grade_response method
$tempresult = $this->grade_response($question, $nameprefix);
$saanswers = $tempresult->correctanswers;
} else {
$saanswers = $correctanswers;
}
foreach ($saanswers as $key => $saanswer) {
unset($saanswers[$key]); // Unsets the nameprefix occurence
$saanswers[$saanswer->id] = trim($saanswer->answer);
}
$saanswers = draw_rand_array($saanswers, count($saanswers));
// Print the shortanswer questions and input controls:
echo '<table border="0" cellpadding="10">';
foreach ($question->response as $inputname => $response) {
if (!($saquestion = get_record('quiz_questions', 'id',
quiz_extract_posted_id($inputname, $nameprefix)))) {
notify("Error: cannot find shortanswer question for $inputname ");
continue;
}
echo '<tr><td align="left" valign="top">';
echo $saquestion->questiontext;
echo '</td>';
echo '<td align="right" valign="top">';
if (!empty($correctanswers)
&& $correctanswers[$inputname]->id == $response) {
echo '<span="highlight">';
choose_from_menu($saanswers, $inputname, $response);
echo '</span><br />';
} else {
choose_from_menu($saanswers, $inputname, $response);
if ($readonly && $quiz->correctanswers
&& isset($correctanswer[$inputname])) {
quiz_print_correctanswer($correctanswer[$inputname]->answer);
}
}
if ($quiz->feedback && isset($answers[$inputname])
&& $answers[$inputname]->feedback) {
quiz_print_comment($answers[$inputname]->feedback);
}
echo '</td></tr>';
}
echo '</table>';
}
function grade_response($question, $nameprefix) {
global $QUIZ_QTYPES;
$result->answers = array();
$result->correctanswers = array();
$result->grade = 0.0;
foreach ($question->response as $inputname => $subresponse) {
if ($subquestion = get_record('quiz_questions',
'id', quiz_extract_posted_id($inputname, $nameprefix),
// These two query conditions are security checks that prevents cheating...
'qtype', SHORTANSWER,
'category', $question->category)) {
if ($subresponse = get_record('quiz_answers',
'id', $subresponse)) {
$subquestion->response[$inputname] = $subresponse->answer;
} else {
$subquestion->response[$inputname] = '';
}
// Use the shortanswer framework to for grading...
$subresult = $QUIZ_QTYPES[SHORTANSWER]
->grade_response($subquestion, $inputname);
// Summarize shortanswer results
if (isset($subresult->answers[$inputname])) {
$result->answers[$inputname] =
$subresult->answers[$inputname];
$result->grade += $result->answers[$inputname]->fraction;
if ($result->answers[$inputname]->fraction >= 1.0) {
$result->correctanswers[$inputname] =
$result->answers[$inputname];
continue;
}
}
// Pick the first correctanswer:
foreach ($subresult->correctanswers as $correct) {
$result->correctanswers[$inputname] = $correct;
break;
}
}
}
if ($result->grade) {
$result->grade /= count($question->response);
}
return $result;
function get_sa_candidates($categorylist, $questionsinuse=0) {
return get_records_select('quiz_questions',
"qtype = '".SHORTANSWER."' " .
"AND category IN ($categorylist) " .
"AND parent = '0' " .
"AND hidden = '0'" .
"AND id NOT IN ($questionsinuse)");
}
}
//// END OF CLASS ////
//////////////////////////////////////////////////////////////////////////
@@ -0,0 +1,20 @@
<?php // $Id$
require_once($CFG->dirroot . '/mod/quiz/questiontypes/rqp/lib.php');
$types = quiz_rqp_get_types();
print_heading_with_help(get_string('editingrqp', 'quiz'), 'rqp', 'quiz');
if (empty($question->id)) {
$question->options->type = '';
$question->options->source = '';
$question->options->format = '';
}
else if (!$QUIZ_QTYPES[$question->qtype]->get_question_options($question)) {
$question->options->type = '';
$question->options->source = '';
$question->options->format = '';
echo "<p align=\"center\">Error! Could not load the options for this question!</p>\n";
}
require('rqp.html');
?>
+192
View File
@@ -0,0 +1,192 @@
<?php
require_once('../../../../config.php');
require_once('lib.php');
require_once('remote.php');
// Check user admin
require_login();
if (!isadmin()) {
error('You need to be an admin user to use this page.', $CFG->wwwroot . '/login/index.php');
}
if (!$site = get_site()) {
error('Site isn\'t defined!');
}
$sesskey = required_param('sesskey', PARAM_RAW);
if (!confirm_sesskey($sesskey)) {
error(get_string('confirmsesskeybad', 'error'));
}
$type->id = optional_param('id', NULL, PARAM_INT);
$type->name = optional_param('name', NULL, PARAM_RAW);
if (is_null($type->name)) {
$delete = optional_param('delete', NULL, PARAM_INT);
if (!is_null($delete)) {
$type->id = $delete;
$confirm = optional_param('confirm', NULL, PARAM_RAW);
}
}
else {
$delete = NULL;
$type->rendering_server = required_param('rendering_server', PARAM_RAW);
$type->cloning_server = required_param('cloning_server', PARAM_RAW);
}
// Print the header
$stradmin = get_string('admin');
$strconfiguration = get_string('configuration');
$strmanagemodules = get_string('managemodules');
$strmodulename = get_string('modulename', 'quiz');
$stritemtypes = get_string('itemtypes', 'quiz');
$streditingitemtype = get_string(is_null($type->id) ? 'addingitemtype' : 'editingitemtype', 'quiz');
$navigation = '<a href="' . s($CFG->wwwroot) . '/' . s($CFG->admin) . '/index.php">' . $stradmin . '</a> -> ' .
'<a href="' . s($CFG->wwwroot) . '/' . s($CFG->admin) . '/configure.php">' . $strconfiguration . '</a> -> ' .
'<a href="' . s($CFG->wwwroot) . '/' . s($CFG->admin) . '/modules.php">' . $strmanagemodules . '</a> -> ' .
'<a href="' . s($CFG->wwwroot) . '/' . s($CFG->admin) . '/module.php?module=quiz&amp;sesskey=' . s(rawurlencode($sesskey)) . '">' . $strmodulename . '</a> -> ' .
'<a href="types.php?sesskey=' . s(rawurlencode($sesskey)) . '">' . $stritemtypes . '</a> -> ' .
$streditingitemtype;
print_header($site->shortname . ': ' . $strmodulename . ': ' . $stritemtypes . ': ' . $streditingitemtype, $site->fullname, $navigation, '', '', true, '', '');
print_heading($streditingitemtype);
$err = array();
// Save the type
while (!is_null($type->name)) { // using like if but with support for break
// Check basic settings
if (empty($type->name)) {
$err['name'] = get_string('missingitemtypename', 'quiz');
break;
}
if (empty($type->rendering_server)) {
$err['rendering_server'] = get_string('missingrenderingserver', 'quiz');
break;
}
// Check servers exist and work
$servers = remote_server_info($type, true, !empty($type->cloning_server));
if (false === $servers['rendering']) {
$err['rendering_server'] = get_string('renderingserverconnectfailed', 'quiz', $type->rendering_server);
break;
}
if (!empty($type->cloning_server)) {
if (false === $servers['cloning']) {
$err['cloning_server'] = get_string('cloningserverconnectfailed', 'quiz', $type->cloning_server);
break;
}
}
if (!$servers['rendering']->rendering) {
$err['rendering_server'] = get_string('renderingserverdoesnt', 'quiz', $type->rendering_server);
break;
}
if (!empty($type->cloning_server)) {
if (!$servers['cloning']->cloning) {
if ($type->cloning_server !== $type->rendering_server) {
$err['cloning_server'] = get_string('cloningserverdoesnt', 'quiz', $type->cloning_server);
break;
}
if (!$servers['rendering']->implicitCloning) {
$err['cloning_server'] = get_string('cloningrenderingserverdoesnt', 'quiz', $type->cloning_server);
break;
}
}
}
// Set the server specific flags
$type->flags = 0;
$type->flags |= $servers['rendering']->itemCaching ? REMOTE_ITEM_CACHE : 0;
if (!empty($type->cloning_server)) {
$type->flags |= $servers['cloning']->cloning ? REMOTE_CLONING : 0;
if ($type->cloning_server === $type->rendering_server) {
$type->flags |= $servers['rendering']->implicitCloning ? REMOTE_IMPLICIT_CLONING : 0;
}
$type->flags |= $servers['cloning']->templateCaching ? REMOTE_TEMPLATE_CACHE : 0;
}
// Save the type in the database
if (!quiz_rqp_save_type($type)) {
error('Could not save the item type', 'types.php?sesskey=' . s(rawurlencode($sesskey)));
}
redirect('types.php?sesskey=' . rawurlencode($sesskey), get_string('success'));
// error and redirect both call exit so we never go round the loop
}
if (is_null($type->name)) {
// Load the type
if (is_null($type->id)) {
$type->name = '';
$type->rendering_server = '';
$type->cloning_server = '';
} else {
if (! ($type = get_record('quiz_rqp_type', 'id', $type->id))) {
error('Could not load the item type', 'types.php?sesskey=' . s(rawurlencode($sesskey)));
}
}
}
// Display the delete form
if (!is_null($delete)) {
if (md5($delete) === $confirm) {
if (!quiz_rqp_delete_type($delete)) {
error('Could not delete the item type', 'types.php?sesskey=' . s(rawurlencode($sesskey)));
}
redirect('types.php?sesskey=' . rawurlencode($sesskey), get_string('success'));
}
notice_yesno(get_string('deleteitemtypecheck', 'quiz', $type->name), 'edittype.php?delete=' . s(rawurlencode($delete)) . '&amp;confirm=' . s(rawurlencode(md5($delete))) . '&amp;sesskey=' . s(rawurlencode($sesskey)), 'types.php?sesskey=' . s(rawurlencode($sesskey)));
print_footer();
exit;
}
?>
<form name="theform" method="post" action="edittype.php">
<center>
<table cellpadding="5">
<tr valign="top">
<td align="right"><b><?php print_string('name'); ?>:</b>
</td>
<td>
<input type="text" name="name" size="50" value="<?php p($type->name); ?>" />
<?php
if (isset($err['name'])) {
formerr($err['name']);
}
?>
</td>
</tr>
<tr valign="top">
<td align="right"><b><?php print_string('renderingserver', 'quiz'); ?>:</b>
</td>
<td>
<input type="text" name="rendering_server" size="50" value="<?php p($type->rendering_server); ?>" />
<?php
if (isset($err['rendering_server'])) {
formerr($err['rendering_server']);
}
?>
</td>
</tr>
<tr valign="top">
<td align="right"><b><?php print_string('cloningserver', 'quiz'); ?>:</b>
</td>
<td>
<input type="text" name="cloning_server" size="50" value="<?php p($type->cloning_server); ?>" />
<?php
if (isset($err['cloning_server'])) {
formerr($err['cloning_server']);
}
?>
</td>
</tr>
</table>
<input type="hidden" name="id" value="<?php p($type->id); ?>" />
<input type="hidden" name="sesskey" value="<?php p($sesskey); ?>" />
<input type="submit" value="<?php print_string('savechanges'); ?>" />
</center>
</form>
<?php
print_footer();
?>
Binary file not shown.

After

Width:  |  Height:  |  Size: 125 B

+113
View File
@@ -0,0 +1,113 @@
<?php // $Id$
/**
* Library of functions used by the RQP question type
*
* @version $Id$
* @author Alex Smith and other members of the Serving Mathematics project
* {@link http://maths.york.ac.uk/serving_maths}
* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
* @package quiz
*/
function quiz_rqp_get_types() {
return get_records('quiz_rqp_type', '', '', 'name ASC');
}
function quiz_rqp_save_type($type) {
if (empty($type->id)) {
return insert_record('quiz_rqp_type', $type, false);
}
return update_record('quiz_rqp_type', $type);
}
function quiz_rqp_delete_type($id) {
return delete_records('quiz_rqp_type', 'id', $id);
}
/**
* Creates a colon separated list of space separated values from an associative
* array of arrays
*
* An associative array of values or an associative array of arrays is imploded
* to a string by creating a colon separated list of space separated values. The
* key is treated as the first value. The {@link quiz_rqp_explode} function can
* restore the array from this string representation.
* @return string The string representation of the array. This is a colon
* separated list of space separated values.
* @param array $array An associative array of single values or an associative
* array of arrays to be imploded.
*/
function quiz_rqp_implode($array) {
if (count($array) < 1) {
return '';
}
$str = '';
foreach ($array as $key => $val) {
$str .= $key . ' ';
if (is_array($val)) {
if (count($val) > 0) {
foreach ($val as $subval) {
$str .= $subval . ' ';
}
// Remove the trailing space
$str = substr($str, 0, -1);
}
} else {
$str .= $val;
}
$str .= ':';
}
// Remove the trailing semi-colon
return substr($str, 0, -1);
}
/**
* Recreates an associative array or an associative array of arrays from the
* string representation
*
* Takes a colon separated list of space separated values as produced by
* {@link quiz_rqp_implode} and recreates the array. If an array of single values
* is expected then an error results if an element has more than one value.
* Otherwise every value is an array.
* @return array The associative array restored from the string. Every
* element is a single value if $multi is false or an array
* if $multi is true.
* @param string $str The string to explode. This is a colon separated list of
* space separated values.
* @param boolean $multi Flag indicating if the values in the array are expected
* to be of multiple cardinality (i.e. an array of arrays
* is expected) or single values (i.e. an array of values).
* The default is false indicating an array of single
* values is expected.
*/
function quiz_rqp_explode($str, $multi=false) {
// Explode by colon
if ($str === '') {
return array();
}
$array = explode(':', $str);
$n = count($array);
$return = array();
for ($i = 0; $i < $n; $i++) {
// Explode by space
$array[$i] = explode(' ', $array[$i]);
// Get the key
$key = array_shift($array[$i]);
if (array_key_exists($key, $return)) {
// Element appears twice!
return false;
}
// Save the element
if ($multi) {
$return[$key] = $array[$i];
} else if (count($array[$i]) > 1) {
return false;
} else {
$return[$key] = $array[$i][0];
}
}
return $return;
}
?>
File diff suppressed because it is too large Load Diff
+460
View File
@@ -0,0 +1,460 @@
<?php // $Id$
/**
* This file defines the RQP question type class
*
* @version $Id$
* @author Alex Smith and other members of the Serving Mathematics project
* {@link http://maths.york.ac.uk/serving_maths}
* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
* @package quiz
*/
require_once($CFG->dirroot . '/mod/quiz/questiontypes/rqp/lib.php');
require_once('remote.php');
/**
* RQP question type class
*/
class quiz_rqp_qtype extends quiz_default_questiontype {
/**
* Name of the rqp question type
*
* @ return string 'rqp'
*/
function name() {
return 'rqp';
}
/**
* Save the type-specific options
*
* This also saves additional information that it receives from
* an RQP_itemInformation call to the RQP server
*/
function save_question_options($form) {
global $CFG;
// Check source type
if (!$type = get_record('quiz_rqp_type', 'id', $form->type)) {
$result->notice = get_string('invalidsourcetype', 'quiz');
return $result;
}
// Create the object to be stored in quiz_rqp table
$options = new object;
$options->question = $form->id;
$options->type = $form->type;
$options->type_name = $type->name;
$options->source = $form->source;
$options->format = $form->format;
$options->rendering_server = $type->rendering_server;
$options->cloning_server = $type->cloning_server;
// Check source file
$item = remote_item_info($options);
if (false === $item || is_soap_fault($item)) {
$result->notice = get_string('invalidsource', 'quiz', $options);
return $result;
}
if ($item->sourceErrors) {
array_walk($item->sourceErrors,
create_function('&$val', '$val = $val->message;'));
$result->notice = get_string('invalidsource', 'quiz', $options) .
'<br />' . implode('<br />', array_values($item->sourceErrors));
return $result;
}
// Time dependent items are not supported by the quiz module yet
if ($item->timeDependent) {
$result->noticeyesno = get_string('notimedependentitems', 'quiz');
return $result;
}
// Set the format and item specific flags
$options->format = $item->format;
$options->maxscore = $item->maxScore;
$options->flags = 0;
$options->flags |= $item->template ? REMOTE_TEMPLATE : 0;
$options->flags |= $item->adaptive ? REMOTE_ADAPTIVE : 0;
// Check for cloning support if item is a template
if ($item->template && !($type->flags & REMOTE_TEMPLATES_SUPPORTED)) {
$result->notice = get_string('itemrequirescloning', 'quiz');
return $result;
}
// Save the options
if ($old = get_record('quiz_rqp', 'question', $form->id)) {
$old->type = $options->type;
$old->source = $options->source;
$old->format = $options->format;
$old->flags = $options->flags;
$old->maxscore = $options->maxscore;
if (!update_record('quiz_rqp', $old)) {
$result->error = "Could not update quiz rqp options! (id=$old->id)";
return $result;
}
} else {
if (!insert_record('quiz_rqp', $options)) {
$result->error = 'Could not insert quiz rqp options!';
return $result;
}
}
return true;
}
/**
* Loads the question type specific options for the question.
*
* This function loads all question type specific options for the
* question from the database into the $question->options field.
* @return bool Indicates success or failure.
* @param object $question The question object for the question.
*/
function get_question_options(&$question) {
$options =& $question->options;
if (! ($options = get_record('quiz_rqp', 'question', $question->id))) {
return false;
}
if (!$type = get_record('quiz_rqp_type', 'id', $options->type)) {
return false;
}
$options->type_name = $type->name;
$options->rendering_server = $type->rendering_server;
$options->cloning_server = $type->cloning_server;
$options->flags &= $type->flags;
return true;
}
/**
* Return a value or array of values which will give full marks if graded as
* the $state->responses field
*
* The correct answers are obtained from the RQP server via the
* RQP_SessionInformation operation
* @return mixed An array of values giving the responses corresponding
* to the (or a) correct answer to the question.
* @param object $question The question for which the correct answer is to
* be retrieved.
* @param object $state The state object that corresponds to the question,
* for which a correct answer is needed.
*/
function get_correct_responses(&$question, &$state) {
$info = remote_session_info($question, $state);
if (false === $info || is_soap_fault($info)) {
return null;
}
return $info->correctResponses;
}
/**
* Creates empty session and response information for the question
*
* This function is called to start a question session. Empty question type
* specific session data and empty response data is added to the state object.
* @return bool Indicates success or failure.
* @param object $question The question for which the session is to be created.
* @param object $state The state to create the session for. This is passed by
* reference and will be updated.
* @param object $quiz The quiz for which the session is to be started.
* (not used)
* @param object $attempt The quiz attempt for which the session is to be
* started. (not used)
*/
function create_session_and_responses(&$question, &$state, $quiz, $attempt) {
$state->responses = array('' => '');
$state->options->persistent_data = '';
$state->options->template_vars = array();
return true;
}
/**
* Restores the session data and most recent responses for the given state
*
* This function loads any session data associated with the question session
* in the given state from the quiz_rqp_states table into the state object.
* @return bool Indicates success or failure.
* @param object $question The question object for the question including any
* question type specific information.
* @param object $state The saved state to load the session for. This
* object is updated to include the question
* type specific session information and responses
* (it is passed by reference).
*/
function restore_session_and_responses(&$question, &$state) {
if (!$options = get_record('quiz_rqp_states', 'stateid', $state->id)) {
return false;
}
$state->responses = quiz_rqp_explode($options->responses);
$state->options->persistent_data = $options->persistent_data;
$state->options->template_vars =
quiz_rqp_explode($options->template_vars, true);
return true;
}
/**
* Saves the session data and responses for the question in a new state
*
* This function saves all session data from the state object into the
* quiz_rqp_states table
* @return bool Indicates success or failure.
* @param object $question The question object for the question including
* the question type specific information.
* @param object $state The state for which the question type specific
* data and responses should be saved.
*/
function save_session_and_responses(&$question, &$state) {
$options->stateid = $state->id;
$options->responses = quiz_rqp_implode($state->responses);
$options->persistent_data = $state->options->persistent_data;
$options->template_vars =
quiz_rqp_implode($state->options->template_vars);
if (!insert_record('quiz_rqp_states', $options)) {
return false;
}
return true;
}
/**
* Prints the main content of the question including any interactions
*
* This function prints the main content of the question which it obtains
* from the RQP server via the Render operation. It also updates
* $state->options->persistent_data and $state->options->template_vars
* with the values returned by the RQP server.
* @param object $question The question to be rendered.
* @param object $state The state to render the question in. The grading
* information is in ->grade, ->raw_grade and
* ->penalty. The current responses are in
* ->responses. The last graded state is in
* ->last_graded (hence the most recently graded
* responses are in ->last_graded->responses).
* @param object $quiz The quiz to which the question belongs.
* @param object $options An object describing the rendering options.
* The fields are:
* ->readonly Review / interactive mode
* ->feedback Show feedback for the graded
* responses
* ->validation Show how the current responses
* responses were interpreted
* ->correct_responses Show solutions
* These are all boolean values.
*/
function print_question_formulation_and_controls(&$question, &$state,
$quiz, $options) {
// Use the render output created during grading if it exists
if (isset($state->options->renderoutput)) {
$output =& $state->options->renderoutput;
} else {
// Otherwise perform a render operation
$output = remote_render($question, $state, false,
$options->readonly ? 'readonly' : 'normal');
if (false === $output || is_soap_fault($output)) {
unset($output);
notify('Error: Rendering failed!');
exit;
}
}
$state->options->persistent_data = $output->persistentData;
$state->options->template_vars = $output->templateVars;
// Print the head (this may not work, really it should be in the head
// section of the html document but moodle doesn't allow question types
// to put things there)
if (isset($output->output[RQP_URI_COMPONENT . 'head'])) {
echo $output->output[RQP_URI_COMPONENT . 'head']->output;
}
// Print the title
if (isset($output->output[RQP_URI_COMPONENT . 'title'])) {
echo '<h2>' . $output->output[RQP_URI_COMPONENT . 'title']->output
. "</h2>\n";
}
// Print the stem
if (isset($output->output[RQP_URI_COMPONENT . 'stem'])) {
echo '<div class="RQPstem">';
echo $output->output[RQP_URI_COMPONENT . 'stem']->output;
echo '</div>';
}
// Print the interactions
if (isset($output->output[RQP_URI_COMPONENT . 'interactions'])) {
echo '<div class="RQPinteractions" align="right">';
echo $output->output[RQP_URI_COMPONENT . 'interactions']->output;
echo '</div>';
}
// Print the last answer
if (isset($output->output[RQP_URI_COMPONENT . 'lastAnswer'])) {
echo '<div class="RQPlastAnswer">';
echo $output->output[RQP_URI_COMPONENT . 'lastAnswer']->output;
echo '</div>';
}
// Print the validation when required
if ($options->validation) {
if (isset($output->output[RQP_URI_COMPONENT . 'validation'])) {
echo '<div class="RQPvalidation">';
echo $output->output[RQP_URI_COMPONENT . 'validation']->output;
echo '</div>';
}
}
// Print the feedback when required
if ($options->feedback) {
if (isset($output->output[RQP_URI_COMPONENT . 'feedback'])) {
echo '<div class="RQPfeedback">';
echo $output->output[RQP_URI_COMPONENT . 'feedback']->output;
echo '</div>';
}
}
// Print the solution when required
if ($options->correct_responses) {
if (isset($output->output[RQP_URI_COMPONENT . 'solution'])) {
echo $output->output[RQP_URI_COMPONENT . 'solution']->output;
echo '</div>';
}
}
// Note: hint(s) and modal feedback are ignored; moodle does not support
// them yet.
// Remove the render output created during grading (if any)
unset($state->options->renderoutput);
}
/**
* Prints the submit and validate buttons
* @param object $question The question for which the buttons are to be printed
* @param object $state The state the question is in (not used)
* @param object $quiz The quiz to which the question belongs.
* @param object $options An object describing the rendering options.
* (not used. This function should only have been called
* if the options were such that the buttons are required)
*/
function print_question_submit_buttons(&$question, &$state, $quiz, $options) {
echo '<input type="submit" name="';
echo $question->name_prefix;
echo 'validate" value="';
print_string('validate', 'quiz');
echo '" />&nbsp;';
if ($quiz->optionflags & QUIZ_ADAPTIVE) {
echo '<input type="submit" name="';
echo $question->name_prefix;
echo 'mark" value="';
print_string('mark', 'quiz');
echo '" />';
}
}
/**
* Renders the question for printing and returns the LaTeX source produced
*
* This function calls the RQP_Render operation to render the question
* suitable for a printed problem or solution sheet in LaTeX and returns
* the rendered output. It does not advance the state of the question session.
* @return string The LaTeX output.
* @param object $question The question to be rendered.
* @param object $state The state to render the question in.
* @param object $quiz The quiz to which the question belongs.
* @param string $type Indicates if the question or the solution is to be
* rendered. Possible values are 'question' and
* 'solution'.
*/
/*function get_texsource(&$question, &$state, $quiz, $type) {
// Perform the RQP render operation requesting tex output
$output = remote_render($question, $state, 'print', RQP_URI_STATE .
('solution' === $type ? 'solution' : 'normal'));
if (false === $output || is_soap_fault($output)) {
notify('Error: Rendering failed!');
exit;
}
if (isset($output->bodyParts[RQP_URI_FORMAT . 'text/latex2e-parts/body'])) {
$output = $output->bodyParts[RQP_URI_FORMAT . 'text/latex2e-parts/body']->content;
}
else {
$output = '';
}
return $output;
}*/
/**
* Performs response processing and grading
*
* This function calls RQP_Render to perform response processing and grading
* and updates the state accordingly. It also caches the rendering output in case
* it is needed later.
* @return boolean Indicates success or failure.
* @param object $question The question to be graded.
* @param object $state The state to render the question in. The grading
* information is in ->grade, ->raw_grade and
* ->penalty. The currently responses are in
* ->responses. The last graded state is in
* ->last_graded (hence the most recently graded
* responses are in ->last_graded->responses). The
* question type specific information is also
* included. The ->raw_grade and ->penalty fields
* are updated. The ->grade field is computed
* automatically. The cumulative penalty is set
* in ->penalty by adding to the penalty from the
* most recently graded state. The method is able to
* close the question session (preventing any further
* attempts at this question) by setting
* $state->event to QUIZ_EVENTCLOSE.
* @param object $quiz The quiz to which the question belongs.
*/
function grade_responses(&$question, &$state, $quiz) {
// Perform the grading and rendering
$output = remote_render($question, $state, QUIZ_EVENTGRADE == $state->event
|| QUIZ_EVENTCLOSE == $state->event, 'normal');
if (false === $output || is_soap_fault($output)) {
unset($output);
return false;
}
$state->options->persistent_data = $output->persistentData;
$state->options->template_vars = $output->templateVars;
// Save the rendering results for later
$state->options->renderoutput = $output;
if (isset($output->outcomeVars[RQP_URI_OUTCOME . 'rawScore'])) {
$state->raw_grade = (float) $output->outcomeVars[RQP_URI_OUTCOME .
'rawScore'][0];
if (isset($output->outcomeVars[RQP_URI_OUTCOME . 'penalty'])) {
$state->penalty = (float) $output->outcomeVars[RQP_URI_OUTCOME .
'penalty'][0];
} else {
$state->penalty = 0;
}
} else if (isset($output->outcomeVars[RQP_URI_OUTCOME . 'grade'])) {
// This won't work quite as we would like but it is the best we can
// do given that the server won't tell us the information we need
$state->raw_grade = (float) $output->outcomeVars[RQP_URI_OUTCOME .
'grade'][0];
$state->penalty = 0;
} else {
$state->raw_grade = 0;
$state->penalty = 0;
}
$state->raw_grade = ($state->raw_grade * ((float) $question->maxgrade))
/ ((float) $question->options->maxscore);
return true;
}
/**
* Includes configuration settings for the question type on the quiz admin
* page
*
* Returns an array of objects describing the options for the question type
* to be included on the quiz module admin page.
* This is currently only a link to the server setup page types.php
* @return array Array of objects describing the configuration options to
* be included on the quiz module admin page.
*/
function get_config_options() {
global $CFG;
$link->name = 'managetypes';
$link->link = 'types.php';
return array($link);
}
}
//////////////////////////////////////////////////////////////////////////
//// INITIATION - Without this line the question type is not in use... ///
//////////////////////////////////////////////////////////////////////////
$QUIZ_QTYPES[RQP]= new quiz_rqp_qtype();
?>
+205
View File
@@ -0,0 +1,205 @@
<?php // $Id$
/**
* Remote question processing interface (using RQP)
*
* @version $Id$
* @author Alex Smith and others members of the Serving Mathematics project
* {@link http://maths.york.ac.uk/serving_maths}
* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
* @package quiz
*/
// Load functions for the RQP-SOAP binding
require_once('rqp.php');
// Remote item processing flags (cached from server)
define('REMOTE_CLONING', 1);
define('REMOTE_IMPLICIT_CLONING', 2);
define('REMOTE_TEMPLATES_SUPPORTED', 3);
define('REMOTE_TEMPLATE', 4);
define('REMOTE_ADAPTIVE', 8);
// Global connection variable
global $remote_connections;
$remote_connections = array();
/**
* Create connection to rendering server if it does not already exist
*
* @param object $options This holds in particular the URL of the rendering
* server in $options->rendering_server
* @return boolean Indicates success or failure
*/
function remote_rendering_connect($options) {
global $remote_connections;
if (!array_key_exists($options->rendering_server, $remote_connections)) {
$remote_connections[$options->rendering_server] =
rqp_connect($options->rendering_server);
}
if (!$remote_connections[$options->rendering_server]) {
unset($remote_connections[$options->rendering_server]);
return false;
}
return true;
}
/**
* Create connection to cloning server if it does not already exist
*
* @param object $options This holds in particular the URL of the rendering
* server in $options->cloning_server
* @return boolean Indicates success or failure
*/
function remote_cloning_connect($options) {
global $remote_connections;
if (!array_key_exists($options->cloning_server, $remote_connections)) {
$remote_connections[$options->cloning_server] =
rqp_connect($options->cloning_server);
}
if (!$remote_connections[$options->cloning_server]) {
unset($remote_connections[$options->cloning_server]);
return false;
}
return true;
}
function remote_server_info($options, $rendering_server=true,
$cloning_server=true) {
global $remote_connections;
$return = array();
if ($rendering_server) {
if (remote_rendering_connect($options)) {
$return['rendering'] =
rqp_server_info($remote_connections[$options->rendering_server]);
if (is_soap_fault($return['rendering'])) {
$return['rendering'] = false;
}
}
else {
$return['rendering'] = false;
}
}
if ($cloning_server) {
if (remote_cloning_connect($options)) {
$return['cloning'] =
rqp_server_info($remote_connections[$options->cloning_server]);
if (is_soap_fault($return['cloning'])) {
$return['cloning'] = false;
}
}
else {
$return['cloning'] = false;
}
}
return $return;
}
function remote_item_info(&$options, $allinpackage=false) {
global $remote_connections;
if (!remote_rendering_connect($options)) {
return false;
}
if ($allinpackage) {
$items = rqp_item_info($remote_connections[$options->rendering_server],
$options->source, $options->format, 0);
if ($items->length > 0) {
$items = array($items);
for ($i = 1; $i < $items[0]->length; $i++) {
$items[$i] =
rqp_item_info($remote_connections[$options->rendering_server],
$options->source, $options->format, $i);
}
}
return $items;
}
return rqp_item_info($remote_connections[$options->rendering_server],
$options->source, $options->format, 0);
}
/**
* Perform a remote rendering operation on the RQP question
*
* @param string $output One of 'normal', 'readonly' or 'print'.
*/
function remote_render(&$question, &$state, $advanceState=false,
$output='normal', $from=0, $to=0) {
global $remote_connections;
// Make the code more readable
$options =& $question->options;
// Add prefix to response variable names
$responses = array();
foreach ($state->responses as $key => $resp) {
$responses[$question->name_prefix . $key] = $resp;
}
// Prepare the render format
if ('print' === $output) {
$renderFormat = RQP_URI_FORMAT . 'latex-2e';
} else if ('readonly' === $output) {
$renderFormat = RQP_URI_FORMAT . 'xhtml-1.0-print';
} else {
$renderFormat = RQP_URI_FORMAT . 'xhtml-1.0-web';
}
// Perform the RQP operation
if (!remote_rendering_connect($options)) {
return false;
}
$output = array();
for ($i = $from; $i <= $to; $i++) {
$output[$i] = rqp_render($remote_connections[$options->rendering_server],
$options->source, $options->format, $state->options->persistent_data,
$question->name_prefix, $responses, $advanceState,
$renderFormat, $state->options->template_vars, 0, '', '', '', $i);
if (false === $output[$i] || is_soap_fault($output[$i])) {
return $output[$i];
}
}
if ($from == $to) {
return $output[$from];
}
return $output;
}
function remote_session_info(&$question, &$state, $from=0, $to=0) {
global $remote_connections;
// Make the code more readable
$options =& $question->options;
// Perform the RQP operation
if (!remote_rendering_connect($options)) {
return false;
}
$info = array();
for ($i = $from; $i <= $to; $i++) {
$info[$i] = rqp_session_info($remote_connections[$options->rendering_server],
$options->source, $options->format, $state->options->persistent_data,
$state->options->template_vars, 0, '', $i);
if (false === $info[$i] || is_soap_fault($info[$i])) {
return $info[$i];
}
}
if ($from == $to) {
return $info[$from];
}
return $info;
}
function remote_clone($options, $vars=array()) {
global $remote_connections;
if (!remote_cloning_connect($options)) {
return false;
}
return rqp_clone($remote_connections[$options->cloning_server],
$options->source, $options->format, $vars);
}
?>
+83
View File
@@ -0,0 +1,83 @@
<?php
if (empty($types)) { // display an error if no types have been defined
echo '<p><b>' . get_string('notypes', 'quiz') . "</b></p>\n";
if (isadmin()) {
echo '<p align="center"><a href="questiontypes/rqp/edittype.php?sesskey='.sesskey().'">'.get_string('additemtype', 'quiz')."</a></p>\n";
}
}
else { // otherwise display the page as normal
?>
<form name="theform" method="post" action="question.php">
<table cellpadding="5">
<tr valign="top">
<td align="right"><b><?php print_string('category', 'quiz'); ?>:</b></td>
<td>
<?php quiz_category_select_menu($course->id, true, true, $question->category); ?>
</td>
</tr>
<tr valign="top">
<td align="right"><b><?php print_string('questionname', 'quiz'); ?>:</b></td>
<td>
<input type="text" name="name" size="50" value="<?php p($question->name); ?>" />
<input type="hidden" name="questiontext" value="" />
<input type="hidden" name="questiontextformat" value="0" />
<input type="hidden" name="image" value="" />
<?php
if (isset($err['name'])) {
formerr($err['name']);
}
?>
</td>
</tr>
<tr valign="top">
<td align="right"><b><?php print_string('itemtype', 'quiz'); ?>:</b>
</td>
<td>
<?php
echo "<select name=\"type\">\n";
foreach ($types as $type) {
echo '<option value="' . s($type->id) . '"' . ($type->id === $question->options->type ? ' selected="selected"' : '') . '>' . s($type->name) . "</option>\n";
}
echo "</select>\n";
if (isset($err['type'])) {
formerr($err['type']);
}
?>
</td>
</tr>
<tr valign="top">
<td align="right"><b><?php print_string('itemsource', 'quiz'); ?>:</b>
</td>
<td>
<?php
print_textarea(false, 20, 60, 630, 300, "source", $question->options->source);
echo "<br />\n";
if (isset($err['source'])) {
formerr($err['source']);
}
?>
</td>
</tr>
<tr valign="top">
<td align="right"><b><?php print_string('itemsourceformat', 'quiz'); ?></b>
</td>
<td><input name="format" type="text" size="50" maxLength="255" value="<?php p($question->options->format); ?>" />
<?php
if (isset($err['format'])) {
formerr($err['format']);
}
?>
</td>
</tr>
<?php
$QUIZ_QTYPES[$question->qtype]->print_replacement_options($question, $course, $contextquiz);
$QUIZ_QTYPES[$question->qtype]->print_question_form_end($question);
} // end of else block for when some types have been defined
?>
</table>
</form>
+277
View File
@@ -0,0 +1,277 @@
<?php // $Id$
/**
* Library of functions binding RQP to SOAP
*
* @version $Id$
* @author Alex Smith and others members of the Serving Mathematics project
* {@link http://maths.york.ac.uk/serving_maths}
* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
* @package quiz
*/
// Load the SOAP library that gives a unified wrapping to either the native
// PHP5 SOAP extension if available or to nuSOAP otherwise.
require_once('uni_soap.php');
/**
* Base RQP URI for RQP-defined identifiers
*
* RQP defines standard URIs for common values of the parameters. Currently
* there is no RQP domain so we define a base URI here so that it can be
* changed later.
*/
define('RQP_URI_BASE', 'http://rqp.org/');
/**
* RQP parameter URIs
*
* RQP defines standard URIs for common values of the parameters. These are
* defined in several categories under different directories under the base
* URI.
*/
define('RQP_URI_ERROR', RQP_URI_BASE . 'errors/');
define('RQP_URI_FORMAT', RQP_URI_BASE . 'formats/');
define('RQP_URI_OUTCOME', RQP_URI_BASE . 'outcomes/');
define('RQP_URI_COMPONENT', RQP_URI_BASE . 'components/');
/**
* Start a SOAP connection
*
* @param string $server The URL of the RQP server that we want to connect to
* @return mixed Returns a SoapClient object if connection is successful
* or false in the case of a soap fault.
*/
function rqp_connect($server) {
$connection = soap_connect($server . '?wsdl');
if (is_soap_fault($connection)) {
return false;
}
return $connection;
}
/**
* Get server information using the RQP_ServerInformation operation
*
* @param SoapClient $connection The URL of the RQP server that we want to connect to
* @return object Object holding the return parameters or a SoapFault.
*/
function rqp_server_info($connection) {
return soap_call($connection, 'RQP_ServerInformation', array());
}
/**
* Get item information using the RQP_ItemInformation operation
*
* @param SoapClient $connection The URL of the RQP server that we want to connect to
* @param string $source Item source
* @param anyURI $format Item format
* @return object Object holding the return parameters or a SoapFault.
*/
function rqp_item_info($connection, $source, $format='', $index=0) {
$itemInfo = soap_call($connection, 'RQP_ItemInformation',
array('source'=>$source, 'format'=>$format, 'index'=>$index));
if (is_soap_fault($itemInfo)) {
return $itemInfo;
}
// put the sourceErrors into an associative array indexed by the
// error identifier
$sourceErrors = array();
if (!empty($itemInfo->sourceErrors)) {
foreach ($itemInfo->sourceErrors as $error) {
$id = $error->identifier;
unset($error->identifier);
$sourceErrors[$id] = (object) $error;
}
}
$itemInfo->sourceErrors = $sourceErrors;
return $itemInfo;
}
/**
* Process an item template to produce template variables using the RQP_ProcessTemplate operation
*
* @param SoapClient $connection The URL of the RQP server that we want to connect to
* @param string $source Item source
* @param anyURI $format Item format
* @return object Object holding the return parameters or a SoapFault.
*/
function rqp_process_template($connection, $source, $format='') {
$return = soap_call($connection, 'RQP_ProcessTemplate',
array('source'=>$source, 'format'=>$format));
if (is_soap_fault($return)) {
return $return;
}
$templateVars = array();
if (!empty($return->templateVars)) {
foreach ($return->templateVars as $var) {
$templateVars[$var->identifier] = $var->values;
}
}
$return->templateVars = $templateVars;
return $return;
}
/**
* Clone an item template using the RQP_ProcessTemplate operation
*
* @param SoapClient $connection The URL of the RQP server that we want to connect to
* @param string $source Item source
* @param anyURI $format Item format
* @param array $templateVars Associative array of template variables
* @return object Object holding the return parameters or a SoapFault.
*/
function rqp_clone($connection, $source, $format='', $templateVars=array()) {
// make an array of key-value pairs from the template variables array
array_walk($templateVars, create_function('&$val, $key',
'$val = (object) array(\'identifier\'=>$key, \'values\'=>$val);'));
$return = soap_call($connection, 'RQP_Clone', array('source'=>$source,
'format'=>$format, 'templateVars'=>array_values($templateVars)));
if (is_soap_fault($return)) {
return $return;
}
$templateVars = array();
if (!empty($return->templateVars)) {
foreach ($return->templateVars as $var) {
$templateVars[$var->identifier] = $var->values;
}
}
$return->templateVars = $templateVars;
return $return;
}
/**
* Get runtime information about the item in the given state using the
* RQP_SessionInformation operation
*
* @param SoapClient $connection The URL of the RQP server that we want to connect to
* @param string $source Item source
* @param anyURI $format Item format
* @param string $persistentData String giving the state of the item session
* @param array $templateVars Associative array of template variables
* @param string $embedPrefix
* @return object Object holding the return parameters or a SoapFault.
*/
function rqp_session_info($connection, $source, $format='', $persistentData='',
$templateVars=array(), $embedPrefix='', $index=0) {
// make an array of key-value pairs from the template variables array
array_walk($templateVars, create_function('&$val, $key',
'$val = (object) array(\'identifier\'=>$key, \'values\'=>$val);'));
$return = soap_call($connection, 'RQP_SessionInformation',
array('source'=>$source, 'format'=>$format, 'index'=>$index,
'templateVars'=>array_values($templateVars),
'persistentData'=>$persistentData, 'embedPrefix'=>$embedPrefix));
if (is_soap_fault($return)) {
return $return;
}
$templateVars = array();
if (!empty($return->templateVars)) {
foreach ($return->templateVars as $var) {
$templateVars[$var->identifier] = $var->values;
}
}
$return->templateVars = $templateVars;
$responses = array();
if (!empty($return->correctResponses)) {
foreach ($return->correctResponses as $var) {
$responses[$var->name] = $var->value;
}
}
$return->correctResponses = $responses;
return $return;
}
/**
* Process and render the item in the given state using the RQP_Render operation
*
* @param SoapClient $connection The URL of the RQP server that we want to connect to
* @param string $source Item source
* @param anyURI $format Item format
* @param string $persistentData String giving the state of the item session
* @param string $embedPrefix
* @param array $responses Student responses from the form elements
* @param boolean $advanceState Should the item be advanced to the next state?
* @param anyURI $renderFormat
* @param array $templateVars Associative array of template variables
* @param anyURI $mediaBase
* @param anyURI $appletBase
* @param anyURI $modalFormat
* @return object Object holding the return parameters or a SoapFault.
*/
function rqp_render($connection, $source, $format='', $persistentData='',
$embedPrefix='', $responses=array(), $advanceState=true, $renderFormat='',
$templateVars=array(), $mediaBase='', $appletBase='',
$modalFormat='', $index=0) {
// make an array of key-value pairs from the template vars array
array_walk($templateVars, create_function('&$val, $key',
'$val = (object) array(\'identifier\'=>$key, \'values\'=>$val);'));
// make an array of name-value pairs from the responses array
array_walk($responses, create_function('&$val, $key',
'$val = (object) array(\'name\'=>$key, \'value\'=>$val);'));
$return = soap_call($connection, 'RQP_Render', array('source'=>$source,
'format'=>$format, 'index'=>$index, 'templateVars'=>array_values($templateVars),
'persistentData'=>$persistentData, 'responses'=>$responses,
'advanceState'=>$advanceState, 'embedPrefix'=>$embedPrefix,
'appletBase'=>$appletBase, 'mediaBase'=>$mediaBase,
'renderFormat'=>$renderFormat, 'modalFormat'=>$modalFormat));
if (is_soap_fault($return)) {
return $return;
}
$outcomeVars = array();
if (!empty($return->outcomeVars)) {
foreach ($return->outcomeVars as $var) {
$outcomeVars[$var->identifier] = $var->values;
}
}
$return->outcomeVars = $outcomeVars;
$templateVars = array();
if (!empty($return->templateVars)) {
foreach ($return->templateVars as $var) {
$templateVars[$var->identifier] = $var->values;
}
}
$return->templateVars = $templateVars;
$output = array();
if (!empty($return->output)) {
foreach ($return->output as $out) {
$id = $out->identifier;
unset($out->identifier);
$output[$id] = $out;
}
}
$return->output = $output;
return $return;
}
/**
* Call to the RQP_Author operation
*
* @param SoapClient $connection The URL of the RQP server
* @param string $source Item source
* @param anyURI $format Item format
* @param string $persistentData String giving the state of the authoring session
* @param string $embedPrefix
* @param array $responses Teacher responses from the form elements
* @param anyURI $renderFormat
* @return object Object holding the return parameters or a SoapFault.
*/
function rqp_author($connection, $source, $format='', $persistentData='',
$embedPrefix='', $responses=array(), $renderFormat='') {
// make an array of name-value pairs from the responses array
array_walk($responses, create_function('&$val, $key',
'$val = (object) array(\'name\'=>$key, \'value\'=>$val);'));
return soap_call($connection, 'RQP_Author', array('source'=>$source,
'format'=>$format, 'persistentData'=>$persistentData, 'responses'=>$responses,
'embedPrefix'=>$embedPrefix, 'renderFormat'=>$renderFormat));
}
?>
+73
View File
@@ -0,0 +1,73 @@
<?php
// This page lists all the available RQP question types
require_once('../../../../config.php');
require_once('lib.php');
// Check user admin
require_login();
if (!isadmin()) {
error('You need to be an admin user to use this page.', $CFG->wwwroot . '/login/index.php');
}
if (!$site = get_site()) {
error('Site isn\'t defined!');
}
$sesskey = required_param('sesskey', PARAM_RAW);
if (!confirm_sesskey($sesskey)) {
error(get_string('confirmsesskeybad', 'error'));
}
// Print the header
$stradmin = get_string('admin');
$strconfiguration = get_string('configuration');
$strmanagemodules = get_string('managemodules');
$strmodulename = get_string('modulename', 'quiz');
$stritemtypes = get_string('itemtypes', 'quiz');
$navigation = '<a href="' . s($CFG->wwwroot) . '/' . s($CFG->admin) . '/index.php">' . $stradmin . '</a> -> ' .
'<a href="' . s($CFG->wwwroot) . '/' . s($CFG->admin) . '/configure.php">' . $strconfiguration . '</a> -> ' .
'<a href="' . s($CFG->wwwroot) . '/' . s($CFG->admin) . '/modules.php">' . $strmanagemodules . '</a> -> ' .
'<a href="' . s($CFG->wwwroot) . '/' . s($CFG->admin) . '/module.php?module=quiz&amp;sesskey=' . s(rawurlencode($sesskey)) . '">' . $strmodulename . '</a> -> ' .
$stritemtypes;
print_header($site->shortname . ': ' . $strmodulename . ': ' . $stritemtypes, $site->fullname, $navigation, '', '', true, '', '');
// Get list of types
$types = quiz_rqp_get_types();
// Setup the table
$strname = get_string('name');
$strservers = get_string('servers', 'quiz');
$strcapabilities = get_string('capabilities', 'quiz');
$straction = get_string('action');
$stredit = get_string('edit');
$strdelete = get_string('delete');
$table->head = array($strname, $strservers, $strcapabilities, $straction);
$table->align = array('left', 'left', 'left', 'left');
$table->size = array('*', '*', '*', '*');
if ($types) {
foreach ($types as $type) {
$link = '<a href="edittype.php?id=' . s(rawurlencode($type->id)) . '&amp;sesskey=' . s(rawurlencode($sesskey)) . '">' . s($type->name) . '</a>';
$servers = get_string('rendering', 'quiz') . ': ' . s($type->rendering_server);
if ($type->cloning_server) {
$servers .= "<br />\n" . get_string('cloning', 'quiz') . ': ' . s($type->cloning_server);
}
$capabilities = '';
$actions = '<a title="' . $stredit . '" href="edittype.php?id=' . s(rawurlencode($type->id)) . '&amp;sesskey=' . s(rawurlencode($sesskey)) . '"><img src="../../../../pix/t/edit.gif" border="0" alt="' . $stredit . '" /></a>&nbsp;<a title="' . $strdelete . '" href="edittype.php?delete=' . s(rawurlencode($type->id)) . '&amp;sesskey=' . s(rawurlencode($sesskey)) . '"><img src="../../../../pix/t/delete.gif" border="0" alt="' . $strdelete . '" /></a>';
$table->data[] = array($link, $servers, $capabilities, $actions);
}
}
// Print the table
print_heading($stritemtypes);
print_table($table);
// Print add link
echo '<p align="center"><a href="edittype.php?sesskey=' . s(rawurlencode($sesskey)) . '">' . get_string('additemtype', 'quiz') . "</a></p>\n";
// Finish the page
print_footer();
?>
+113
View File
@@ -0,0 +1,113 @@
<?php
// Web services wrapper library script
if (class_exists('SoapClient')) {
// Use the native PHP5 support
require_once('uni_soap.php5');
}
else{
// Use nuSOAP instead
require_once('nusoap.php');
function make_soap_fault($faultcode, $faultstring, $faultactor='', $detail='', $faultname='', $headerfault='') {
return new soap_fault($faultcode, $faultactor, $faultstring, $detail);
}
function is_soap_fault($obj) {
if (!is_object($obj))
return false;
return (strcasecmp(get_class($obj), 'soap_fault') === 0);
}
if (class_exists('soap_client')) {
function soap_connect($wsdl, $trace=false) {
return new soap_client($wsdl, 'wsdl');
}
}
else {
function soap_connect($wsdl, $trace=false) {
return new soapclient($wsdl, 'wsdl');
}
}
function soap_call($connection, $call, $params) {
$result = $connection->call($call, $params);
if ($connection->fault) {
return @make_soap_fault($result['faultcode'], $result['faultstring'], '', $result['detail']);
}
if ($connection->error_str) {
return @make_soap_fault('server', $connection->error_str, '', $connection->response);
}
/* Fix objects being returned as associative arrays (to fit with PHP5
SOAP support */
return fix_object($result);
}
function soap_serve($wsdl, $functions) {
global $HTTP_RAW_POST_DATA;
$s = new soap_server($wsdl);
$s->service(isset($HTTP_RAW_POST_DATA) ? $HTTP_RAW_POST_DATA : '');
}
function get_last_soap_messages($connection) {
return array('request'=>$connection->request, 'response'=>$connection->response);
}
/* Fix objects being returned as associative arrays (to fit with PHP5
SOAP support */
function fix_object($value) {
if (is_array($value)) {
$value = array_map('fix_object', $value);
$keys = array_keys($value);
/* check for arrays of length 1 (they get given the key "item"
rather than 0 by nusoap) */
if (1 === count($value) && 'item' === $keys[0]) {
$value = array_values($value);
}
else {
/* cast to object if it is an associative array with at least
one string key */
foreach ($keys as $key) {
if (is_string($key)) {
$value = (object) $value;
break;
}
}
}
}
return $value;
}
// Fix simple type encoding - not needed for nuSOAP
function soap_encode($value, $name, $type, $namespace, $encode=0) {
return $value;
}
// Fix complex type encoding - not needed for nuSOAP
function soap_encode_object($value, $name, $type, $namespace) {
return $value;
}
// Fix array encoding - not needed for nuSOAP
function soap_encode_array($value, $name, $type, $namespace) {
return $value;
}
}
// In both cases...
function handle_soap_wsdl_request($wsdlfile, $address=false) {
header('Content-type: application/wsdl+xml');
$wsdl = file_get_contents($wsdlfile);
if (false !== $address) {
if (true === $address) {
$address = (($_SERVER['SERVER_PORT'] == 443) ? 'https://' : 'http://') . $_SERVER['SERVER_NAME'] . $_SERVER['SCRIPT_NAME'];
}
$wsdl = str_replace('###SERVER_ADDRESS###', $address, $wsdl);
}
echo $wsdl;
exit;
}
?>
+118
View File
@@ -0,0 +1,118 @@
<?php // $Id$
/**
* Library of SOAP functions wrapping the PHP5 SOAP functions
*
* The purpose of this wrapping is to make it easier to use alternative SOAP
* implementations like nuSOAP in case the PHP5 SOAP extension is not available
* @version $Id$
* @author Alex Smith and others members of the Serving Mathematics project
* {@link http://maths.york.ac.uk/serving_maths}
* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
* @package quiz
*/
/**
* Create a new SoapClient object
*
* @param string $wsdl URI of the WSDL file
* @param boolean $trace indicates if the soap messages should be saved (i.e. if
* get_soap_messages is used) and should be used only for debugging
* @return mixed Returns either a SoapClient object or, if the connection failed,
* a SoapFault object.
*/
function soap_connect($wsdl, $trace=false) {
try {
$connection = new SoapClient($wsdl, array('soap_version'=>SOAP_1_1, 'exceptions'=>true, 'trace'=>$trace));
}
catch (SoapFault $f) {
$connection = $f;
}
catch (Exception $e) {
$connection = new SoapFault('client', 'Could not connect to the service');
}
return $connection;
}
/**
* Make a call to a SoapClient
*
* @param SoapClient $connection The SoapClient to call
* @param string $call Operation to be performed by client
* @param array $params Parameters for the call
* @return mixed The return parameters of the operation or a SoapFault
* If the operation returned several parameters then these
* are returned as an object rather than an array
*/
function soap_call($connection, $call, $params) {
try {
$return = $connection->__soapCall($call, $params);
}
catch (SoapFault $f) {
$return = $f;
}
catch (Exception $e) {
$return = new SoapFault('client', 'Could call the method');
}
// return multiple parameters using an object rather than an array
if (is_array($return)) {
$keys = array_keys($return);
$assoc = true;
foreach ($keys as $key) {
if (!is_string($key)) {
$assoc = false;
break;
}
}
if ($assoc)
$return = (object) $return;
}
return $return;
}
function soap_serve($wsdl, $functions) {
// create server object
$s = new SoapServer($wsdl);
// export functions
foreach ($functions as $func)
$s->addFunction($func);
// handle the request
$s->handle();
}
function make_soap_fault($faultcode, $faultstring, $faultactor='', $detail='', $faultname='', $headerfault='') {
return new SoapFault($faultcode, $faultstring, $faultactor, $detail, $faultname, $headerfault);
}
function get_last_soap_messages($connection) {
return array('request'=>$connection->__getLastRequest(), 'response'=>$connection->__getLastResponse());
}
// Fix simple type encoding - work around a bug in PHP
function soap_encode($value, $name, $type, $namespace, $encode=XSD_STRING) {
$value = new SoapVar($value, $encode, $type, $namespace);
if ('' === $name)
return $value;
return new SoapParam($value, $name);
}
// Fix complex type encoding - work around a bug in PHP
function soap_encode_object($value, $name, $type, $namespace) {
if (!is_object($value))
return $value;
$value = new SoapVar($value, SOAP_ENC_OBJECT, $type, $namespace);
if ('' === $name)
return $value;
return new SoapParam($value, $name);
}
// Fix array encoding - work around a bug in PHP
function soap_encode_array($value, $name, $type, $namespace) {
if (!is_array($value))
return $value;
$value = new SoapVar($value, SOAP_ENC_ARRAY, 'ArrayOf' . $type, $namespace);
if ('' === $name)
return $value;
return new SoapParam($value, $name);
}
?>
@@ -1,204 +1,229 @@
<?php // $Id$
///////////////////
/// SHORTANSWER ///
///////////////////
/// QUESTION TYPE CLASS //////////////////
///
/// This class contains some special features in order to make the
/// question type embeddable within a multianswer (cloze) question
///
class quiz_shortanswer_qtype extends quiz_default_questiontype {
function get_answers($question, $addedcondition='') {
// The added condition is one addition that has been added
// to the behaviour of this question type in order to make
// it embeddable within a multianswer (embedded cloze) question
global $CFG;
// There can be multiple answers
return get_records_sql("SELECT a.*, sa.usecase
FROM {$CFG->prefix}quiz_shortanswer sa,
{$CFG->prefix}quiz_answers a
WHERE sa.question = '$question->id'
AND sa.question = a.question "
. $addedcondition);
}
function name() {
return 'shortanswer';
}
function save_question_options($question) {
if (!$oldanswers = get_records("quiz_answers", "question", $question->id, "id ASC")) {
$oldanswers = array();
}
$answers = array();
$maxfraction = -1;
// Insert all the new answers
foreach ($question->answer as $key => $dataanswer) {
if ($dataanswer != "") {
if ($oldanswer = array_shift($oldanswers)) { // Existing answer, so reuse it
$answer = $oldanswer;
$answer->answer = trim($dataanswer);
$answer->fraction = $question->fraction[$key];
$answer->feedback = $question->feedback[$key];
if (!update_record("quiz_answers", $answer)) {
$result->error = "Could not update quiz answer! (id=$answer->id)";
return $result;
}
} else { // This is a completely new answer
unset($answer);
$answer->answer = trim($dataanswer);
$answer->question = $question->id;
$answer->fraction = $question->fraction[$key];
$answer->feedback = $question->feedback[$key];
if (!$answer->id = insert_record("quiz_answers", $answer)) {
$result->error = "Could not insert quiz answer!";
return $result;
}
}
$answers[] = $answer->id;
if ($question->fraction[$key] > $maxfraction) {
$maxfraction = $question->fraction[$key];
}
}
}
if ($options = get_record("quiz_shortanswer", "question", $question->id)) {
$options->answers = implode(",",$answers);
$options->usecase = $question->usecase;
if (!update_record("quiz_shortanswer", $options)) {
$result->error = "Could not update quiz shortanswer options! (id=$options->id)";
return $result;
}
} else {
unset($options);
$options->question = $question->id;
$options->answers = implode(",",$answers);
$options->usecase = $question->usecase;
if (!insert_record("quiz_shortanswer", $options)) {
$result->error = "Could not insert quiz shortanswer options!";
return $result;
}
}
/// Perform sanity checks on fractional grades
if ($maxfraction != 1) {
$maxfraction = $maxfraction * 100;
$result->noticeyesno = get_string("fractionsnomax", "quiz", $maxfraction);
return $result;
} else {
return true;
}
}
function print_question_formulation_and_controls($question,
$quiz, $readonly, $answers, $correctanswers, $nameprefix) {
/// This implementation is also used by question type NUMERICAL
/// Print question text and media
echo format_text($question->questiontext,
$question->questiontextformat,
NULL, $quiz->course);
quiz_print_possible_question_image($quiz->id, $question);
/// Print input controls
$stranswer = get_string("answer", "quiz");
if (isset($question->response[$nameprefix])) {
$value = ' value="'.htmlSpecialChars($question->response[$nameprefix]).'" ';
} else {
$value = ' value="" ';
}
$inputname = ' name="'.$nameprefix.'" ';
echo "<p align=\"right\">$stranswer: <input type=\"text\" $readonly $inputname size=\"80\" $value /></p>";
if ($quiz->feedback && isset($answers[$nameprefix])
&& $feedback = $answers[$nameprefix]->feedback) {
quiz_print_comment($feedback);
}
if ($readonly && $quiz->correctanswers) {
$delimiter = '';
$correct = '';
foreach ($correctanswers as $correctanswer) {
$correct .= $delimiter.$correctanswer->answer;
$delimiter = ', ';
}
quiz_print_correctanswer($correct);
}
}
function grade_response($question, $nameprefix, $addedanswercondition='') {
if (isset($question->response[$nameprefix])) {
$response0 = trim(stripslashes($question->response[$nameprefix]));
} else {
$response0 = '';
}
$answers = $this->get_answers($question, $addedanswercondition);
/// Determine ->answers[]
$result->answers = array();
if ('' !== $response0) {
/// These are things to protect in the strings when wildcards are used
$search = array('\\', '+', '(', ')', '[', ']', '-');
$replace = array('\\\\', '\+', '\(', '\)', '\[', '\]', '\-');
foreach ($answers as $answer) {
$answer->answer = trim($answer->answer); // Just in case
if (empty($result->answers) || $answer->fraction
> $result->answers[$nameprefix]->fraction) {
if (!$answer->usecase) { // Don't compare case
$response0 = strtolower($response0);
$answer0 = strtolower($answer->answer);
} else {
$answer0 = $answer->answer;
}
if (strpos(' '.$answer0, '*')) {
$answer0 = str_replace('\*','@@@@@@',$answer0);
$answer0 = str_replace('*','.*',$answer0);
$answer0 = str_replace($search, $replace, $answer0);
$answer0 = str_replace('@@@@@@', '\*',$answer0);
if (ereg('^'.$answer0.'$', $response0)) {
$result->answers[$nameprefix] = $answer;
}
} else if ($answer0 == $response0) {
$result->answers[$nameprefix] = $answer;
}
}
}
}
$result->grade = isset($result->answers[$nameprefix])
? $result->answers[$nameprefix]->fraction
: 0.0;
$result->correctanswers = quiz_extract_correctanswers($answers,
$nameprefix);
return $result;
}
}
//// END OF CLASS ////
//////////////////////////////////////////////////////////////////////////
//// INITIATION - Without this line the question type is not in use... ///
//////////////////////////////////////////////////////////////////////////
$QUIZ_QTYPES[SHORTANSWER]= new quiz_shortanswer_qtype();
?>
<?php // $Id$
///////////////////
/// SHORTANSWER ///
///////////////////
/// QUESTION TYPE CLASS //////////////////
///
/// This class contains some special features in order to make the
/// question type embeddable within a multianswer (cloze) question
///
class quiz_shortanswer_qtype extends quiz_default_questiontype {
function name() {
return 'shortanswer';
}
function get_question_options(&$question) {
// Get additional information from database
// and attach it to the question object
if (!$question->options = get_record('quiz_shortanswer', 'question', $question->id)) {
notify('Error: Missing question options!');
return false;
}
if (!$question->options->answers = get_records('quiz_answers', 'question',
$question->id)) {
notify('Error: Missing question answers!');
return false;
}
return true;
}
function save_question_options($question) {
if (!$oldanswers = get_records("quiz_answers", "question", $question->id, "id ASC")) {
$oldanswers = array();
}
$answers = array();
$maxfraction = -1;
// Insert all the new answers
foreach ($question->answer as $key => $dataanswer) {
if ($dataanswer != "") {
if ($oldanswer = array_shift($oldanswers)) { // Existing answer, so reuse it
$answer = $oldanswer;
$answer->answer = trim($dataanswer);
$answer->fraction = $question->fraction[$key];
$answer->feedback = $question->feedback[$key];
if (!update_record("quiz_answers", $answer)) {
$result->error = "Could not update quiz answer! (id=$answer->id)";
return $result;
}
} else { // This is a completely new answer
unset($answer);
$answer->answer = trim($dataanswer);
$answer->question = $question->id;
$answer->fraction = $question->fraction[$key];
$answer->feedback = $question->feedback[$key];
if (!$answer->id = insert_record("quiz_answers", $answer)) {
$result->error = "Could not insert quiz answer!";
return $result;
}
}
$answers[] = $answer->id;
if ($question->fraction[$key] > $maxfraction) {
$maxfraction = $question->fraction[$key];
}
}
}
if ($options = get_record("quiz_shortanswer", "question", $question->id)) {
$options->answers = implode(",",$answers);
$options->usecase = $question->usecase;
if (!update_record("quiz_shortanswer", $options)) {
$result->error = "Could not update quiz shortanswer options! (id=$options->id)";
return $result;
}
} else {
unset($options);
$options->question = $question->id;
$options->answers = implode(",",$answers);
$options->usecase = $question->usecase;
if (!insert_record("quiz_shortanswer", $options)) {
$result->error = "Could not insert quiz shortanswer options!";
return $result;
}
}
/// Perform sanity checks on fractional grades
if ($maxfraction != 1) {
$maxfraction = $maxfraction * 100;
$result->noticeyesno = get_string("fractionsnomax", "quiz", $maxfraction);
return $result;
} else {
return true;
}
}
function print_question_formulation_and_controls(&$question, &$state, $quiz, $options) {
/// This implementation is also used by question type NUMERICAL
$answers = &$question->options->answers;
$correctanswers = $this->get_correct_responses($question, $state);
$readonly = empty($options->readonly) ? '' : 'disabled="disabled"';
$nameprefix = $question->name_prefix;
/// Print question text and media
echo format_text($question->questiontext,
$question->questiontextformat,
NULL, $quiz->course);
quiz_print_possible_question_image($quiz->id, $question);
/// Print input controls
$stranswer = get_string("answer", "quiz");
if (isset($state->responses[''])) {
$value = ' value="'.htmlSpecialChars($state->responses['']).'" ';
} else {
$value = ' value="" ';
}
$inputname = ' name="'.$nameprefix.'" ';
echo "<p align=\"right\">$stranswer: <input type=\"text\" $readonly $inputname size=\"80\" $value /></p>";
if ($options->feedback) {
$testedstate = clone($state);
$teststate = clone($state);
foreach($answers as $answer) {
$teststate->responses[''] = trim($answer->answer);
if($this->compare_responses($question, $testedstate, $teststate)) {
quiz_print_comment($answer->feedback);
break;
}
}
}
if ($options->readonly && $options->correct_responses) {
$delimiter = '';
$correct = '';
if ($correctanswers) {
foreach ($correctanswers as $correctanswer) {
$correct .= $delimiter.$correctanswer;
$delimiter = ', ';
}
}
quiz_print_correctanswer($correct);
}
}
function grade_responses(&$question, &$state, $quiz) {
$answers = &$question->options->answers;
$testedstate = clone($state);
$teststate = clone($state);
foreach($answers as $answer) {
$teststate->responses[''] = trim($answer->answer);
if($this->compare_responses($question, $testedstate, $teststate)) {
if (empty($state->raw_grade) && $state->raw_grade < $answer->fraction) {
$state->raw_grade = $answer->fraction;
}
}
}
if (empty($state->raw_grade)) {
$state->raw_grade = 0;
}
// Make sure we don't assign negative or too high marks
$state->raw_grade = min(max((float) $state->raw_grade,
0.0), 1.0) * $question->maxgrade;
$state->penalty = $question->penalty;
// Only allow one attempt at the question
// $state->event = QUIZ_EVENTCLOSE;
return true;
}
function compare_responses(&$question, &$state, &$teststate) {
if (isset($state->responses[''])) {
$response0 = trim(stripslashes($state->responses['']));
} else {
$response0 = '';
}
if (isset($teststate->responses[''])) {
$response1 = trim(stripslashes($teststate->responses['']));
} else {
$response1 = '';
}
if (!$question->options->usecase) { // Don't compare case
$response0 = strtolower($response0);
$response1 = strtolower($response1);
}
/// These are things to protect in the strings when wildcards are used
$search = array('\\', '+', '(', ')', '[', ']', '-');
$replace = array('\\\\', '\+', '\(', '\)', '\[', '\]', '\-');
if (strpos(' '.$response1, '*')) {
$answer0 = str_replace('\*','@@@@@@',$response1);
$answer0 = str_replace('*','.*',$response1);
$answer0 = str_replace($search, $replace, $response1);
$answer0 = str_replace('@@@@@@', '\*',$response1);
if (ereg('^'.$response1.'$', $response0)) {
return true;
}
} else if ($response1 == $response0) {
return true;
}
return false;
}
function print_question_grading_details(&$question, &$state, $quiz, $options) {
// do nothing
}
}
//// END OF CLASS ////
//////////////////////////////////////////////////////////////////////////
//// INITIATION - Without this line the question type is not in use... ///
//////////////////////////////////////////////////////////////////////////
$QUIZ_QTYPES[SHORTANSWER]= new quiz_shortanswer_qtype();
?>
@@ -31,7 +31,7 @@
</td>
<td>
<?php if (isset($err["questiontext"])) {
formerr($err["questiontext"]);
formerr($err["questiontext"]);
echo "<br />";
}
print_textarea($usehtmleditor, 15, 60, 630, 300, "questiontext", $question->questiontext);
@@ -65,7 +65,7 @@
<tr valign="top">
<td align="right"><b><?php print_string("casesensitive", "quiz") ?>:</b></td>
<td>
<?php
<?php
unset($menu);
$menu[0] = get_string("caseno", "quiz");
$menu[1] = get_string("caseyes", "quiz");
@@ -73,13 +73,21 @@
?>
</td>
</tr>
<tr valign="top">
<td align="right"><b><?php print_string("penalty", "quiz") ?>:</b></td>
<td>
<input type="text" name="penalty" size="3" value="<?php p($question->penalty) ?>" alt="<?php print_string("penalty", "quiz") ?>" />
<?php helpbutton('penalty', get_string('penalty', 'quiz'), 'quiz'); ?>
<?php if (isset($err["penalty"])) formerr($err["penalty"]); ?>
</td>
</tr>
<tr valign="top">
<td align="right"><b><?php print_string("correctanswers", "quiz") ?></b>:</td>
<td>
<?php print_string("filloutoneanswer", "quiz") ?>
</td>
</tr>
<?php
<?php
for ($i=1; $i<=QUIZ_MAX_NUMBER_ANSWERS; $i++) {
?>
<tr valign="top">
@@ -115,7 +123,7 @@
<?php
}
?>
<?php
<?php
$QUIZ_QTYPES[$question->qtype]->print_replacement_options($question, $course, $contextquiz);
$QUIZ_QTYPES[$question->qtype]->print_question_form_end($question);
?>
+130 -66
View File
@@ -78,92 +78,156 @@ class quiz_truefalse_qtype extends quiz_default_questiontype {
return true;
}
function print_question_formulation_and_controls($question,
$quiz, $readonly, $answers, $correctanswers, $nameprefix) {
/**
* Loads the question type specific options for the question.
*/
function get_question_options(&$question) {
// Get additional information from database
// and attach it to the question object
if (!$question->options = get_record('quiz_truefalse', 'question',
$question->id)) {
notify('Error: Missing question options!');
return false;
}
// Load possible answers
if (!$answers = get_records('quiz_answers', 'question',
$question->id)) {
notify('Error: Missing question answers!');
return false;
}
$question->options->answers = array(
'true' => $answers[$question->options->trueanswer],
'false' => $answers[$question->options->falseanswer]);
if (!$options = get_record("quiz_truefalse", "question", $question->id)) {
notify("Error: Missing question options!");
}
if (!$true = get_record("quiz_answers", "id", $options->trueanswer)) {
notify("Error: Missing question answers!");
}
if (!$false = get_record("quiz_answers", "id", $options->falseanswer)) {
notify("Error: Missing question answers!");
}
return true;
}
/**
* Prints the main content of the question including any interactions
*/
function print_question_formulation_and_controls(&$question, &$state,
$quiz, $options) {
$answers = &$question->options->answers;
$correctanswers = $this->get_correct_responses($question, $state);
$readonly = $options->readonly ? ' disabled="disabled"' : '';
// Print question formulation
echo format_text($question->questiontext,
$question->questiontextformat,
NULL, $quiz->course);
quiz_print_possible_question_image($quiz->id, $question);
// Print input controls
// Update the answer strings
$stranswer = get_string('answer', 'quiz');
$strlastanswer = get_string('lastanswer', 'quiz');
$stranswer = get_string("answer", "quiz");
if (!$true->answer) {
$true->answer = get_string("true", "quiz");
if (!$answers['true']->answer) {
$answers['true']->answer = get_string('true', 'quiz');
}
if (!$false->answer) {
$false->answer = get_string("false", "quiz");
if (!$answers['false']->answer) {
$answers['false']->answer = get_string('false', 'quiz');
}
$truechecked = "";
$falsechecked = "";
if (!isset($question->response[$nameprefix])) {
$question->response[$nameprefix] = '';
// Work out the selected answer and last marked answer
$selected = '';
$marked = '';
$teststate = clone($state);
$teststate->responses[''] = $answers['true']->answer;
if ($this->compare_responses($question, $state, $teststate)) {
$selected = 'true';
}
if ($true->id == $question->response[$nameprefix]) {
$truechecked = 'checked="checked"';
} else if ($false->id == $question->response[$nameprefix]) {
$falsechecked = 'checked="checked"';
if ($this->compare_responses($question, $state->last_graded,
$teststate)) {
$marked = 'true';
}
$teststate->responses[''] = $answers['false']->answer;
if ($this->compare_responses($question, $state, $teststate)) {
$selected = 'false';
}
if ($this->compare_responses($question, $state->last_graded,
$teststate)) {
$marked = 'false';
}
$truecorrect = "";
$falsecorrect = "";
if ($readonly && $quiz->correctanswers) {
if (!empty($correctanswers[$nameprefix.$true->id])) {
$truecorrect = 'class="highlight"';
}
if (!empty($correctanswers[$nameprefix.$false->id])) {
$falsecorrect = 'class="highlight"';
}
/* Work out the correct answer if feedback or correct responses are
requested */
if ($options->feedback || $options->correct_responses) {
$correctstate = clone($state);
$correctstate->responses[''] = $correctanswers[''];
$correct = '';
if (!is_null($correctstate->responses)) {
$teststate->responses[''] = $answers['true']->answer;
if ($this->compare_responses($question, $correctstate,
$teststate)) {
$correct = 'true';
}
$teststate->responses[''] = $answers['false']->answer;
if ($this->compare_responses($question, $correctstate,
$teststate)) {
$correct = 'false';
}
}
}
$inputname = ' name="'.$nameprefix.'" ';
echo "<table align=\"right\" cellpadding=\"5\"><tr><td align=\"right\">$stranswer:&nbsp;&nbsp;</td>";
echo "<td $truecorrect>";
echo "<input $truechecked type=\"radio\" $readonly $inputname value=\"$true->id\" alt=\"".s($true->answer)."\" />$true->answer";
echo "</td><td $falsecorrect>";
echo "<input $falsechecked type=\"radio\" $readonly $inputname value=\"$false->id\" alt=\"".s($false->answer)."\" />$false->answer";
echo "</td></tr></table><br clear=\"all\" />";// changed from clear=ALL jm
if ($quiz->feedback && isset($answers[$nameprefix])
&& $feedback = $answers[$nameprefix]->feedback) {
quiz_print_comment(
"$feedback");
// Work out which radio button to select (if either)
$truechecked = ('true' === $selected) ? ' checked="checked"' : '';
$falsechecked = ('false' === $selected) ? ' checked="checked"' : '';
// Work out which answer is correct if we need to highlight it
if ($options->correct_responses) {
$truecorrect = ('true' === $correct) ? ' class="highlight"' : '';
$falsecorrect = ('false' === $correct) ? ' class="highlight"' : '';
} else {
$truecorrect = '';
$falsecorrect = '';
}
// Print the controls
$inputname = ' name="'.$question->name_prefix.'" ';
echo '<table align="right" cellpadding="5"><tr><td align="right">';
echo $stranswer . ':&nbsp;&nbsp;</td>';
echo '<td' . $truecorrect . '>';
echo '<input type="radio"' . $truechecked . $readonly . $inputname;
echo 'value="' . $answers['true']->answer . '" alt="';
echo s($answers['true']->answer) . '" />' . s($answers['true']->answer);
echo '</td><td' . $falsecorrect . '>';
echo '<input type="radio"' . $falsechecked . $readonly . $inputname;
echo 'value="' . $answers['false']->answer . '" alt="';
echo s($answers['false']->answer) . '" />';
p($answers['false']->answer);
if (!empty($marked) && (!$options->readonly || $marked !== $selected)) {
/* This should never happen but it is here both for robustness and
to serve as an example for question type authors */
echo '</td></tr><tr><td><font size="1">';
echo $strlastanswer . ':&nbsp;&nbsp;</font></td>';
echo '</td>';
if ('true' === $marked) {
echo '<td><font size="1">' . $answers['true']->answer;
echo '</font></td><td></td>';
} else {
echo '<td></td><td><font size="1">';
echo $answers['false']->answer . '</font></td>';
}
}
echo '</td></tr></table><br clear="all" />';
if ($options->feedback && !empty($marked)) {
quiz_print_comment($answers[$marked]->feedback);
}
}
function grade_response($question, $nameprefix) {
$answers = get_records("quiz_answers", "question", $question->id);
if (isset($question->response[$nameprefix])
&& isset($answers[$question->response[$nameprefix]])) {
$result->answers = array($nameprefix
=> $answers[$question->response[$nameprefix]]);
$result->grade = $result->answers[$nameprefix]->fraction;
} else {
$result->answers = array();
$result->grade = 0.0;
}
$result->correctanswers = quiz_extract_correctanswers($answers,
$nameprefix);
return $result;
/**
* This questiontype does not have to print any additional grading details
*
* @param object $question
* @param object $state
* @param object $quiz
* @param object $options
*/
function print_question_grading_details(&$question, &$state, $quiz, $options) {
// do nothing
}
}
//// END OF CLASS ////
+24 -46
View File
@@ -8,7 +8,7 @@
optional_variable($id); // Course Module ID, or
optional_variable($q); // quiz ID
optional_variable($mode, "overview"); // Report mode
optional_variable($mode, "simplestat"); // Report mode
if ($id) {
if (! $cm = get_record("course_modules", "id", $id)) {
@@ -41,50 +41,29 @@
error("You are not allowed to use this script");
}
add_to_log($course->id, "quiz", "report", "report.php?id=$cm->id", "$quiz->id", "$cm->id");
/// Print the page header
if (empty($noheader)) {
$strquizzes = get_string("modulenameplural", "quiz");
$strquiz = get_string("modulename", "quiz");
$strreport = get_string("report", "quiz");
print_header_simple(format_string($quiz->name), "",
"<a href=\"index.php?id=$course->id\">$strquizzes</a>
-> <a href=\"view.php?id=$cm->id\">".format_string($quiz->name,true)."</a> -> $strreport",
"", "", true, update_module_button($cm->id, $course->id, $strquiz), navmenu($course, $cm));
print_heading(format_string($quiz->name));
/// Print list of available quiz reports
$allreports = get_list_of_plugins("mod/quiz/report");
$reportlist = array ("overview", "regrade"); // Standard reports we want to show first
foreach ($allreports as $report) {
if (!in_array($report, $reportlist)) {
$reportlist[] = $report;
}
}
$tabs = array();
$row = array();
$currenttab = '';
foreach ($reportlist as $report) {
$row[] = new tabobject($report, "report.php?id=$cm->id&amp;mode=$report",
get_string("report$report", "quiz"));
if ($report == $mode) {
$currenttab = $report;
}
}
$tabs[] = $row;
print_tabs($tabs, $currenttab);
// if no questions have been set up yet redirect to edit.php
if (!$quiz->questions and isteacheredit($course->id)) {
redirect('edit.php?quizid='.$quiz->id);
}
add_to_log($course->id, "quiz", "report", "report.php?id=$cm->id", "$quiz->id", "$cm->id");
/// Define some strings
$strquizzes = get_string("modulenameplural", "quiz");
$strquiz = get_string("modulename", "quiz");
/// Print the page header
print_header_simple(format_string($quiz->name), "",
"<a href=\"index.php?id=$course->id\">$strquizzes</a>
-> ".format_string($quiz->name),
"", "", true, update_module_button($cm->id, $course->id, $strquiz), navmenu($course, $cm));
/// Print the tabs
$currenttab = 'reports';
include('tabs.php');
/// Open the selected quiz report and display it
@@ -103,9 +82,8 @@
error("Error occurred during pre-processing!");
}
if (empty($noheader)) {
print_footer($course);
}
/// Print footer
print_footer($course);
?>
+22 -19
View File
@@ -8,6 +8,9 @@
class quiz_report extends quiz_default_report {
function display($quiz, $cm, $course) { /// This function just displays the report
print_heading('Not yet implemented');
return true;
global $CFG;
global $download, $quests,$qtally,$table_colcount,$max_choices, $analysis,$qs_in_order,$total_user_count,$match_number, $thismin,$thismax,$myxls,$match_qs,$formatbc,$workbook,$strquestion,$showtext,$debug;
@@ -29,7 +32,7 @@ class quiz_report extends quiz_default_report {
$strdiscrimination = get_string('discrimination', 'quiz');
//Get the question ids
//$showtext causes M/C text to whos in top table.
//$showtext causes M/C text to whos in top table.
//This could be made into a user toggle if we want to complicate matters
$showtext = 1;
$containsMCTF = 0; //used to toggle title in final listing
@@ -86,7 +89,7 @@ class quiz_report extends quiz_default_report {
}
}
}
}
}
}
}
@@ -102,7 +105,7 @@ class quiz_report extends quiz_default_report {
$userdata[$thisresp->userid][$thisresp->attemptno]['name']=fullname($thisresp);
$userdata[$thisresp->userid][$thisresp->attemptno]['attemptid']=$thisresp->aid;
}
if($debug and !$download){
print("<h3>User Data</h3>");
print_object($userdata);
@@ -114,7 +117,7 @@ class quiz_report extends quiz_default_report {
// //print_object($thisattempt);
$reportline++;
$data_tally[$reportline][$thisattempt['attemptid']][] = $thisattempt['name'];
$data_tally[$reportline][$thisattempt['attemptid']][] =round(($thisattempt['grade']/$quiz->sumgrades)*100,0);
$data_tally[$reportline][$thisattempt['attemptid']][] =round(($thisattempt['grade']/$quiz->sumgrades)*100,0);
//now for each question, record response as it should be printed and whether right, wrong or skipped
//SHORTASNSWER the answer as in $userdata; TF or MULTI need response looked by from cid from $quests
//MATCH needs elaborate processing
@@ -173,7 +176,7 @@ class quiz_report extends quiz_default_report {
print("<h3>Data Tally</h3>");
print_object($data_tally);
}
//Create a list of all attempts with their scores for item analysis
//Also create $data2 that has attempt id as key
foreach ($data_tally as $thistally){
@@ -184,7 +187,7 @@ class quiz_report extends quiz_default_report {
}
}
}
//now go through scores from top to bottom and from $data2 accumulate number correct for top 1/3 and bottom 1/3 of scorers
if ($scores) {
arsort($scores);
@@ -223,7 +226,7 @@ class quiz_report extends quiz_default_report {
}
}
}
if($debug and !$download){
print("<h3>Scores</h3>");
print_object($scores);
@@ -232,7 +235,7 @@ class quiz_report extends quiz_default_report {
print("<h3>Bottom Scores</h3>");
print_object($bott_scores);
}
//Create here an array with the response analysis data for use with both screen display & Excel
// 2 dimensional array has as many cells across as items + title, as many down as $max_choices
// plus one row [0] for correct items
@@ -297,7 +300,7 @@ class quiz_report extends quiz_default_report {
$current_column += $match_number[$qid] -1;
break;
default:
break;
}
}
@@ -325,7 +328,7 @@ class quiz_report extends quiz_default_report {
$format =& $workbook->add_format();
$format->set_bold(0);
$formaty =& $workbook->add_format();
$formaty->set_bg_color('yellow');
$formaty->set_bg_color('yellow');
$formatyc =& $workbook->add_format();
$formatyc->set_bg_color('yellow'); //bold text on yellow bg
$formatyc->set_bold(1);
@@ -392,7 +395,7 @@ class quiz_report extends quiz_default_report {
}
//now print the lines of answers
$row = 2;
foreach ($data_tally as $thisuserno=>$thisuser){
foreach($thisuser as $thisattemptno=>$thisattempt){
@@ -401,7 +404,7 @@ class quiz_report extends quiz_default_report {
//$thisitemkeys 1 & 2 are name and total score
//There needs to be a 3-way branch, keys0 & 1 just print $thisitem
//else if $thisitem['qtype'] = 5, then processing for MATCH is needed
//else the data to be printed is in $thisitem['data'] and
//else the data to be printed is in $thisitem['data'] and
//$thisitem['score'] == 1 shows that the item was correct
if ($thisitem['score'] < 1) {$thiscolor = $formatred;} else {$thiscolor = $formatblue;}
$col++;
@@ -571,7 +574,7 @@ class quiz_report extends quiz_default_report {
if ($download == "txt") {
/// Print header to force download
header("Content-Type: application/download\n");
header("Content-Type: application/download\n");
$downloadfilename = clean_filename("$course->shortname ".format_string($quiz->name,true));
header("Content-Disposition: attachment; filename=\"$downloadfilename.txt\"");
header("Expires: 0");
@@ -582,7 +585,7 @@ class quiz_report extends quiz_default_report {
echo format_string($quiz->name,true);
echo "\n";
/// Print all the user data
$colcount = count($question_ids);
foreach ($data_tally as $thisuserno=>$thisuser){
@@ -697,9 +700,9 @@ class quiz_report extends quiz_default_report {
print("</tr>\n");
}
}
print("</table><br />\n");
if($debug and !$download){
print("<h3>Qtally</h3>");
print_object($qtally);
@@ -730,7 +733,7 @@ class quiz_report extends quiz_default_report {
$nowdata = $analysis[$i][$j];
if (strpos($nowdata,"//")>0) {$nowdata = str_replace("//","<br />",$nowdata);}
print("<td align=\"center\">&nbsp;$nowdata</td>");
}
}
print("</tr>\n");
@@ -797,7 +800,7 @@ class quiz_report extends quiz_default_report {
}
}
print("</table>\n");
echo "<br />\n";
echo "<table border=\"0\" align=\"center\"><tr>\n";
echo "<td>";
@@ -821,7 +824,7 @@ function qr_quiz_responses($quiz) {
// Given any quiz number, get all responses and place in
// $response object
global $CFG;
$resp_recs =get_records_sql("SELECT r.id as rid, r.attempt, r.answer, r.question, a.attempt as attemptno, a.id as aid, a.quiz, a.userid, a.sumgrades, u.id as uid, u.lastname, u.firstname FROM {$CFG->prefix}quiz_responses r, {$CFG->prefix}quiz_attempts a, {$CFG->prefix}user u WHERE a.id = r.attempt AND a.quiz = '$quiz' AND a.userid = u.id ORDER BY u.lastname ASC, u.firstname ASC, r.id ASC");
return $resp_recs;
}
-284
View File
@@ -1,284 +0,0 @@
<?php // $Id$
require_once($CFG->libdir.'/tablelib.php');
/// Overview report just displays a big table of all the attempts
class quiz_report extends quiz_default_report {
function display($quiz, $cm, $course) { /// This function just displays the report
global $CFG, $SESSION, $db;
$strreallydel = addslashes(get_string('deleteattemptcheck','quiz'));
$strnoattempts = get_string('noattempts','quiz');
$strtimeformat = get_string('strftimedatetime');
$action = optional_param('action', '');
switch($action) {
case 'delete': /// Some attempts need to be deleted
$attemptids = isset($_POST['attemptid']) ? $_POST['attemptid'] : array();
if(!is_array($attemptids) || empty($attemptids)) {
break;
}
foreach($attemptids as $num => $attemptid) {
if(empty($attemptid)) {
unset($attemptids[$num]);
}
}
foreach($attemptids as $attemptid) {
if ($todelete = get_record('quiz_attempts', 'id', $attemptid)) {
delete_records('quiz_attempts', 'id', $attemptid);
delete_records('quiz_responses', 'attempt', $attemptid);
// Search quiz_attempts for other instances by this user.
// If none, then delete record for this quiz, this user from quiz_grades
// else recalculate best grade
$userid = $todelete->userid;
if (!record_exists('quiz_attempts', 'userid', $userid, 'quiz', $quiz->id)) {
delete_records('quiz_grades', 'userid', $userid,'quiz', $quiz->id);
} else {
quiz_save_best_grade($quiz, $userid);
}
}
}
break;
}
if(!isset($SESSION->quiz_overview_table)) {
$SESSION->quiz_overview_table = array('noattempts' => false, 'teacherattempts' => true, 'detailedmarks' => false);
}
foreach($SESSION->quiz_overview_table as $option => $value) {
$urlparam = optional_param($option, NULL);
if($urlparam === NULL) {
$$option = $value;
}
else {
$$option = $SESSION->quiz_overview_table[$option] = $urlparam;
}
}
$tablecolumns = array('checkbox', 'picture', 'fullname', 'timefinish', 'duration', 'sumgrades');
$tableheaders = array(NULL, '', get_string('fullname'), get_string('attemptedon', 'quiz'), get_string('attemptduration', 'quiz'), get_string('grade', 'quiz').'/'.$quiz->grade);
if($detailedmarks) {
$questions = explode(',', $quiz->questions);
foreach($questions as $number => $questionid) {
$tablecolumns[] = '$'.$questionid;
$tableheaders[] = '#'.($number + 1);
}
}
$table = new flexible_table('mod-quiz-report-overview-report');
$table->define_columns($tablecolumns);
$table->define_headers($tableheaders);
$table->define_baseurl($CFG->wwwroot.'/mod/quiz/report.php?id='.$cm->id);
$table->sortable(true);
$table->collapsible(true);
$table->initialbars(true);
$table->column_suppress('picture');
$table->column_suppress('fullname');
$table->column_class('picture', 'picture');
$table->set_attribute('cellspacing', '0');
$table->set_attribute('id', 'attempts');
$table->set_attribute('class', 'generaltable generalbox');
// Start working -- this is necessary as soon as the niceties are over
$table->setup();
/// Check to see if groups are being used in this quiz
if ($groupmode = groupmode($course, $cm)) { // Groups are being used
$currentgroup = setup_and_print_groups($course, $groupmode, "report.php?id=$cm->id&amp;mode=overview");
} else {
$currentgroup = false;
}
/// Get all teachers and students
if ($currentgroup) {
$users = get_group_users($currentgroup);
}
else {
$users = get_course_users($course->id);
}
if(!$teacherattempts) {
$teachers = get_course_teachers($course->id);
if(!empty($teachers)) {
$keys = array_keys($teachers);
}
foreach($keys as $key) {
unset($users[$key]);
}
}
if(empty($users)) {
print_heading($strnoattempts);
return true;
}
// Construct the SQL
if($where = $table->get_sql_where()) {
$where .= ' AND ';
}
if($sort = $table->get_sql_sort()) {
$sortparts = explode(',', $sort);
$newsort = array();
$firsttime = true;
foreach($sortparts as $sortpart) {
$sortpart = trim($sortpart);
if(substr($sortpart, 0, 1) == '$') {
if($firsttime) {
$qnum = intval(substr($sortpart, 1));
$where .= '('.sql_isnull('qr.question').' OR qr.question = '.$qnum.') AND ';
$newsort[] = 'grade '.(strpos($sortpart, 'ASC')? 'ASC' : 'DESC');
$firsttime = false;
}
}
else {
$newsort[] = $sortpart;
}
}
$sort = ' ORDER BY '.implode(', ', $newsort);
}
$select = 'SELECT '.$db->Concat('u.id', '\'#\'', $db->IfNull('qa.attempt', '0')).' AS uvsa, u.id AS userid, u.firstname, u.lastname, u.picture, qa.id AS attempt, qa.sumgrades, qa.timefinish, qa.timestart, qa.timefinish - qa.timestart AS duration ';
$group = 'GROUP BY uvsa';
$sql = 'FROM '.$CFG->prefix.'user u '.
'LEFT JOIN '.$CFG->prefix.'quiz_attempts qa ON u.id = qa.userid '.
'LEFT JOIN '.$CFG->prefix.'quiz_responses qr ON qr.attempt = qa.id '.
'WHERE '.$where.'u.id IN ('.implode(',', array_keys($users)).') AND ('.($noattempts ? sql_isnull('qa.quiz').' OR ' : '') . 'qa.quiz = '.$quiz->id.') ';
$total = count_records_sql('SELECT COUNT(DISTINCT('.$db->Concat('u.id', '\'#\'', $db->IfNull('qa.attempt', '0')).')) '.$sql);
$table->pagesize(10, $total);
if($table->get_page_start() !== '' && $table->get_page_size() !== '') {
$limit = ' '.sql_paging_limit($table->get_page_start(), $table->get_page_size());
}
else {
$limit = '';
}
$attempts = get_records_sql($select.$sql.$group.$sort.$limit);
if(!empty($attempts)) {
foreach ($attempts as $attempt) {
$picture = print_user_picture($attempt->userid, $course->id, $attempt->picture, false, true);
$row = array(
'<input type="checkbox" name="attemptid[]" value="'.$attempt->attempt.'" />',
$picture,
'<a href="'.$CFG->wwwroot.'/user/view.php?id='.$attempt->userid.'&amp;course='.$course->id.'">'.fullname($attempt).'</a>',
empty($attempt->attempt) ? '-' : '<a href="review.php?q='.$quiz->id.'&amp;attempt='.$attempt->attempt.'">'.userdate($attempt->timestart, $strtimeformat).'</a>',
empty($attempt->attempt) || empty($attempt->timefinish) ? '-' : format_time($attempt->duration),
$attempt->sumgrades === NULL ? '-' : format_float($attempt->sumgrades / $quiz->sumgrades * $quiz->grade,$quiz->decimalpoints)
);
if($detailedmarks) {
if(empty($attempt->attempt)) {
foreach($questions as $question) {
$row[] = '-';
}
}
else {
$responses = get_records('quiz_responses', 'attempt', $attempt->attempt, 'question');
foreach($responses as $response) {
$row[] = $response->grade;
}
}
}
$table->add_data($row);
}
echo '<div id="tablecontainer">';
echo '<form id="attemptsform" method="post" action="report.php" onsubmit="var menu = document.getElementById(\'actionmenu\'); return (menu.options[menu.selectedIndex].value == \'delete\' ? \''.$strreallydel.'\' : true);">';
echo '<input type="hidden" name="id" value="'.$cm->id.'" />';
}
$table->print_html();
if(!empty($attempts)) {
echo '<table id="commands">';
echo '<tr><td>';
echo '<a href="javascript:select_all_in(\'DIV\', null, \'tablecontainer\');">'.get_string('selectall', 'quiz').'</a> / ';
echo '<a href="javascript:deselect_all_in(\'DIV\', null, \'tablecontainer\');">'.get_string('selectnone', 'quiz').'</a> ';
echo '</td><td style="text-align: right;">';
$options = array('dummy' => ' ', 'delete' => get_string('delete'));
$menu = str_replace('<select', '<select id="actionmenu"', choose_from_menu($options, 'action', '', get_string('selectedattempts', 'quiz'), 'if(this.selectedIndex > 1) submitFormById(\'attemptsform\');', '', true));
echo $menu;
echo '<noscript id="noscriptactionmenu" style="display: inline;">';
echo '<input type="submit" value="'.get_string('go').'" /></noscript>';
echo '<script type="text/javascript">'."\n<!--\n".'document.getElementById("noscriptactionmenu").style.display = "none";'."\n-->\n".'</script>';
echo '</td></tr></table>';
echo '</form></div>';
}
else {
print_heading(get_string('noattemptsmatchingfilter', 'quiz', strtolower($course->students)));
}
echo '<div class="controls">';
echo '<form method="report.php">';
echo '<p>'.get_string('displayoptions', 'quiz').': ';
echo '<input type="hidden" name="id" value="'.$cm->id.'" />';
echo '<input type="hidden" name="noattempts" value="0" />';
echo '<input type="hidden" name="teacherattempts" value="0" />';
echo '<input type="hidden" name="detailedmarks" value="0" />';
echo '<input type="checkbox" id="checknoattempts" name="noattempts" '.($noattempts?'checked="checked" ':'').'value="1" /> <label for="checknoattempts">'.get_string('shownoattempts', 'quiz').'</label> ';
echo '<input type="checkbox" id="checkteacherattempts" name="teacherattempts" '.($teacherattempts?'checked="checked" ':'').'value="1" /> <label for="checkteacherattempts">'.get_string('showteacherattempts', 'quiz').'</label> ';
echo '<input type="checkbox" id="checkdetailedmarks" name="detailedmarks" '.($detailedmarks?'checked="checked" ':'').'value="1" /> <label for="checkdetailedmarks">'.get_string('showdetailedmarks', 'quiz').'</label> ';
echo '<input type="submit" value="'.get_string('go').'" />';
echo '</p>';
echo '</form>';
echo '</div>';
return true;
}
function quiz_get_user_attempts_list($quiz, $attempts, $bestgrade, $timeformat) {
/// Returns a little list of all attempts, one per line,
/// with each grade linked to the feedback report and with the best grade highlighted
/// Each also has information about date and lapsed time
$bestgrade = format_float($bestgrade,$quiz->decimalpoints);
foreach ($attempts as $attempt) {
$attemptgrade = format_float(($attempt->sumgrades / $quiz->sumgrades) * $quiz->grade,$quiz->decimalpoints);
$attemptdate = userdate($attempt->timestart, $timeformat);
if ($attempt->timefinish) {
$attemptlapse = format_time($attempt->timefinish - $attempt->timestart);
} else {
$attemptlapse = "...";
}
$button = "<input type=\"checkbox\" name=\"box$attempt->id\" value=\"$attempt->id\" alt=\"box$attempt->id\" />";
$revurl = "review.php?q=$quiz->id&amp;attempt=$attempt->id";
if ($attemptgrade == $bestgrade) {
$userattempts[] = "$button&nbsp;<span class=\"highlight\">$attemptgrade</span>&nbsp;<a href=\"$revurl\">$attemptdate</a>&nbsp;($attemptlapse)";
} else {
$userattempts[] = "$button&nbsp;$attemptgrade&nbsp;<a href=\"$revurl\">$attemptdate</a>&nbsp;($attemptlapse)";
}
}
return implode("<br />\n", $userattempts);
}
}
?>
-77
View File
@@ -1,77 +0,0 @@
<?php // $Id$
/// Overview report just displays a big table of all the attempts
class quiz_report extends quiz_default_report {
function display($quiz, $cm, $course) { /// This function just displays the report
global $CFG;
if (!$attempts = get_records("quiz_attempts", "quiz", $quiz->id)) {
print_header(get_string("noattempts", "quiz"));
print_continue("report.php?id=$cm->id");
print_footer($course);
exit;
}
$users = array();
$count->attempt = 0;
$count->changed = 0;
foreach ($attempts as $attempt) {
set_time_limit(120);
if (!$attempt->timefinish) { // Skip incomplete attempts
continue;
}
if($quiz->timelimit > 0) {
$timelimit = ($quiz->timelimit * 60) + 60;
$timetaken = $attempt->timefinish - $attempt->timestart;
if($timetaken > $timelimit) {
// Skip overdued attempts
continue;
}
}
$count->attempt++;
if (! $questions = quiz_get_attempt_questions($quiz, $attempt)) {
error("Could not reconstruct quiz results for attempt $attempt->id!");
}
if (!$result = quiz_grade_responses($quiz, $questions)) {
error("Could not re-grade this quiz attempt!");
}
if ($attempt->sumgrades != $result->sumgrades) {
$attempt->sumgrades = $result->sumgrades;
$count->changed++;
if (! update_record("quiz_attempts", $attempt)) {
notify("Could not regrade attempt $attempt->id");
}
}
$users[$attempt->userid] = $attempt->userid;
}
if ($users) {
foreach ($users as $userid) {
if (! quiz_save_best_grade($quiz, $userid)) {
notify("Could not save best grade for user $userid!");
}
}
}
print_heading(get_string("regradecomplete", "quiz"));
print_heading(get_string("regradecount", "quiz", $count));
return true;
}
}
?>
+17 -14
View File
@@ -20,11 +20,11 @@ function quiz_report_simplestat_get_attempt_responses($attempt) {
global $CFG;
if (!$responses = get_records_sql("SELECT q.id, q.qtype, q.category, q.questiontext,
q.defaultgrade, q.image, r.answer
FROM {$CFG->prefix}quiz_responses r,
q.defaultgrade, q.image, s.answer
FROM {$CFG->prefix}quiz_states s,
{$CFG->prefix}quiz_questions q
WHERE r.attempt = '$attempt->id'
AND q.id = r.question")) {
AND q.id = s.question")) {
notify("Could not find any responses for that attempt!");
return false;
}
@@ -402,7 +402,7 @@ function quiz_report_simplestat_grade_attempt_results($quiz, $questions) {
error("No questions!");
}
if (!$grades = get_records_menu("quiz_question_grades", "quiz", $quiz->id, "", "question,grade")) {
if (!$grades = get_records_menu('quiz_question_instances', 'quiz', $quiz->id, '', 'question,grade')) {
error("No grades defined for these quiz questions!");
}
@@ -448,6 +448,9 @@ class quiz_report extends quiz_default_report {
global $CFG;
global $download;
print_heading('Not yet implemented');
return true;
optional_variable($download, "");
/// Check to see if groups are being used in this quiz
@@ -550,12 +553,12 @@ class quiz_report extends quiz_default_report {
/// Print names of all the fields
$myxls->write_string(0,0,format_string($quiz->name,true));
$myxls->set_column(0,0,25);
$myxls->set_column(1,$count,9);
for ($i=1; $i<=$count; $i++) {
$myxls->write_string(0,$i,$i);
}
/// Print all the user data
$row=1;
@@ -587,16 +590,16 @@ class quiz_report extends quiz_default_report {
}
$workbook->close();
exit;
}
/// If a text file is wanted, produce one
if ($download == "txt") {
/// Print header to force download
header("Content-Type: application/download\n");
header("Content-Type: application/download\n");
$downloadfilename = clean_filename("$course->shortname ".format_string($quiz->name,true));
header("Content-Disposition: attachment; filename=$downloadfilename.txt");
header("Expires: 0");
@@ -605,13 +608,13 @@ class quiz_report extends quiz_default_report {
/// Print names of all the fields
echo format_string($quiz->name,true);
for ($i=1; $i<=$count; $i++) {
echo "\t$i";
}
echo "\n";
/// Print all the user data
foreach ($data as $userid => $datum) {
@@ -639,7 +642,7 @@ class quiz_report extends quiz_default_report {
echo "\t$percent";
}
echo "\n";
exit;
}
@@ -697,7 +700,7 @@ class quiz_report extends quiz_default_report {
$options["download"] = "txt";
print_single_button("report.php", $options, get_string("downloadtext"));
echo "</table>";
return true;
}
+141 -46
View File
@@ -28,6 +28,24 @@
// | (CL,pk->id,fk->definition)
// |
// |
// |.......................................
// | .
// quiz | .
// (CL,pk->id) | ----quiz_question_datasets---- .
// | | | (CL,pk->id,fk->question, | .
// |-------------------| | | fk->dataset_definition) | .
// quiz_grades | | | | .
// (UL,pk->id,fk->quiz) |---------------------| | | | .
// | | | | | .
// quiz_attempts | quiz_questions quiz_dataset_definitions
// (UL,pk->id,fk->quiz) | (CL,pk->id,fk->category,files) (CL,pk->id,fk->category)
// | |--------------------------------| |
// | quiz_question_instances | |
// | (CL,pk->id,fk->quiz,fk->question) | |
// | | | quiz_dataset_items
// |---------------------| | (CL,pk->id,fk->definition)
// quiz_states |
// (UL,pk->id, fk->attempt, fk->question_instance) |
// |
// --------------------------------------------------------------------------------------------------------------
// | | | | | | |
@@ -88,7 +106,7 @@
// - quiz_question_grades
// - quiz_attempts
// - quiz_grades
// - quiz_responses
// - quiz_states
// This step is the standard mod backup. (course dependent).
//STEP 1. Restore categories/questions and associated structures
@@ -327,7 +345,7 @@
$question->hidden = backup_todb($que_info['#']['HIDDEN']['0']['#']);
// If it is a random question then hide it
if ($question->qtype == 4) {
if ($question->qtype = RANDOM) {
$question->hidden = 1;
}
@@ -1394,39 +1412,39 @@
return $status;
}
//This function restores the quiz_question_grades
function quiz_question_grades_restore_mods($quiz_id,$info,$restore) {
//This function restores the quiz_question_instances
function quiz_question_instances_restore_mods($quiz_id,$info,$restore) {
global $CFG;
$status = true;
//Get the quiz_question_grades array
$grades = $info['MOD']['#']['QUESTION_GRADES']['0']['#']['QUESTION_GRADE'];
//Get the quiz_question_instances array
$instances = $info['MOD']['#']['QUESTION_INSTANCES']['0']['#']['QUESTION_INSTANCE'];
//Iterate over question_grades
for($i = 0; $i < sizeof($grades); $i++) {
$gra_info = $grades[$i];
//traverse_xmlize($gra_info); //Debug
//Iterate over question_instances
for($i = 0; $i < sizeof($instances); $i++) {
$ins_info = $instances[$i];
//traverse_xmlize($ins_info); //Debug
//print_object ($GLOBALS['traverse_array']); //Debug
//$GLOBALS['traverse_array']=""; //Debug
//We'll need this later!!
$oldid = backup_todb($gra_info['#']['ID']['0']['#']);
$oldid = backup_todb($ins_info['#']['ID']['0']['#']);
//Now, build the QUESTION_GRADES record structure
$grade->quiz = $quiz_id;
$grade->question = backup_todb($gra_info['#']['QUESTION']['0']['#']);
$grade->grade = backup_todb($gra_info['#']['GRADE']['0']['#']);
//Now, build the QUESTION_INSTANCES record structure
$instance->quiz = $quiz_id;
$instance->question = backup_todb($ins_info['#']['QUESTION']['0']['#']);
$instance->grade = backup_todb($ins_info['#']['GRADE']['0']['#']);
//We have to recode the question field
$question = backup_getid($restore->backup_unique_code,"quiz_questions",$grade->question);
$question = backup_getid($restore->backup_unique_code,"quiz_questions",$instance->question);
if ($question) {
$grade->question = $question->new_id;
$instance->question = $question->new_id;
}
//The structure is equal to the db, so insert the quiz_question_grades
$newid = insert_record ("quiz_question_grades",$grade);
//The structure is equal to the db, so insert the quiz_question_instances
$newid = insert_record ("quiz_question_instances",$instance);
//Do some output
if (($i+1) % 10 == 0) {
@@ -1439,7 +1457,78 @@
if ($newid) {
//We have the newid, update backup_ids
backup_putid($restore->backup_unique_code,"quiz_question_grades",$oldid,
backup_putid($restore->backup_unique_code,"quiz_question_instances",$oldid,
$newid);
} else {
$status = false;
}
}
return $status;
}
//This function restores the quiz_question_versions
function quiz_question_versions_restore_mods($quiz_id,$info,$restore) {
global $CFG, $USER;
$status = true;
//Get the quiz_question_versions array
$versions = $info['MOD']['#']['QUESTION_VERSIONS']['0']['#']['QUESTION_VERSION'];
//Iterate over question_versions
for($i = 0; $i < sizeof($versions); $i++) {
$ver_info = $versions[$i];
//traverse_xmlize($ver_info); //Debug
//print_object ($GLOBALS['traverse_array']); //Debug
//$GLOBALS['traverse_array']=""; //Debug
//We'll need this later!!
$oldid = backup_todb($ver_info['#']['ID']['0']['#']);
//Now, build the QUESTION_VERSIONS record structure
$version->quiz = $quiz_id;
$version->oldquestion = backup_todb($ver_info['#']['OLDQUESTION']['0']['#']);
$version->newquestion = backup_todb($ver_info['#']['NEWQUESTION']['0']['#']);
$version->userid = backup_todb($ver_info['#']['USERID']['0']['#']);
$version->timestamp = backup_todb($ver_info['#']['TIMESTAMP']['0']['#']);
//We have to recode the oldquestion field
$question = backup_getid($restore->backup_unique_code,"quiz_questions",$version->oldquestion);
if ($question) {
$version->oldquestion = $question->new_id;
}
//We have to recode the newquestion field
$question = backup_getid($restore->backup_unique_code,"quiz_questions",$version->newquestion);
if ($question) {
$version->newquestion = $question->new_id;
}
//We have to recode the userid field
$user = backup_getid($restore->backup_unique_code,"user",$version->userid);
if ($user) {
$version->userid = $user->new_id;
} else { //Assign to current user
$version->userid = $USER->id;
}
//The structure is equal to the db, so insert the quiz_question_versions
$newid = insert_record ("quiz_question_versions",$version);
//Do some output
if (($i+1) % 10 == 0) {
echo ".";
if (($i+1) % 200 == 0) {
echo "<br />";
}
backup_flush(300);
}
if ($newid) {
//We have the newid, update backup_ids
backup_putid($restore->backup_unique_code,"quiz_question_versions",$oldid,
$newid);
} else {
$status = false;
@@ -1572,8 +1661,8 @@
//We have the newid, update backup_ids
backup_putid($restore->backup_unique_code,"quiz_attempts",$oldid,
$newid);
//Now process quiz_responses
$status = quiz_responses_restore_mods($newid,$att_info,$restore);
//Now process quiz_states
$status = quiz_states_restore_mods($newid,$att_info,$restore);
} else {
$status = false;
}
@@ -1582,18 +1671,18 @@
return $status;
}
//This function restores the quiz_responses
function quiz_responses_restore_mods($attempt_id,$info,$restore) {
//This function restores the quiz_states
function quiz_states_restore_mods($attempt_id,$info,$restore) {
global $CFG;
$status = true;
//Get the quiz_responses array
$responses = $info['#']['RESPONSES']['0']['#']['RESPONSE'];
//Iterate over responses
for($i = 0; $i < sizeof($responses); $i++) {
$res_info = $responses[$i];
//Get the quiz_states array
$states = $info['#']['STATES']['0']['#']['STATE'];
//Iterate over states
for($i = 0; $i < sizeof($states); $i++) {
$res_info = $states[$i];
//traverse_xmlize($res_info); //Debug
//print_object ($GLOBALS['traverse_array']); //Debug
//$GLOBALS['traverse_array']=""; //Debug
@@ -1609,9 +1698,15 @@
$response->grade = backup_todb($res_info['#']['GRADE']['0']['#']);
//We have to recode the question field
$question = backup_getid($restore->backup_unique_code,"quiz_questions",$response->question);
$question = backup_getid($restore->backup_unique_code,"quiz_questions",$state->question);
if ($question) {
$response->question = $question->new_id;
$state->question = $question->new_id;
}
//We have to recode the originalquestion field
$question = backup_getid($restore->backup_unique_code,"quiz_questions",$response->originalquestion);
if ($question) {
$response->originalquestion = $question->new_id;
}
//We have to recode the originalquestion field
@@ -1623,7 +1718,7 @@
//We have to recode the answer field
//It depends of the question type !!
//We get the question first
$question = get_record("quiz_questions","id",$response->question);
$question = get_record("quiz_questions","id",$state->question);
//It exists
if ($question) {
//Depending of the qtype, we make different recodes
@@ -1633,16 +1728,16 @@
break;
case 2: //TRUEFALSE QTYPE
//The answer is one answer id. We must recode it
$answer = backup_getid($restore->backup_unique_code,"quiz_answers",$response->answer);
$answer = backup_getid($restore->backup_unique_code,"quiz_answers",$state->answer);
if ($answer) {
$response->answer = $answer->new_id;
$state->answer = $answer->new_id;
}
break;
case 3: //MULTICHOICE QTYPE
//The answer is a comma separated list of answers. We must recode them
$answer_field = "";
$in_first = true;
$tok = strtok($response->answer,",");
$tok = strtok($state->answer,",");
while ($tok) {
//Get the answer from backup_ids
$answer = backup_getid($restore->backup_unique_code,"quiz_answers",$tok);
@@ -1657,20 +1752,20 @@
//check for next
$tok = strtok(",");
}
$response->answer = $answer_field;
$state->answer = $answer_field;
break;
case 4: //RANDOM QTYPE
//The answer links to another question id, we must recode it
$answer_link = backup_getid($restore->backup_unique_code,"quiz_questions",$response->answer);
$answer_link = backup_getid($restore->backup_unique_code,"quiz_questions",$state->answer);
if ($answer_link) {
$response->answer = $answer_link->new_id;
$state->answer = $answer_link->new_id;
}
break;
case 5: //MATCH QTYPE
//The answer is a comma separated list of hypen separated math_subs (for question and answer)
$answer_field = "";
$in_first = true;
$tok = strtok($response->answer,",");
$tok = strtok($state->answer,",");
while ($tok) {
//Extract the match_sub for the question and the answer
$exploded = explode("-",$tok);
@@ -1695,13 +1790,13 @@
//check for next
$tok = strtok(",");
}
$response->answer = $answer_field;
$state->answer = $answer_field;
break;
case 6: //RANDOMSAMATCH QTYPE
//The answer is a comma separated list of hypen separated question_id and answer_id. We must recode them
$answer_field = "";
$in_first = true;
$tok = strtok($response->answer,",");
$tok = strtok($state->answer,",");
while ($tok) {
//Extract the question_id and the answer_id
$exploded = explode("-",$tok);
@@ -1726,7 +1821,7 @@
//check for next
$tok = strtok(",");
}
$response->answer = $answer_field;
$state->answer = $answer_field;
break;
case 7: //DESCRIPTION QTYPE
//Nothing to do (there is no awser to this qtype)
@@ -1739,7 +1834,7 @@
//The answer is a comma separated list of hypen separated multianswer_id and answers. We must recode them.
$answer_field = "";
$in_first = true;
$tok = strtok($response->answer,",");
$tok = strtok($state->answer,",");
while ($tok) {
//Extract the multianswer_id and the answer
$exploded = explode("-",$tok);
@@ -1775,7 +1870,7 @@
//check for next
$tok = strtok(",");
}
$response->answer = $answer_field;
$state->answer = $answer_field;
break;
case 10: //CALCULATED QTYPE
//Nothing to do. The response is a text.
@@ -1790,7 +1885,7 @@
}
//The structure is equal to the db, so insert the quiz_attempts
$newid = insert_record ("quiz_responses",$response);
$newid = insert_record ("quiz_states",$state);
//Do some output
if (($i+1) % 10 == 0) {
@@ -1803,7 +1898,7 @@
if ($newid) {
//We have the newid, update backup_ids
backup_putid($restore->backup_unique_code,"quiz_responses",$oldid,
backup_putid($restore->backup_unique_code,"quiz_states",$oldid,
$newid);
} else {
$status = false;
+161 -85
View File
@@ -1,56 +1,64 @@
<?php // $Id$
// This page prints a review of a particular quiz attempt
/**
* This page prints a review of a particular quiz attempt
*
* @version $Id$
* @author Martin Dougiamas and many others. This has recently been completely
* rewritten by Alex Smith, Julian Sedding and Gustav Delius as part of
* the Serving Mathematics project
* {@link http://maths.york.ac.uk/serving_maths}
* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
* @package quiz
*/
require_once("../../config.php");
require_once("locallib.php");
optional_variable($id); // Course Module ID, or
optional_variable($q); // quiz ID
require_variable($attempt); // A particular attempt ID for review
if ($id) {
if (! $cm = get_record("course_modules", "id", $id)) {
error("Course Module ID was incorrect");
}
if (! $course = get_record("course", "id", $cm->course)) {
error("Course is misconfigured");
}
if (! $quiz = get_record("quiz", "id", $cm->instance)) {
error("Course module is incorrect");
}
} else {
if (! $quiz = get_record("quiz", "id", $q)) {
error("Course module is incorrect");
}
if (! $course = get_record("course", "id", $quiz->course)) {
error("Course is misconfigured");
}
if (! $cm = get_coursemodule_from_instance("quiz", $quiz->id, $course->id)) {
error("Course Module ID was incorrect");
}
}
$attempt = required_param('attempt', PARAM_INT); // A particular attempt ID for review
$page = optional_param('page', 0, PARAM_INT); // The required page
$showall = optional_param('showall', 0, PARAM_BOOL);
if (! $attempt = get_record("quiz_attempts", "id", $attempt)) {
error("No such attempt ID exists");
}
if (! $quiz = get_record("quiz", "id", $attempt->quiz)) {
error("Course module is incorrect");
}
if (! $course = get_record("course", "id", $quiz->course)) {
error("Course is misconfigured");
}
if (! $cm = get_coursemodule_from_instance("quiz", $quiz->id, $course->id)) {
error("Course Module ID was incorrect");
}
if (!count_records('quiz_newest_states', 'attemptid', $attempt->id)) {
// this question has not yet been upgraded to the new model
quiz_upgrade_states($attempt);
}
require_login($course->id, false, $cm);
$isteacher = isteacher($course->id);
$popup = false; // controls whether the protected window is used
if (!isteacher($course->id)) {
if (!$quiz->review) {
if (!$isteacher) {
if (!$attempt->timefinish) {
redirect('attempt.php?q='.$quiz->id);
}
// If not even responses are to be shown in review then we
// don't allow any review
if (!($quiz->review & QUIZ_REVIEW_RESPONSES)) {
error(get_string("noreview", "quiz"));
}
if (time() < $quiz->timeclose and $quiz->review == QUIZ_REVIEW_AFTER) {
error(get_string("noreviewuntil", "quiz", userdate($quiz->timeclose)));
}
if (time() > $quiz->timeclose and $quiz->review == QUIZ_REVIEW_BEFORE) {
error(get_string("noreview", "quiz"));
if ((time() - $attempt->timefinish) > 120) { // always allow review right after attempt
if (time() < $quiz->timeclose and !($quiz->review & QUIZ_REVIEW_OPEN)) {
error(get_string("noreviewuntil", "quiz", userdate($quiz->timeclose)));
}
if (time() >= $quiz->timeclose and !($quiz->review & QUIZ_REVIEW_CLOSED)) {
error(get_string("noreview", "quiz"));
}
} else {
// immediately after the attempt the review should be in the protected window if required
$popup = $quiz->popup;
}
if ($attempt->userid != $USER->id) {
error("This is not your attempt!");
@@ -59,57 +67,82 @@
add_to_log($course->id, "quiz", "review", "review.php?id=$cm->id&amp;attempt=$attempt->id", "$quiz->id", "$cm->id");
// Print the page header
/// Print the page header
$strquizzes = get_string("modulenameplural", "quiz");
$strquiz = get_string("modulename", "quiz");
$strreport = get_string("report", "quiz");
$strreview = get_string("review", "quiz");
$strname = get_string("name");
$strattempts = get_string("attempts", "quiz");
$strscore = get_string("score", "quiz");
$strgrade = get_string("grade");
$strbestgrade = get_string("bestgrade", "quiz");
$strtimetaken = get_string("timetaken", "quiz");
$strtimecompleted = get_string("timecompleted", "quiz");
$strtimecompleted = get_string("completedon", "quiz");
$stroverdue = get_string("overdue", "quiz");
print_header_simple(format_string($quiz->name), "",
if (!empty($popup)) {
define('MESSAGE_WINDOW', true); // This prevents the message window coming up
print_header($course->shortname.': '.format_string($quiz->name), '', '', '', '', false, '', '', false, '');
/// Include Javascript protection for this page
include('protect_js.php');
} else {
$strupdatemodule = isteacheredit($course->id)
? update_module_button($cm->id, $course->id, get_string('modulename', 'quiz'))
: "";
print_header_simple(format_string($quiz->name), "",
"<a href=\"index.php?id=$course->id\">$strquizzes</a>
-> <a href=\"view.php?id=$cm->id\">".format_string($quiz->name,true)."</a> -> $strreview",
"", "", true);
"", "", true, $strupdatemodule);
}
echo '<div id="overDiv" style="position:absolute; visibility:hidden; z-index:1000;"></div>'; // for overlib
print_heading(format_string($quiz->name));
/// Include Javascript protection for this page if required
if (!empty($quiz->popup)) {
include("protect_js.php");
/// Print heading and tabs if this is part of a preview
if ($isteacher) {
$currenttab = ($attempt->userid == $USER->id) ? 'preview' : '';
include('tabs.php');
} else {
print_heading(format_string($quiz->name));
}
if (!($questions = quiz_get_attempt_questions($quiz, $attempt))) {
error("Unable to get questions from database for quiz $quiz->id attempt $attempt->id number $attempt->attempt");
/// Load all the questions and states needed by this script
// load the questions needed by page
$pagelist = $showall ? quiz_questions_in_quiz($attempt->layout) : quiz_questions_on_page($attempt->layout, $page);
$sql = "SELECT q.*, i.grade AS maxgrade, i.id AS instance".
" FROM {$CFG->prefix}quiz_questions q,".
" {$CFG->prefix}quiz_question_instances i".
" WHERE i.quiz = '$quiz->id' AND q.id = i.question".
" AND q.id IN ($pagelist)";
if (!$questions = get_records_sql($sql)) {
error('No questions found');
}
if (!$result = quiz_grade_responses($quiz, $questions)) {
error("Could not re-grade this quiz attempt!");
// Load the question type specific information
if (!quiz_get_question_options($questions)) {
error('Could not load question options');
}
// Restore the question sessions to their most recent states
// creating new sessions where required
if (!$states = quiz_restore_question_sessions($questions, $quiz, $attempt)) {
error('Could not restore question sessions');
}
/// Print infobox
$timelimit = (int)$quiz->timelimit * 60;
$overtime = 0;
if ($timetaken = ($attempt->timefinish - $attempt->timestart)) {
if($timelimit && $timetaken > ($timelimit + 60)) {
$overtime = $timetaken - $timelimit;
$overtime = format_time($overtime);
if ($attempt->timefinish) {
if ($timetaken = ($attempt->timefinish - $attempt->timestart)) {
if($timelimit && $timetaken > ($timelimit + 60)) {
$overtime = $timetaken - $timelimit;
$overtime = format_time($overtime);
}
$timetaken = format_time($timetaken);
} else {
$timetaken = "-";
}
$timetaken = format_time($timetaken);
} else {
$timetaken = "-";
$timetaken = get_string('unfinished', 'quiz');
}
$table->align = array("right", "left");
@@ -118,41 +151,84 @@
$picture = print_user_picture($student->id, $course->id, $student->picture, false, true);
$table->data[] = array($picture, fullname($student, true));
}
$table->data[] = array("$strtimetaken:", $timetaken);
$table->data[] = array("$strtimecompleted:", userdate($attempt->timefinish));
if ($isteacher and count($attempts = get_records_select('quiz_attempts', "quiz = '$quiz->id' AND userid = '$attempt->userid'", 'attempt ASC')) > 1) {
// print list of attempts
$attemptlist = '';
foreach ($attempts as $at) {
$attemptlist .= ($at->id == $attempt->id)
? '<b>'.$at->attempt.'</b>, '
: '<a href="review.php?attempt='.$at->id.($showall?'&amp;showall=true':'').'">'.$at->attempt.'</a>, ';
}
$table->data[] = array(get_string('attempts', 'quiz').':', trim($attemptlist, ' ,'));
}
$table->data[] = array(get_string('startedon', 'quiz').':', userdate($attempt->timestart));
if ($attempt->timefinish) {
$table->data[] = array("$strtimecompleted:", userdate($attempt->timefinish));
$table->data[] = array("$strtimetaken:", $timetaken);
}
if (!empty($overtime)) {
$table->data[] = array("$stroverdue:", $overtime);
}
if ($quiz->grade) {
if($overtime) {
$result->sumgrades = "0";
$result->percentage = "0";
$result->grade = "0.0";
}
$table->data[] = array("$strscore:", "$result->sumgrades/$quiz->sumgrades ($result->percentage %)");
$table->data[] = array("$strgrade:", "$result->grade/$quiz->grade");
$percentage = format_float(($attempt->sumgrades/$quiz->sumgrades)*100, 0);
$grade = format_float(($attempt->sumgrades/$quiz->sumgrades)*$quiz->grade, $CFG->quiz_decimalpoints);
$table->data[] = array("$strscore:", "$attempt->sumgrades/$quiz->sumgrades ($percentage %)");
$table->data[] = array("$strgrade:", $grade.get_string('outof', 'quiz').$quiz->grade);
}
if ($isteacher and $attempt->userid == $USER->id) {
// the teacher is at the end of a preview. Print button to start new preview
unset($buttonoptions);
$buttonoptions['q'] = $quiz->id;
$buttonoptions['forcenew'] = true;
echo '<center>';
print_single_button($CFG->wwwroot.'/mod/quiz/attempt.php', $buttonoptions, get_string('startagain', 'quiz'));
echo '</center>';
} else { // print number of the attempt
print_heading(get_string('reviewofattempt', 'quiz', $attempt->attempt));
}
print_table($table);
if (isteacher($course->id)) {
print_continue("report.php?q=$quiz->id");
} else {
print_continue("view.php?q=$quiz->id");
/// Print the navigation panel if required
$numpages = quiz_number_of_pages($attempt->layout);
if ($numpages > 1 and !$showall) {
print_paging_bar($numpages, $page, 1, 'review.php?attempt='.$attempt->id.'&amp;');
echo '<center><a href="review.php?attempt='.$attempt->id.'&amp;showall=true">';
print_string('showall', 'quiz');
echo '</a></center>';
}
$quiz->feedback = true;
$quiz->correctanswers = true;
$quiz->shuffleanswers = false;
$quiz->shufflequestions = false;
quiz_print_quiz_questions($quiz, $questions, $result);
/// Print all the questions
if (isteacher($course->id)) {
print_continue("report.php?q=$quiz->id");
} else {
print_continue("view.php?q=$quiz->id");
$pagequestions = explode(',', $pagelist);
$number = quiz_first_questionnumber($attempt->layout, $pagelist);
foreach ($pagequestions as $i) {
$options = quiz_get_reviewoptions($quiz, $attempt, $isteacher);
$options->validation = QUIZ_EVENTVALIDATE === $states[$i]->event;
$options->history = ($isteacher and !$attempt->preview) ? 'all' : 'graded';
// Print the question
if ($i > 0) {
echo "<br />\n";
}
quiz_print_quiz_question($questions[$i], $states[$i], $number, $quiz, $options);
$number += $questions[$i]->length;
}
print_footer($course);
// Print the navigation panel if required
if ($numpages > 1 and !$showall) {
print_paging_bar($numpages, $page, 1, 'review.php?attempt='.$attempt->id.'&amp;');
}
// print javascript button to close the window, if necessary
if (!$isteacher) {
include('attempt_close_js.php');
}
if (empty($popup)) {
print_footer($course);
}
?>
+86
View File
@@ -0,0 +1,86 @@
<!-- This form fragment is used to set the review options -->
<td align="right">
<table>
<tr valign="top">
<tr><td align="right"><b><?php print_string("reviewoptions", "quiz") ?>:</b></td></tr>
<tr><td align="right"><?php print_string("reviewimmediately", "quiz") ?>:</td></tr>
<tr><td align="right"><?php print_string("reviewopen", "quiz") ?>:</td></tr>
<tr><td align="right"><?php print_string("reviewclosed", "quiz") ?>:</td></tr>
</tr>
</table>
</td>
<td>
<table>
<tr valign="top">
<td align="center"><?php print_string('responses', 'quiz') ?></td>
<td align="center"><?php print_string('scores', 'quiz') ?></td>
<td align="center"><?php print_string('feedback', 'quiz') ?></td>
<td align="center"><?php print_string('answers', 'quiz') ?></td>
<!-- <td align="center"><?php print_string('solutions', 'quiz') ?></td>-->
<td>
<?php helpbutton("review2", get_string("allowreview","quiz"), "quiz"); ?>
</td>
</tr>
<tr>
<td align="center">
<input type="checkbox" name="responsesimmediately" value="Yes" <?php echo ($form->review & QUIZ_REVIEW_RESPONSES & QUIZ_REVIEW_IMMEDIATELY) ? 'checked="checked"' : '' ?> />
</td>
<td align="center">
<input type="checkbox" name="scoreimmediately" value="Yes" <?php echo ($form->review & QUIZ_REVIEW_SCORES & QUIZ_REVIEW_IMMEDIATELY) ? 'checked="checked"' : '' ?> />
</td>
<td align="center">
<input type="checkbox" name="feedbackimmediately" value="Yes" <?php echo ($form->review & QUIZ_REVIEW_FEEDBACK & QUIZ_REVIEW_IMMEDIATELY) ? 'checked="checked"' : '' ?> />
</td>
<td align="center">
<input type="checkbox" name="answersimmediately" value="Yes" <?php echo ($form->review & QUIZ_REVIEW_ANSWERS & QUIZ_REVIEW_IMMEDIATELY) ? 'checked="checked"' : '' ?> />
</td>
<!--
<td align="center">
<input type="checkbox" name="solutionsimmediately" value="Yes" <?php echo ($form->review & QUIZ_REVIEW_SOLUTIONS & QUIZ_REVIEW_IMMEDIATELY) ? 'checked="checked"' : '' ?> />
</td>
-->
</tr>
<tr>
<td align="center">
<input type="checkbox" name="responsesopen" value="Yes" <?php echo ($form->review & QUIZ_REVIEW_RESPONSES & QUIZ_REVIEW_OPEN) ? 'checked="checked"' : '' ?> />
</td>
<td align="center">
<input type="checkbox" name="scoreopen" value="Yes" <?php echo ($form->review & QUIZ_REVIEW_SCORES & QUIZ_REVIEW_OPEN) ? 'checked="checked"' : '' ?> />
</td>
<td align="center">
<input type="checkbox" name="feedbackopen" value="Yes" <?php echo ($form->review & QUIZ_REVIEW_FEEDBACK & QUIZ_REVIEW_OPEN) ? 'checked="checked"' : '' ?> />
</td>
<td align="center">
<input type="checkbox" name="answersopen" value="Yes" <?php echo ($form->review & QUIZ_REVIEW_ANSWERS & QUIZ_REVIEW_OPEN) ? 'checked="checked"' : '' ?> />
</td>
<!--
<td align="center">
<input type="checkbox" name="solutionsopen" value="Yes" <?php echo ($form->review & QUIZ_REVIEW_SOLUTIONS & QUIZ_REVIEW_OPEN) ? 'checked="checked"' : '' ?> />
</td>
-->
</tr>
<tr>
<td align="center">
<input type="checkbox" name="responsesclosed" value="Yes" <?php echo ($form->review & QUIZ_REVIEW_RESPONSES & QUIZ_REVIEW_CLOSED) ? 'checked="checked"' : '' ?> />
</td>
<td align="center">
<input type="checkbox" name="scoreclosed" value="Yes" <?php echo ($form->review & QUIZ_REVIEW_SCORES & QUIZ_REVIEW_CLOSED) ? 'checked="checked"' : '' ?> />
</td>
<td align="center">
<input type="checkbox" name="feedbackclosed" value="Yes" <?php echo ($form->review & QUIZ_REVIEW_FEEDBACK & QUIZ_REVIEW_CLOSED) ? 'checked="checked"' : '' ?> />
</td>
<td align="center">
<input type="checkbox" name="answersclosed" value="Yes" <?php echo ($form->review & QUIZ_REVIEW_ANSWERS & QUIZ_REVIEW_CLOSED) ? 'checked="checked"' : '' ?> />
</td>
<!--
<td align="center">
<input type="checkbox" name="solutionsclosed" value="Yes" <?php echo ($form->review & QUIZ_REVIEW_SOLUTIONS & QUIZ_REVIEW_CLOSED) ? 'checked="checked"' : '' ?> />
</td>
-->
</tr>
</table>
</td>
+117
View File
@@ -0,0 +1,117 @@
<?php // $Id$
/**
* This page prints a review of a particular question attempt
*
* @version $Id$
* @author Martin Dougiamas and many others. This has recently been completely
* rewritten by Alex Smith, Julian Sedding and Gustav Delius as part of
* the Serving Mathematics project
* {@link http://maths.york.ac.uk/serving_maths}
* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
* @package quiz
*/
require_once('../../config.php');
require_once('locallib.php');
// Either stateid or (attemptid AND questionid) must be given
$stateid = optional_param('state', 0, PARAM_INT); // state id
$attemptid = optional_param('attempt', 0, PARAM_INT); // attempt id
$questionid = optional_param('question', 0, PARAM_INT); // attempt id
$number = required_param('number', PARAM_INT); // question number
if ($stateid) {
if (! $state = get_record('quiz_states', 'id', $stateid)) {
error('Invalid state id');
}
if (! $attempt = get_record('quiz_attempts', 'id', $state->attempt)) {
error('No such attempt ID exists');
}
} elseif ($attemptid) {
if (! $attempt = get_record('quiz_attempts', 'id', $attemptid)) {
error('No such attempt ID exists');
}
if (! $neweststate = get_field('quiz_newest_states', 'new', 'attemptid', $attemptid, 'questionid', $questionid)) {
// newest_state not set, probably because this is an old attempt from the old quiz module code
if (! $state = get_record('quiz_states', 'question', $questionid, 'attempt', $attemptid)) {
error('Invalid question id');
}
} else {
if (! $state = get_record('quiz_states', 'id', $neweststate->new)) {
error('Invalid state id');
}
}
} else {
error('Parameter missing');
}
if (! $question = get_record('quiz_questions', 'id', $state->question)) {
error('Question for this state is missing');
}
if (! $quiz = get_record('quiz', 'id', $attempt->quiz)) {
error('Course module is incorrect');
}
if (! $course = get_record('course', 'id', $quiz->course)) {
error('Course is misconfigured');
}
if (! $cm = get_coursemodule_from_instance('quiz', $quiz->id, $course->id)) {
error('Course Module ID was incorrect');
}
require_login($course->id, false, $cm);
$isteacher = isteacher($course->id);
if (!$isteacher) {
if (!$attempt->timefinish) {
redirect('attempt.php?q='.$quiz->id);
}
// If not even responses are to be shown in review then we
// don't allow any review
if (!($quiz->review & QUIZ_REVIEW_RESPONSES)) {
error(get_string("noreview", "quiz"));
}
if ((time() - $attempt->timefinish) > 120) { // always allow review right after attempt
if (time() < $quiz->timeclose and !($quiz->review & QUIZ_REVIEW_OPEN)) {
error(get_string("noreviewuntil", "quiz", userdate($quiz->timeclose)));
}
if (time() >= $quiz->timeclose and !($quiz->review & QUIZ_REVIEW_CLOSED)) {
error(get_string("noreview", "quiz"));
}
}
if ($attempt->userid != $USER->id) {
error('This is not your attempt!');
}
}
//add_to_log($course->id, 'quiz', 'review', "review.php?id=$cm->id&amp;attempt=$attempt->id", "$quiz->id", "$cm->id");
/// Print the page header
$strquizzes = get_string('modulenameplural', 'quiz');
$strreviewquestion = get_string('reviewquestion', 'quiz');
print_header();
echo '<div id="overDiv" style="position:absolute; visibility:hidden; z-index:1000;"></div>'; // for overlib
/// Print heading
print_heading(format_string($quiz->name));
$instance = get_record('quiz_question_instances', 'quiz', $quiz->id, 'question', $question->id);
$question->instance = $instance->id;
$question->maxgrade = $instance->grade;
$question->name_prefix = 'r';
$QUIZ_QTYPES[$question->qtype]->get_question_options($question);
quiz_restore_state($question, $state);
$state->last_graded = $state;
$options = quiz_get_reviewoptions($quiz, $attempt, $isteacher);
$options->validation = ($state->event == QUIZ_EVENTVALIDATE);
$options->history = 'all';
quiz_print_quiz_question($question, $state, $number, $quiz, $options);
print_footer();
?>
+61
View File
@@ -0,0 +1,61 @@
.feedbacktext {
display:block;
}
body#mod-quiz-attempts table#attempts,
body#mod-quiz-attempts table#commands
{
width: 80%;
margin: auto;
}
body#mod-quiz-attempts table#attempts {
margin: 20px auto;
}
body#mod-quiz-attempts table#attempts .header,
body#mod-quiz-attempts table#attempts .cell
{
padding: 4px;
}
body#mod-quiz-attempts table#attempts .header .commands {
display: inline;
}
body#mod-quiz-attempts table#attempts .picture {
width: 40px;
}
body#mod-quiz-attempts table#attempts td {
border-left-width: 1px;
border-right-width: 1px;
border-left-style: solid;
border-right-style: solid;
vertical-align: bottom;
}
.feedbacktext {
text-align:right;
}
body#mod-quiz-attempts table#attempts .header {
text-align: left;
}
body#mod-quiz-attempts table#attempts .picture {
text-align: center !important;
}
body#mod-quiz-attempts .controls {
text-align: center;
}
body#mod-quiz-attempts table#attempts td {
border-color: #dddddd;
}
body#mod-quiz-attempts table#attempts .r1 {
background-color: #eeeeee;
}
.editorhelptext {
font-size:0.7em;
}
+59
View File
@@ -0,0 +1,59 @@
<?php // $Id$
/// This file to be included so we can assume config.php has already been included.
if (empty($quiz)) {
error('You cannot call this script in that way');
}
if (!isset($currenttab)) {
$currenttab = '';
}
if (!isset($cm)) {
$cm = get_coursemodule_from_instance('quiz', $quiz->id);
}
if (!isset($course)) {
$course = get_record('course', 'id', $quiz->course);
}
//print_heading(format_string($quiz->name));
$tabs = array();
$row = array();
$inactive = array();
$row[] = new tabobject('info', "view.php?q=$quiz->id", get_string('info', 'quiz'));
$row[] = new tabobject('attempts', "attempts.php?q=$quiz->id", get_string('attempts', 'quiz'));
$row[] = new tabobject('preview', "attempt.php?q=$quiz->id", get_string('preview', 'quiz'));
if (isteacheredit($course->id)) {
$row[] = new tabobject('edit', "edit.php?quizid=$quiz->id", get_string('editquiz', 'quiz'));
//$row[] = new tabobject('update', "$CFG->wwwroot/course/mod.php?update=$cm->id&amp;sesskey=$USER->sesskey", get_string('updatethis', '', get_string('modulename', 'quiz')));
}
$row[] = new tabobject('reports', "report.php?q=$quiz->id", get_string('reports', 'quiz'));
$tabs[] = $row;
if ($currenttab == 'reports' and isset($mode)) {
$inactive[] = 'reports';
$allreports = get_list_of_plugins("mod/quiz/report");
$reportlist = array ('simplestat', 'fullstat'); // Standard reports we want to show first
foreach ($allreports as $report) {
if (!in_array($report, $reportlist)) {
$reportlist[] = $report;
}
}
$row = array();
$currenttab = '';
foreach ($reportlist as $report) {
$row[] = new tabobject($report, "report.php?q=$quiz->id&amp;mode=$report",
get_string("report$report", "quiz"));
if ($report == $mode) {
$currenttab = $report;
}
}
$tabs[] = $row;
}
print_tabs($tabs, $currenttab, $inactive);
?>
+1 -1
View File
@@ -10,7 +10,7 @@ function countdown_clock() {
if(quizTimerValue == 0) {
clearTimeout(timeout_id);
//alert(timesup);
document.forms[0].timeup.value = 1;
document.forms[0].submit();
}
+1 -1
View File
@@ -5,7 +5,7 @@
// This fragment is called by moodle_needs_upgrading() and /admin/index.php
////////////////////////////////////////////////////////////////////////////////
$module->version = 2005042002; // The (date) version of this module
$module->version = 2005050405; // The (date) version of this module
$module->requires = 2005021600; // Requires this Moodle version
$module->cron = 0; // How often should cron check this module (seconds)?
+111 -57
View File
@@ -11,15 +11,16 @@
$q = optional_param('q', 0, PARAM_INT); // quiz ID
$edit = optional_param('edit', '');
if ($id) {
if (! $cm = get_record("course_modules", "id", $id)) {
error("Course Module ID was incorrect");
}
if (! $course = get_record("course", "id", $cm->course)) {
error("Course is misconfigured");
}
if (! $quiz = get_record("quiz", "id", $cm->instance)) {
error("Course module is incorrect");
}
@@ -37,7 +38,8 @@
}
require_login($course->id, false, $cm);
$isteacher = isteacher($course->id);
// if no questions have been set up yet redirect to edit.php
if (!$quiz->questions and isteacheredit($course->id)) {
redirect('edit.php?quizid='.$quiz->id);
@@ -75,30 +77,54 @@
echo '<td id="middle-column">';
if (isteacher($course->id)) {
$attemptcount = count_records_select("quiz_attempts", "quiz = '$quiz->id' AND timefinish > 0");
$a->count = count_records("quiz_grades", "quiz", "$quiz->id");
$a->items = get_string('users');
$strusers = get_string('counteditems', '', $a);
$strviewallanswers = get_string("viewallanswers","quiz",$attemptcount);
echo "<div class=\"reportlink\"><a href=\"report.php?id=$cm->id\">$strviewallanswers ($strusers)</a></div>";
} else if (!$cm->visible) {
notice(get_string("activityiscurrentlyhidden"));
}
$available = ($quiz->timeopen < $timenow and $timenow < $quiz->timeclose) || isteacher($course->id);
$available = ($quiz->timeopen < $timenow and $timenow < $quiz->timeclose) || $isteacher;
// Print the main part of the page
// Print heading and tabs for teacher
if ($isteacher) {
$currenttab = 'info';
include('tabs.php');
}
print_heading(format_string($quiz->name));
if (trim(strip_tags($quiz->intro))) {
print_simple_box(format_text($quiz->intro), 'center', '70%', '', 5, 'generalbox', 'intro');
print_simple_box(format_text($quiz->intro), "center");
}
if ($quiz->attempts > 1) {
echo "<p align=\"center\">".get_string("attemptsallowed", "quiz").": $quiz->attempts</p>";
echo "<p align=\"center\">".get_string("grademethod", "quiz").": ".$QUIZ_GRADE_METHOD[$quiz->grademethod]."</p>";
} else {
echo "<br />";
}
if ($available) {
if ($quiz->timelimit) {
echo "<p align=\"center\">".get_string("quiztimelimit","quiz", format_time($quiz->timelimit * 60))."</p>";
}
echo "<p align=\"center\">".get_string("quizavailable", "quiz", userdate($quiz->timeclose));
} else if ($timenow < $quiz->timeopen) {
echo "<p align=\"center\">".get_string("quiznotavailable", "quiz", userdate($quiz->timeopen));
} else {
echo "<p align=\"center\">".get_string("quizclosed", "quiz", userdate($quiz->timeclose));
}
// This is all the teacher will get
if ($isteacher) {
print_footer($course);
exit;
}
if (isguest()) {
print_heading(get_string("guestsno", "quiz"));
$wwwroot = $CFG->wwwroot.'/login/index.php';
if (!empty($CFG->loginhttps)) {
$wwwroot = str_replace('http','https', $wwwroot);
}
notice_yesno(get_string('guestno', 'quiz').'<br /><br />'.get_string('liketologin'),
$wwwroot, $_SERVER['HTTP_REFERER']);
print_footer($course);
exit;
}
@@ -109,11 +135,10 @@
$numattempts = 0;
}
if ($quiz->attempts > 1) {
echo "<p align=\"center\">".get_string("attemptsallowed", "quiz").": $quiz->attempts</p>";
echo "<p align=\"center\">".get_string("grademethod", "quiz").": ".$QUIZ_GRADE_METHOD[$quiz->grademethod]."</p>";
} else {
echo "<br />";
$unfinished = false;
if ($unfinishedattempt = quiz_get_user_attempt_unfinished($quiz->id, $USER->id)) {
$attempts[] = $unfinishedattempt;
$unfinished = true;
}
$strattempt = get_string("attempt", "quiz");
@@ -125,7 +150,7 @@
$mygrade = quiz_get_best_grade($quiz, $USER->id);
if ($numattempts) {
if ($numattempts) {
if ($quiz->grade and $quiz->sumgrades) {
if ($quiz->grade <> $quiz->sumgrades) {
$table->head = array($strattempt, $strtimetaken, $strtimecompleted, "$strmarks / $quiz->sumgrades", "$strgrade / $quiz->grade");
@@ -142,61 +167,88 @@
$table->size = array("", "", "");
}
foreach ($attempts as $attempt) {
if ($timetaken = ($attempt->timefinish - $attempt->timestart)) {
$timetaken = format_time($timetaken);
$timetaken = '';
$datecompleted = '';
if ($attempt->timefinish > 0) {
$timetaken = format_time($attempt->timefinish - $attempt->timestart);
$datecompleted = userdate($attempt->timefinish);
} else if ($available) {
$timetaken = format_time(time() - $attempt->timestart);
$strconfirmstartattempt = addslashes(get_string("confirmstartattempt","quiz"));
$datecompleted = "\n".'<script language="javascript" type="text/javascript">';
$datecompleted .= "\n<!--\n"; // -->
if (!empty($quiz->popup)) {
$datecompleted .= "var windowoptions = 'left=0, top=0, height='+window.screen.height+
', width='+window.screen.width+', channelmode=yes, fullscreen=yes, scrollbars=yes, '+
'resizeable=no, directories=no, toolbar=no, titlebar=no, location=no, status=no, '+
'menubar=no';\n";
$jslink = 'javascript:';
if ($quiz->timelimit) {
$jslink .= "if (confirm('$strconfirmstartattempt')) ";
}
$jslink .= "var popup = window.open(\\'attempt.php?id=$cm->id\\', \\'quizpopup\\', windowoptions);";
} else {
$jslink = "attempt.php?id=$cm->id";
}
$linktext = get_string('continueattemptquiz', 'quiz');
$datecompleted .= "document.write('<a href=\"$jslink\" alt=\"$linktext\">$linktext</a>');";
$datecompleted .= "\n-->\n";
$datecompleted .= '</script>';
$datecompleted .= '<noscript>';
$datecompleted .= '<strong>'.get_string('noscript', 'quiz').'</strong>';
$datecompleted .= '</noscript>';
} else {
$timetaken = "-";
$timetaken = format_time($quiz->timeclose - $attempt->timestart);
$datecompleted = userdate($quiz->timeclose);
}
if ($quiz->grade and $quiz->sumgrades) {
$attemptmark = '';
$attemptmark = format_float($attempt->sumgrades,$quiz->decimalpoints);
$attemptgrade = format_float(($attempt->sumgrades/$quiz->sumgrades)*$quiz->grade,$quiz->decimalpoints);
if ($attemptgrade == $mygrade) {
$attemptgrade = "<span class=\"highlight\">$attemptgrade</span>";
}
if (quiz_review_allowed($quiz)) {
$attemptmark = "<a href=\"review.php?q=$quiz->id&amp;attempt=$attempt->id\">$attempt->sumgrades</a>";
// if attempt is closed and review is allowed then make attemptnumber and
// mark and grade into links to review page
if (quiz_review_allowed($quiz) and $attempt->timefinish > 0) {
$attemptmark = "<a href=\"review.php?q=$quiz->id&amp;attempt=$attempt->id\">".format_float($attempt->sumgrades,$quiz->decimalpoints).'</a>';
$attemptgrade = "<a href=\"review.php?q=$quiz->id&amp;attempt=$attempt->id\">$attemptgrade</a>";
$attempt->attempt = "<a href=\"review.php?q=$quiz->id&amp;attempt=$attempt->id\">#$attempt->attempt</a>";
}
if ($quiz->grade <> $quiz->sumgrades) {
$table->data[] = array( $attempt->attempt,
format_time($attempt->timefinish - $attempt->timestart),
userdate($attempt->timefinish),
$table->data[] = array( $attempt->attempt,
$timetaken,
$datecompleted,
$attemptmark, $attemptgrade);
} else {
$table->data[] = array( $attempt->attempt,
format_time($attempt->timefinish - $attempt->timestart),
userdate($attempt->timefinish),
$table->data[] = array( $attempt->attempt,
$timetaken,
$datecompleted,
$attemptgrade);
}
} else { // No grades are being used
if (quiz_review_allowed($quiz)) {
$attempt->attempt = "<a href=\"review.php?q=$quiz->id&amp;attempt=$attempt->id\">#$attempt->attempt</a>";
if($attempt->timefinish > 0) {
$attempt->attempt = "<a href=\"review.php?q=$quiz->id&amp;attempt=$attempt->id\">#$attempt->attempt</a>";
} else {
$attempt->attempt = "<a href=\"attempt.php?id=$id\">#$attempt->attempt</a>";
}
}
$table->data[] = array( $attempt->attempt,
format_time($attempt->timefinish - $attempt->timestart),
userdate($attempt->timefinish));
$table->data[] = array( $attempt->attempt,
$timetaken,
$datecompleted);
}
}
print_table($table);
}
if ($available) {
if ($quiz->timelimit) {
echo "<p align=\"center\">".get_string("quiztimelimit","quiz", format_time($quiz->timelimit * 60))."</p>";
}
echo "<p align=\"center\">".get_string("quizavailable", "quiz", userdate($quiz->timeclose));
} else if ($timenow < $quiz->timeopen) {
echo "<p align=\"center\">".get_string("quiznotavailable", "quiz", userdate($quiz->timeopen));
} else {
echo "<p align=\"center\">".get_string("quizclosed", "quiz", userdate($quiz->timeclose));
}
if (!$quiz->questions) {
print_heading(get_string("noquestions", "quiz"));
} else {
if ($numattempts < $quiz->attempts or !$quiz->attempts) {
if ($numattempts < $quiz->attempts or !$quiz->attempts) {
if ($available) {
$options["id"] = $cm->id;
if ($numattempts and $quiz->grade) {
@@ -216,22 +268,24 @@
if ($quiz->grade) {
print_heading(get_string("yourfinalgradeis", "quiz", "$mygrade / $quiz->grade"));
}
print_continue('../../course/view.php?id='.$course->id);
}
}
// Finish the page
echo '</td></tr></table>';
print_footer($course);
function quiz_review_allowed($quiz) {
if (!$quiz->review) {
// If not even responses are to be shown in review then we
// don't allow any review
if (!($quiz->review & QUIZ_REVIEW_RESPONSES)) {
return false;
}
if ((time() < $quiz->timeclose) and ($quiz->review == QUIZ_REVIEW_AFTER)) {
if ((time() < $quiz->timeclose) and !($quiz->review & QUIZ_REVIEW_OPEN)) {
return false;
}
if ((time() > $quiz->timeclose) and ($quiz->review == QUIZ_REVIEW_BEFORE)) {
if ((time() > $quiz->timeclose) and !($quiz->review & QUIZ_REVIEW_CLOSED)) {
return false;
}
return true;
+2 -2
View File
@@ -4,13 +4,13 @@ defined('MOODLE_INTERNAL') or die('Direct access to this script is forbidden.');
$window = (!empty($quiz->popup)) ? 'quizpopup' : '_self';
$windowoptions = ($window == '_self') ? '' : "left=0, top=0, height='+window.screen.height+', width='+window.screen.width+', channelmode=yes, fullscreen=yes, scrollbars=yes, resizeable=no, directories=no, toolbar=no, titlebar=no, location=no, status=no, menubar=no";
$buttontext = ($numattempts) ? get_string('reattemptquiz', 'quiz') : get_string('attemptquiznow', 'quiz');
$buttontext = ($unfinished) ? get_string('continueattemptquiz', 'quiz') : $buttontext;
?>
<script language="javascript" type="text/javascript">
<!--
document.write('<input type="button" value="<?php echo $buttontext ?>" '+
'onclick="javascript: <?php if ($quiz->timelimit) echo "if (confirm(\\'$strconfirmstartattempt\\'))"; ?> '+
'onclick="javascript: <?php if ($quiz->timelimit and !$unfinished) echo "if (confirm(\\'$strconfirmstartattempt\\'))"; ?> '+
'window.open(\'attempt.php?id=<?php echo $cm->id ?>\', \'<?php echo $window ?>\', \'<?php echo $windowoptions ?>\'); " />');
// -->
</script>
+1 -1
View File
@@ -6,7 +6,7 @@
// This is compared against the values stored in the database to determine
// whether upgrades should be performed (see lib/db/*.php)
$version = 2005043000; // YYYYMMDD = date
$version = 2005050600; // YYYYMMDD = date
// XY = increments within a single day
$release = '1.5 ALPHA'; // Human-friendly version name