Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
tree: 3e2d9e9567
Fetching contributors…

Cannot retrieve contributors at this time

file 412 lines (333 sloc) 17.626 kb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412
<?php // $Id$
/**
* Quiz report to help teachers manually grade quiz questions that need it.
*
* @package quiz
* @subpackage reports
*/

// 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($CFG->dirroot . "/mod/quiz/editlib.php");
require_once($CFG->libdir . '/tablelib.php');

/**
* Quiz report to help teachers manually grade quiz questions that need it.
*
* @package quiz
* @subpackage reports
*/
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);

        $this->print_header_and_tabs($cm, $course, $quiz, $reportmode="grading");

        // Check permissions
        $context = get_context_instance(CONTEXT_MODULE, $cm->id);
        if (!has_capability('mod/quiz:grade', $context)) {
            notify(get_string('gradingnotallowed', 'quiz_grading'));
            return true;
        }

        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&amp;q=$quiz->id", "$quiz->id", "$cm->id");

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

            // now go through all of the responses and save them.
            foreach($data->manualgrades as $uniqueid => $response) {
                // get our attempt
                if (! $attempt = get_record('quiz_attempts', 'uniqueid', $uniqueid)) {
                    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, $attempt->userid);
                }
            }
            notify(get_string('changessaved', 'quiz'));
        }

        // our 3 different views
        // the first one displays all of the manually graded questions in the quiz
        // with the number of ungraded attempts for each question

        // the second view displays the users who have answered the essay question
        // and all of their attempts at answering the question

        // the third prints the question with a comment
        // and grade form underneath it

        switch($action) {
            case 'viewquestions':
                $this->view_questions($quiz);
                break;
            case 'viewquestion':
                $this->view_question($quiz, $question);
                break;
            case 'grade':
                $this->print_questions_and_form($quiz, $question);
                break;
        }
        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 boolean
* @todo Look for the TODO in this code to see what still needs to be done
**/
    function view_questions($quiz) {
        global $CFG, $QTYPE_MANUAL;

        $users = get_course_students($quiz->course);

        if(empty($users)) {
            print_heading(get_string("noattempts", "quiz"));
            return true;
        }

        // 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)".
               " AND q.qtype IN ($QTYPE_MANUAL)".
               " ORDER BY q.name";
        if (empty($questionlist) or !$questions = get_records_sql($sql)) {
            print_heading(get_string('noessayquestionsfound', 'quiz'));
            return false;
        }

        notify(get_string('essayonly', 'quiz_grading'));

        // get all the finished attempts by the users
        $userids = implode(', ', array_keys($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&amp;q=$quiz->id&amp;action=viewquestion&amp;questionid=$question->id\">".
$question->name."</a>";
// determine the number of ungraded attempts
$ungraded = 0;
foreach ($attempts as $attempt) {
if (!$this->is_graded($question, $attempt)) {
$ungraded++;
}
}

$table->data[] = array($link, $ungraded);
}
print_table($table);
} else {
print_heading(get_string('noattempts', 'quiz'));
}

return true;
}

