mirror of
https://github.com/moodle/moodle.git
synced 2025-08-06 09:26:35 +02:00

Moved viewquestions code to function view_questions. This is an attempt to make the code much more readable. All action code will eventually be moved to its own function.
394 lines
19 KiB
PHP
394 lines
19 KiB
PHP
<?php // $Id$
|
|
|
|
// This file allows a teacher to grade essay questions.
|
|
// Could be later expanded to change grades for all question types
|
|
|
|
# Flow of the file:
|
|
# Get variables, run essential queries
|
|
# Check for post data submitted. If exists, then process data (the data is the grades and comments for essay questions)
|
|
# Check for userid, attemptid, or gradeall and for questionid. If found, print out the appropriate essay question attempts
|
|
# Switch:
|
|
# first case: print out all essay questions in quiz and the number of ungraded attempts
|
|
# second case: print out all users and their attempts for a specific essay question
|
|
|
|
|
|
require_once("editlib.php");
|
|
require_once($CFG->libdir.'/tablelib.php');
|
|
|
|
class quiz_report extends quiz_default_report {
|
|
|
|
function display($quiz, $cm, $course) { /// This function just displays the report
|
|
global $CFG, $SESSION, $USER, $db, $QTYPES;
|
|
|
|
$action = optional_param('action', 'viewquestions', PARAM_ALPHA);
|
|
$questionid = optional_param('questionid', 0, PARAM_INT);
|
|
$attemptid = optional_param('attemptid', 0, PARAM_INT);
|
|
$gradeall = optional_param('gradeall', 0, PARAM_INT);
|
|
$userid = optional_param('userid', 0, PARAM_INT);
|
|
|
|
$this->print_header_and_tabs($cm, $course, $quiz, $reportmode="grading");
|
|
|
|
//notice('The manual grading is temporarily disabled during development work', $CFG->wwwroot.'/mod/quiz/report.php?q='.$quiz->id);
|
|
|
|
if (!empty($questionid)) {
|
|
if (! $question = get_record('question', 'id', $questionid)) {
|
|
error("Question with id $questionid not found");
|
|
}
|
|
$question->maxgrade = get_field('quiz_question_instances', 'grade', 'quiz', $quiz->id, 'question', $question->id);
|
|
|
|
// Some of the questions code is optimised to work with several questions
|
|
// at once so it wants the question to be in an array. The array key
|
|
// must be the question id.
|
|
$key = $question->id;
|
|
$questions[$key] = &$question;
|
|
|
|
// We need to add additional questiontype specific information to
|
|
// the question objects.
|
|
if (!get_question_options($questions)) {
|
|
error("Unable to load questiontype specific question information");
|
|
}
|
|
// This will have extended the question object so that it now holds
|
|
// all the information about the questions that may be needed later.
|
|
}
|
|
|
|
add_to_log($course->id, "quiz", "manualgrading", "report.php?mode=grading&q=$quiz->id", "$quiz->id", "$cm->id");
|
|
|
|
/// GROUP CODE FROM ATTEMPTS.PHP no sure how to use just yet... need to update later perhaps
|
|
/// 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&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(get_string("noattempts", "quiz"));
|
|
return true;
|
|
} else {
|
|
// for sql queries
|
|
$userids = implode(', ', array_keys($users));
|
|
}
|
|
|
|
echo '<div id="overDiv" style="position:absolute; visibility:hidden; z-index:1000;"></div>'; // for overlib
|
|
|
|
if ($data = data_submitted()) { // post data submitted, process it
|
|
confirm_sesskey();
|
|
|
|
// first, process all the data to extract the teacher's new responses for the question(s)
|
|
foreach ($data as $key => $response) {
|
|
$keyparts = explode('_', $key); // valid keys are in this format: attemptid_stateid_fieldname
|
|
if (count($keyparts) == 3) { // must have 3 parts to the key
|
|
// re-assign to nice variable names for readability
|
|
$attemptid = $keyparts[0];
|
|
$stateid = $keyparts[1];
|
|
$fieldname = $keyparts[2];
|
|
|
|
$responses[$attemptid.'_'.$stateid][$fieldname] = $response;
|
|
}
|
|
}
|
|
// now go through all of the responses to grade them and save them.
|
|
// not totally sure if this process is correct or fully optimized. I need help here!
|
|
foreach($responses as $ids => $response) {
|
|
// get our necessary ids
|
|
$ids = explode('_', $ids);
|
|
$attemptid = $ids[0];
|
|
$stateid = $ids[1];
|
|
|
|
// get our attempt
|
|
if (! $attempt = get_record('quiz_attempts', 'id', $attemptid)) {
|
|
error('No such attempt ID exists');
|
|
}
|
|
|
|
// Load the state for this attempt (The questions array was created earlier)
|
|
$states = get_question_states($questions, $quiz, $attempt);
|
|
// The $states array is indexed by question id but because we are dealing
|
|
// with only one question there is only one entry in this array
|
|
$state = &$states[$question->id];
|
|
|
|
// the following will update the state and attempt
|
|
question_process_comment($question, $state, $attempt, $response['comment'], $response['grade']);
|
|
|
|
// If the state has changed save it and update the quiz grade
|
|
if ($state->changed) {
|
|
save_question_session($question, $state);
|
|
quiz_save_best_grade($quiz);
|
|
}
|
|
}
|
|
notify(get_string('changessaved', 'quiz'));
|
|
|
|
// Provide grading form for a particular question
|
|
// (either for a particular attempt, a particular user, or for all attempts)
|
|
|
|
// First select the attempts to work on
|
|
|
|
} else if ( ( !empty($attemptid) or !empty($gradeall) or !empty($userid)) and !empty($questionid) ) { // need attemptid and questionid or gradeall and a questionid
|
|
// this sql joins the attempts table and the user table
|
|
$select = 'SELECT '.$db->Concat('u.id', '\'#\'', $db->IfNull('qa.attempt', '0')).' AS userattemptid,
|
|
qa.id AS attemptid, qa.uniqueid, qa.attempt, qa.timefinish, qa.preview,
|
|
u.id AS userid, u.firstname, u.lastname, u.picture ';
|
|
$from = 'FROM '.$CFG->prefix.'user u LEFT JOIN '.$CFG->prefix.'quiz_attempts qa ON (u.id = qa.userid AND qa.quiz = '.$quiz->id.') ';
|
|
|
|
if ($gradeall) { // get all user attempts
|
|
$where = 'WHERE u.id IN ('.implode(',', array_keys($users)).') ';
|
|
} else if ($userid) { // get all the attempts for a specific user
|
|
$where = 'WHERE u.id='.$userid.' ';
|
|
} else { // get a specific attempt
|
|
$where = 'WHERE qa.id='.$attemptid.' ';
|
|
}
|
|
|
|
// ignore previews
|
|
$where .= ' AND preview = 0';
|
|
|
|
$where .= 'AND '.$db->IfNull('qa.attempt', '0').' != 0 ';
|
|
$where .= 'AND '.$db->IfNull('qa.timefinish', '0').' != 0 ';
|
|
$sort = 'ORDER BY u.firstname, u.lastname, qa.attempt ASC';
|
|
$attempts = get_records_sql($select.$from.$where.$sort);
|
|
|
|
// Display the form with one part for each selected attempt
|
|
|
|
echo '<form method="post" action="report.php">'.
|
|
'<input type="hidden" name="mode" value="grading">'.
|
|
'<input type="hidden" name="q" value="'.$quiz->id.'">'.
|
|
'<input type="hidden" name="sesskey" value="'.$USER->sesskey.'">'.
|
|
'<input type="hidden" name="action" value="viewquestion">'.
|
|
'<input type="hidden" name="questionid" value="'.$questionid.'">';
|
|
|
|
foreach ($attempts as $attempt) {
|
|
|
|
// Load the state for this attempt (The questions array was created earlier)
|
|
$states = get_question_states($questions, $quiz, $attempt);
|
|
// The $states array is indexed by question id but because we are dealing
|
|
// with only one question there is only one entry in this array
|
|
$state = &$states[$question->id];
|
|
|
|
$options = quiz_get_reviewoptions($quiz, $attempt, true);
|
|
//$options->history = 'all'; // had this on, but seemed confusing for this
|
|
|
|
$options->readonly = 1;
|
|
$options->regrade = 1;
|
|
|
|
// print the user name, attempt count, the question, and some more hidden fields
|
|
echo '<div align="center" width="80%" style="padding:15px;">'.
|
|
'<p>'."$attempt->firstname $attempt->lastname: ".
|
|
get_string('attempt', 'quiz')." $attempt->attempt".
|
|
'</p>';
|
|
|
|
print_question($question, $state, '', $quiz, $options);
|
|
echo '<input type="hidden" name="attemptids[]" value="'.$attempt->attemptid.'">'.
|
|
'<input type="hidden" name="stateids[]" value="'.$state->id.'">';
|
|
echo '</div>';
|
|
|
|
// TODO: This is where the code for printing the comment box and grade selector
|
|
// should go.
|
|
}
|
|
echo '<div align="center"><input type="submit" value="'.get_string('savechanges').'"></div>'.
|
|
'</form>';
|
|
print_footer($course);
|
|
exit();
|
|
}
|
|
|
|
// our 2 different views
|
|
// the first one displays all of the essay questions in the quiz
|
|
// with the number of ungraded attempts for each essay question
|
|
|
|
// the second view displays the users who have answered the essay question
|
|
// and all of their attempts at answering the question
|
|
switch($action) {
|
|
case 'viewquestions':
|
|
$this->view_questions($quiz, $course, $userids);
|
|
break;
|
|
case 'viewquestion':
|
|
// gonna use flexible_table (first time!)
|
|
$tablecolumns = array('picture', 'fullname', 'attempt');
|
|
$tableheaders = array('', get_string('fullname'), get_string("attempts", "quiz"));
|
|
|
|
$table = new flexible_table('mod-quiz-report-grading');
|
|
|
|
$table->define_columns($tablecolumns);
|
|
$table->define_headers($tableheaders);
|
|
$table->define_baseurl($CFG->wwwroot.'/mod/quiz/report.php?mode=grading&q='.$quiz->id.'&action=viewquestion&questionid='.$question->id);
|
|
|
|
$table->sortable(true);
|
|
$table->initialbars(count($users)>20); // will show initialbars if there are more than 20 users
|
|
$table->pageable(true);
|
|
|
|
$table->column_suppress('fullname');
|
|
$table->column_suppress('picture');
|
|
|
|
$table->column_class('picture', 'picture');
|
|
|
|
// attributes in the table tag
|
|
$table->set_attribute('cellspacing', '0');
|
|
$table->set_attribute('id', 'grading');
|
|
$table->set_attribute('class', 'generaltable generalbox');
|
|
$table->set_attribute('align', 'center');
|
|
$table->set_attribute('width', '50%');
|
|
|
|
// get it ready!
|
|
$table->setup();
|
|
|
|
// this sql is a join of the attempts table and the user table. I do this so I can sort by user name and attempt number (not id)
|
|
$select = 'SELECT '.$db->Concat('u.id', '\'#\'', $db->IfNull('qa.attempt', '0')).' AS userattemptid, qa.id AS attemptid, qa.uniqueid, qa.attempt, u.id AS userid, u.firstname, u.lastname, u.picture ';
|
|
$from = 'FROM '.$CFG->prefix.'user u LEFT JOIN '.$CFG->prefix.'quiz_attempts qa ON (u.id = qa.userid AND qa.quiz = '.$quiz->id.') ';
|
|
$where = 'WHERE u.id IN ('.implode(',', array_keys($users)).') ';
|
|
$where .= 'AND '.$db->IfNull('qa.attempt', '0').' != 0 ';
|
|
$where .= 'AND '.$db->IfNull('qa.timefinish', '0').' != 0 ';
|
|
$where .= 'AND preview = 0 '; // ignore previews
|
|
|
|
if($table->get_sql_where()) { // forgot what this does
|
|
$where .= 'AND '.$table->get_sql_where();
|
|
}
|
|
|
|
// sorting of the table
|
|
if($sort = $table->get_sql_sort()) {
|
|
$sort = 'ORDER BY '.$sort; // seems like I would need to have u. or qa. infront of the ORDER BY attribues... but seems to work..
|
|
} else {
|
|
// my default sort rule
|
|
$sort = 'ORDER BY u.firstname, u.lastname, qa.attempt ASC';
|
|
}
|
|
|
|
// set up the pagesize
|
|
$total = count_records_sql('SELECT COUNT(DISTINCT('.$db->Concat('u.id', '\'#\'', $db->IfNull('qa.attempt', '0')).')) '.$from.$where);
|
|
$table->pagesize(10, $total);
|
|
|
|
// this is for getting the correct records for a given page
|
|
if($table->get_page_start() !== '' && $table->get_page_size() !== '') {
|
|
$limit = ' '.sql_paging_limit($table->get_page_start(), $table->get_page_size());
|
|
} else {
|
|
$limit = '';
|
|
}
|
|
//$number = 1;
|
|
// get the attempts and process them
|
|
if ($attempts = get_records_sql($select.$from.$where.$sort.$limit)) {
|
|
foreach($attempts as $attempt) {
|
|
|
|
$picture = print_user_picture($attempt->userid, $course->id, $attempt->picture, false, true);
|
|
|
|
// link here... grades all for this student
|
|
$userlink = "<a href=\"report.php?mode=grading&q=$quiz->id&questionid=$question->id&userid=$attempt->userid\">".
|
|
$attempt->firstname.' '.$attempt->lastname.'</a>';
|
|
|
|
// nab the state of the attempt to see if it is graded or not
|
|
// TODO: should be changed to use the event field
|
|
if (!$neweststate = get_record('question_sessions', 'attemptid', $attempt->uniqueid, 'questionid', $question->id)) {
|
|
error("Can not find newest states for attempt $attempt->uniqueid for question $questionid");
|
|
}
|
|
|
|
if (!$questionstate = get_record('question_essay_states', 'stateid', $neweststate->newest)) {
|
|
error('Could not find question state');
|
|
}
|
|
// change the color of the link based on being graded or not
|
|
if (!$questionstate->graded) {
|
|
$style = 'style="color:#FF0000"'; // red
|
|
} else {
|
|
$style = 'style="color:#008000"'; // green
|
|
}
|
|
|
|
// link for the attempt
|
|
$attemptlink = "<a $style href=\"report.php?mode=grading&q=$quiz->id&questionid=$question->id&attemptid=$attempt->attemptid\">". // &number=$number
|
|
$question->name." attempt $attempt->attempt</a>";
|
|
|
|
$table->add_data( array($picture, $userlink, $attemptlink) );
|
|
}
|
|
//$number += $question->length;
|
|
}
|
|
|
|
// grade all and "back" links
|
|
$links = "<center><a href=\"report.php?mode=grading&q=$quiz->id&questionid=$questionid&gradeall=1\">".get_string('gradeall', 'quiz').'</a> | '.
|
|
"<a href=\"report.php?mode=grading&q=$quiz->id&action=viewquestions\">".get_string('backtoquestionlist', 'quiz').'</a></center>'.
|
|
|
|
// print everything here
|
|
print_heading($question->name);
|
|
echo $links;
|
|
echo '<div id="tablecontainer">';
|
|
$table->print_html();
|
|
echo '</div>';
|
|
echo $links;
|
|
break;
|
|
default:
|
|
error("Invalid Action");
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Prints a table containing all manually graded questions
|
|
*
|
|
* @param object $quiz Quiz object of the currrent quiz
|
|
* @param object $course Course object of the current course
|
|
* @param string $userids Comma-separated list of userids in this course
|
|
* @return void
|
|
* @todo Look for the TODO in this code to see what still needs to be done
|
|
**/
|
|
function view_questions($quiz, $course, $userids) {
|
|
global $CFG;
|
|
|
|
notify(get_string('essayonly', 'quiz_grading'));
|
|
|
|
// setup the table
|
|
$table = new stdClass;
|
|
$table->head = array(get_string("essayquestions", "quiz"), get_string("ungraded", "quiz"));
|
|
$table->align = array("left", "left");
|
|
$table->wrap = array("wrap", "wrap");
|
|
$table->width = "20%";
|
|
$table->size = array("*", "*");
|
|
$table->data = array();
|
|
|
|
// get the essay questions
|
|
$questionlist = quiz_questions_in_quiz($quiz->questions);
|
|
$sql = "SELECT q.*, i.grade AS maxgrade, i.id AS instance".
|
|
" FROM {$CFG->prefix}question q,".
|
|
" {$CFG->prefix}quiz_question_instances i".
|
|
" WHERE i.quiz = '$quiz->id' AND q.id = i.question".
|
|
" AND q.id IN ($questionlist)".
|
|
// TODO: create an array of manually graded questions OR new function (preferred) in question class $QTYPE->is_manually_graded return boolean
|
|
" AND q.qtype = 'essay'".
|
|
" ORDER BY q.name";
|
|
if (empty($questionlist) or !$questions = get_records_sql($sql)) {
|
|
// TODO: Make this none essay specific
|
|
print_heading(get_string('noessayquestionsfound', 'quiz'));
|
|
print_footer($course);
|
|
exit();
|
|
}
|
|
// get all the finished attempts by the users
|
|
if ($attempts = get_records_select('quiz_attempts', "quiz = $quiz->id and timefinish > 0 AND userid IN ($userids) AND preview = 0", 'userid, attempt')) {
|
|
foreach($questions as $question) {
|
|
|
|
$link = "<a href=\"report.php?mode=grading&q=$quiz->id&action=viewquestion&questionid=$question->id\">".
|
|
$question->name."</a>";
|
|
// determine the number of ungraded attempts
|
|
$ungraded = 0;
|
|
foreach ($attempts as $attempt) {
|
|
// grab the state then check if it is graded
|
|
if (!$state = get_record_sql("SELECT state.id, state.event FROM
|
|
{$CFG->prefix}question_states state, {$CFG->prefix}question_sessions sess
|
|
WHERE sess.newest = state.id AND
|
|
sess.attemptid = $attempt->uniqueid AND
|
|
sess.questionid = $question->id")) {
|
|
error('Could not find question state');
|
|
}
|
|
|
|
if (!question_state_is_graded($state)) {
|
|
$ungraded++;
|
|
}
|
|
}
|
|
|
|
$table->data[] = array($link, $ungraded);
|
|
}
|
|
print_table($table);
|
|
} else {
|
|
print_heading(get_string('noattempts', 'quiz'));
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
?>
|