/**
* Prints a table with users and their attempts
*
* @return void
* @todo Add current grade to the table
* Finnish documenting
**/
function view_question($quiz, $question) {
global $CFG, $db;

$users = get_course_students($quiz->course);
$userids = implode(',', array_keys($users));
$usercount = count($users);

// set up table
$tablecolumns = array('picture', 'fullname', 'timefinish', 'grade');
$tableheaders = array('', get_string('fullname'), get_string("completedon", "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&amp;q='.$quiz->id.'&amp;action=viewquestion&amp;questionid='.$question->id);

$table->sortable(true);
$table->initialbars($usercount>20); // will show initialbars if there are more than 20 users
$table->pageable(true);
$table->collapsible(true);

$table->column_suppress('fullname');
$table->column_suppress('picture');
$table->column_suppress('grade');

$table->column_class('picture', 'picture');

// attributes in the table tag
$table->set_attribute('cellspacing', '0');
$table->set_attribute('id', 'attempts');
$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 '.sql_concat('u.id', '\'#\'', $db->IfNull('qa.attempt', '0')).' AS userattemptid, qa.id AS attemptid, qa.uniqueid, qa.attempt, qa.timefinish, 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 ('.$userids.') ';
$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.timefinish ASC';
}

// set up the pagesize
$total = count_records_sql('SELECT COUNT(DISTINCT('.sql_concat('u.id', '\'#\'', $db->IfNull('qa.attempt', '0')).')) '.$from.$where);
$table->pagesize(10, $total);

// get the attempts and process them
if ($attempts = get_records_sql($select.$from.$where.$sort,$table->get_page_start(), $table->get_page_size())) {
foreach($attempts as $attempt) {

$picture = print_user_picture($attempt->userid, $quiz->course, $attempt->picture, false, true);

// link to student profile
$userlink = "<a href=\"$CFG->wwwroot/user/view.php?id=$attempt->userid&amp;course=$quiz->course\">".
fullname($attempt, true).'</a>';

if (!$this->is_graded($question, $attempt)) {
$style = 'class="manual-ungraded"';
} else {
$style = 'class="manual-graded"';
}

// link for the attempt
$attemptlink = "<a $style href=\"report.php?mode=grading&amp;action=grade&amp;q=$quiz->id&amp;questionid=$question->id&amp;attemptid=$attempt->attemptid\">".
userdate($attempt->timefinish, get_string('strftimedatetime')).'</a>';

// grade all attempts for this user
$gradelink = "<a href=\"report.php?mode=grading&amp;action=grade&amp;q=$quiz->id&amp;questionid=$question->id&amp;userid=$attempt->userid\">".
get_string('grade').'</a>';

$table->add_data( array($picture, $userlink, $attemptlink, $gradelink) );
}
}

// grade all and "back" links
$links = "<center><a href=\"report.php?mode=grading&amp;action=grade&amp;q=$quiz->id&amp;questionid=$question->id&amp;gradeall=1\">".get_string('gradeall', 'quiz').'</a> | '.
                "<a href=\"report.php?mode=grading&amp;q=$quiz->id&amp;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;
    }

    /**
* Checks to see if a question in a particular attempt is graded
*
* @return boolean
* @todo Finnish documenting this function
**/
    function is_graded($question, $attempt) {
        global $CFG;

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

        return question_state_is_graded($state);
    }

    /**
* Prints questions with comment and grade form underneath each question
*
* @return void
* @todo Finish documenting this function
**/
    function print_questions_and_form($quiz, $question) {
        global $CFG, $db;

        // grade question specific parameters
        $gradeall = optional_param('gradeall', 0, PARAM_INT);
        $userid = optional_param('userid', 0, PARAM_INT);
        $attemptid = optional_param('attemptid', 0, PARAM_INT);

        // TODO get the context, and put in proper roles an permissions checks.
        $context = NULL;

        $questions[$question->id] = &$question;
        $usehtmleditor = can_use_richtext_editor();
        $users = get_course_students($quiz->course);
        $userids = implode(',', array_keys($users));

        // this sql joins the attempts table and the user table
        $select = 'SELECT '.sql_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 ('.$userids.') ';
        } 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="'.sesskey().'">'.
            '<input type="hidden" name="action" value="viewquestion">'.
            '<input type="hidden" name="questionid" value="'.$question->id.'">';

        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, $context);
            unset($options->questioncommentlink);
            $copy = $state->manualcomment;
            $state->manualcomment = '';

            $options->readonly = 1;

            // print the user name, attempt count, the question, and some more hidden fields
            echo '<div align="center" width="80%" style="padding:15px;">'.
                fullname($attempt, true).': '.
                get_string('attempt', 'quiz').$attempt->attempt;

            print_question($question, $state, '', $quiz, $options);

            $prefix = "manualgrades[$attempt->uniqueid]";
            $grade = round($state->last_graded->grade, 3);
            $state->manualcomment = $copy;

            include($CFG->dirroot . '/question/comment.html');

            echo '</div>';
        }
        echo '<div align="center"><input type="submit" value="'.get_string('savechanges').'"></div>'.
            '</form>';

        if ($usehtmleditor) {
            use_html_editor();
        }
    }

}

?>
Something went wrong with that request. Please try again.