Beispiel #1
0
/**
* For a given question in an attempt we walk the complete history of states
* and recalculate the grades as we go along.
*
* This is used when a question is changed and old student
* responses need to be marked with the new version of a question.
*
* TODO: Make sure this is not quiz-specific
*
* @return boolean            Indicates whether the grade has changed
* @param object  $question   A question object
* @param object  $attempt    The attempt, in which the question needs to be regraded.
* @param object  $cmoptions
* @param boolean $verbose    Optional. Whether to print progress information or not.
*/
function regrade_question_in_attempt($question, $attempt, $cmoptions, $verbose = false)
{
    // load all states for this question in this attempt, ordered in sequence
    if ($states = get_records_select('question_states', "attempt = '{$attempt->uniqueid}' AND question = '{$question->id}'", 'seq_number ASC')) {
        $states = array_values($states);
        // Subtract the grade for the latest state from $attempt->sumgrades to get the
        // sumgrades for the attempt without this question.
        $attempt->sumgrades -= $states[count($states) - 1]->grade;
        // Initialise the replaystate
        $state = clone $states[0];
        $state->manualcomment = get_field('question_sessions', 'manualcomment', 'attemptid', $attempt->uniqueid, 'questionid', $question->id);
        restore_question_state($question, $state);
        $state->sumpenalty = 0.0;
        $replaystate = clone $state;
        $replaystate->last_graded = $state;
        $changed = false;
        for ($j = 1; $j < count($states); $j++) {
            restore_question_state($question, $states[$j]);
            $action = new stdClass();
            $action->responses = $states[$j]->responses;
            $action->timestamp = $states[$j]->timestamp;
            // Change event to submit so that it will be reprocessed
            if (QUESTION_EVENTCLOSE == $states[$j]->event or QUESTION_EVENTGRADE == $states[$j]->event or QUESTION_EVENTCLOSEANDGRADE == $states[$j]->event) {
                $action->event = QUESTION_EVENTSUBMIT;
                // By default take the event that was saved in the database
            } else {
                $action->event = $states[$j]->event;
            }
            if ($action->event == QUESTION_EVENTMANUALGRADE) {
                // Ensure that the grade is in range - in the past this was not checked,
                // but now it is (MDL-14835) - so we need to ensure the data is valid before
                // proceeding.
                if ($states[$j]->grade < 0) {
                    $states[$j]->grade = 0;
                } else {
                    if ($states[$j]->grade > $question->maxgrade) {
                        $states[$j]->grade = $question->maxgrade;
                    }
                }
                $error = question_process_comment($question, $replaystate, $attempt, $replaystate->manualcomment, $states[$j]->grade);
                if (is_string($error)) {
                    notify($error);
                }
            } else {
                // Reprocess (regrade) responses
                if (!question_process_responses($question, $replaystate, $action, $cmoptions, $attempt)) {
                    $verbose && notify("Couldn't regrade state #{$state->id}!");
                }
            }
            // We need rounding here because grades in the DB get truncated
            // e.g. 0.33333 != 0.3333333, but we want them to be equal here
            if (round((double) $replaystate->raw_grade, 5) != round((double) $states[$j]->raw_grade, 5) or round((double) $replaystate->penalty, 5) != round((double) $states[$j]->penalty, 5) or round((double) $replaystate->grade, 5) != round((double) $states[$j]->grade, 5)) {
                $changed = true;
            }
            $replaystate->id = $states[$j]->id;
            $replaystate->changed = true;
            $replaystate->update = true;
            // This will ensure that the existing database entry is updated rather than a new one created
            save_question_session($question, $replaystate);
        }
        if ($changed) {
            // TODO, call a method in quiz to do this, where 'quiz' comes from
            // the question_attempts table.
            update_record('quiz_attempts', $attempt);
        }
        return $changed;
    }
    return false;
}
 function other_cols($colname, $attempt)
 {
     global $QTYPES, $OUTPUT;
     static $states = array();
     if (preg_match('/^qsanswer([0-9]+)$/', $colname, $matches)) {
         if ($attempt->uniqueid == 0) {
             return '-';
         }
         $questionid = $matches[1];
         if (isset($this->gradedstatesbyattempt[$attempt->uniqueid][$questionid])) {
             $stateforqinattempt = $this->gradedstatesbyattempt[$attempt->uniqueid][$questionid];
         } else {
             return '-';
         }
         $question = $this->questions[$questionid];
         restore_question_state($question, $stateforqinattempt);
         if (!$this->is_downloading() || $this->is_downloading() == 'xhtml') {
             $formathtml = true;
         } else {
             $formathtml = false;
         }
         $summary = $QTYPES[$question->qtype]->response_summary($question, $stateforqinattempt, QUIZ_REPORT_RESPONSES_MAX_LEN_TO_DISPLAY, $formathtml);
         if (!$this->is_downloading()) {
             if ($summary) {
                 $link = html_link::make("/mod/quiz/reviewquestion.php?attempt={$attempt->attempt}&question={$question->id}", $summary);
                 $link->add_action(new popup_action('click', $link->url, 'reviewquestion', array('height' => 450, 'width' => 650)));
                 $link->title = $question->formattedname;
                 $summary = $OUTPUT->link($link);
                 if (question_state_is_graded($stateforqinattempt) && $question->maxgrade > 0) {
                     $grade = $stateforqinattempt->grade / $question->maxgrade;
                     $qclass = question_get_feedback_class($grade);
                     $feedbackimg = question_get_feedback_image($grade);
                     $questionclass = "que";
                     return "<span class=\"{$questionclass}\"><span class=\"{$qclass}\">" . $summary . "</span></span>{$feedbackimg}";
                 } else {
                     return $summary;
                 }
             } else {
                 return '';
             }
         } else {
             return $summary;
         }
     } else {
         return NULL;
     }
 }
/**
* For a given question in an attempt we walk the complete history of states
* and recalculate the grades as we go along.
*
* This is used when a question is changed and old student
* responses need to be marked with the new version of a question.
*
* TODO: Make sure this is not quiz-specific
*
* @return boolean            Indicates whether the grade has changed
* @param object  $question   A question object
* @param object  $attempt    The attempt, in which the question needs to be regraded.
* @param object  $cmoptions
* @param boolean $verbose    Optional. Whether to print progress information or not.
* @param boolean $dryrun     Optional. Whether to make changes to grades records
* or record that changes need to be made for a later regrade.
*/
function regrade_question_in_attempt($question, $attempt, $cmoptions, $verbose = false, $dryrun = false)
{
    global $DB;
    // load all states for this question in this attempt, ordered in sequence
    if ($states = $DB->get_records('question_states', array('attempt' => $attempt->uniqueid, 'question' => $question->id), 'seq_number ASC')) {
        $states = array_values($states);
        // Subtract the grade for the latest state from $attempt->sumgrades to get the
        // sumgrades for the attempt without this question.
        $attempt->sumgrades -= $states[count($states) - 1]->grade;
        // Initialise the replaystate
        $replaystate = question_load_specific_state($question, $cmoptions, $attempt, $states[0]->id);
        $replaystate->sumpenalty = 0;
        $replaystate->last_graded->sumpenalty = 0;
        $changed = false;
        for ($j = 1; $j < count($states); $j++) {
            restore_question_state($question, $states[$j]);
            $action = new stdClass();
            $action->responses = $states[$j]->responses;
            $action->timestamp = $states[$j]->timestamp;
            // Change event to submit so that it will be reprocessed
            if (in_array($states[$j]->event, array(QUESTION_EVENTCLOSE, QUESTION_EVENTGRADE, QUESTION_EVENTCLOSEANDGRADE))) {
                $action->event = QUESTION_EVENTSUBMIT;
                // By default take the event that was saved in the database
            } else {
                $action->event = $states[$j]->event;
            }
            if ($action->event == QUESTION_EVENTMANUALGRADE) {
                // Ensure that the grade is in range - in the past this was not checked,
                // but now it is (MDL-14835) - so we need to ensure the data is valid before
                // proceeding.
                if ($states[$j]->grade < 0) {
                    $states[$j]->grade = 0;
                    $changed = true;
                } else {
                    if ($states[$j]->grade > $question->maxgrade) {
                        $states[$j]->grade = $question->maxgrade;
                        $changed = true;
                    }
                }
                if (!$dryrun) {
                    $error = question_process_comment($question, $replaystate, $attempt, $replaystate->manualcomment, $states[$j]->grade);
                    if (is_string($error)) {
                        notify($error);
                    }
                } else {
                    $replaystate->grade = $states[$j]->grade;
                }
            } else {
                // Reprocess (regrade) responses
                if (!question_process_responses($question, $replaystate, $action, $cmoptions, $attempt) && $verbose) {
                    $a = new stdClass();
                    $a->qid = $question->id;
                    $a->stateid = $states[$j]->id;
                    notify(get_string('errorduringregrade', 'question', $a));
                }
                // We need rounding here because grades in the DB get truncated
                // e.g. 0.33333 != 0.3333333, but we want them to be equal here
                if (round((double) $replaystate->raw_grade, 5) != round((double) $states[$j]->raw_grade, 5) or round((double) $replaystate->penalty, 5) != round((double) $states[$j]->penalty, 5) or round((double) $replaystate->grade, 5) != round((double) $states[$j]->grade, 5)) {
                    $changed = true;
                }
                // If this was previously a closed state, and it has been knoced back to
                // graded, then fix up the state again.
                if ($replaystate->event == QUESTION_EVENTGRADE && ($states[$j]->event == QUESTION_EVENTCLOSE || $states[$j]->event == QUESTION_EVENTCLOSEANDGRADE)) {
                    $replaystate->event = $states[$j]->event;
                }
            }
            $replaystate->id = $states[$j]->id;
            $replaystate->changed = true;
            $replaystate->update = true;
            // This will ensure that the existing database entry is updated rather than a new one created
            if (!$dryrun) {
                save_question_session($question, $replaystate);
            }
        }
        if ($changed) {
            if (!$dryrun) {
                // TODO, call a method in quiz to do this, where 'quiz' comes from
                // the question_attempts table.
                $DB->update_record('quiz_attempts', $attempt);
            }
        }
        if ($changed) {
            $toinsert = new object();
            $toinsert->oldgrade = round((double) $states[count($states) - 1]->grade, 5);
            $toinsert->newgrade = round((double) $replaystate->grade, 5);
            $toinsert->attemptid = $attempt->uniqueid;
            $toinsert->questionid = $question->id;
            //the grade saved is the old grade if the new grade is saved
            //it is the new grade if this is a dry run.
            $toinsert->regraded = $dryrun ? 0 : 1;
            $toinsert->timemodified = time();
            $DB->insert_record('quiz_question_regrade', $toinsert);
            return true;
        } else {
            return false;
        }
    }
    return false;
}
$strquizzes = get_string('modulenameplural', 'quiz');
$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.
$questions = array($question->id => &$question);
// Add additional questiontype specific information to the question objects.
if (!get_question_options($questions)) {
    error("Unable to load questiontype specific question information");
}
$baseurl = $CFG->wwwroot . '/mod/quiz/reviewquestion.php?question=' . $question->id . '&amp;number=' . $number . '&amp;attempt=';
$quiz->thispageurl = $baseurl . $attempt->id;
$quiz->cmid = $cm->id;
$session = get_record('question_sessions', 'attemptid', $attempt->uniqueid, 'questionid', $question->id);
$state->sumpenalty = $session->sumpenalty;
$state->manualcomment = $session->manualcomment;
restore_question_state($question, $state);
$state->last_graded = $state;
$options = quiz_get_reviewoptions($quiz, $attempt, $context);
$options->validation = $state->event == QUESTION_EVENTVALIDATE;
$options->history = (has_capability('mod/quiz:viewreports', $context) and !$attempt->preview) ? 'all' : 'graded';
$questionids = array($question->id);
$states = array($question->id => &$state);
$headtags = get_html_head_contributions($questionids, $questions, $states);
print_header('', '', '', '', $headtags);
echo '<div id="overDiv" style="position:absolute; visibility:hidden; z-index:1000;"></div>';
// for overlib
/// Print heading
print_heading(format_string($question->name));
/// Print infobox
$table->align = array("right", "left");
if ($attempt->userid != $USER->id) {
 // Find the previous attempt
 if (!($lastattemptid = get_field('quiz_attempts', 'uniqueid', 'quiz', $attempt->quiz, 'userid', $attempt->userid, 'attempt', $attempt->attempt - 1))) {
     $sloodle->response->quick_output(-701, 'QUIZ', 'Could not find previous attempt to build on.', FALSE);
     exit;
 }
 // For each question find the responses from the previous attempt and save them to the new session
 foreach ($questions as $i => $question) {
     // Load the last graded state for the question
     $statefields = 'n.questionid as question, s.*, n.sumpenalty';
     $sql = "SELECT {$statefields}" . "  FROM {$CFG->prefix}question_states s," . "       {$CFG->prefix}question_sessions n" . " WHERE s.id = n.newgraded" . "   AND n.attemptid = '{$lastattemptid}'" . "   AND n.questionid = '{$i}'";
     if (!($laststate = get_record_sql($sql))) {
         // Only restore previous responses that have been graded
         continue;
     }
     // Restore the state so that the responses will be restored
     restore_question_state($questions[$i], $laststate);
     // prepare the previous responses for new processing
     $action = new stdClass();
     $action->responses = $laststate->responses;
     $action->timestamp = $laststate->timestamp;
     $action->event = QUESTION_EVENTOPEN;
     // Process these responses ...
     question_process_responses($questions[$i], $states[$i], $action, $quiz, $attempt);
     // Fix for Bug #5506: When each attempt is built on the last one,
     // preserve the options from any previous attempt.
     if (isset($laststate->options)) {
         $states[$i]->options = $laststate->options;
     }
     // ... and save the new states
     save_question_session($questions[$i], $states[$i]);
 }
Beispiel #6
0
 /**
  * Get the data for the individual question response analysis table.
  */
 function _process_actual_responses($question, $state)
 {
     global $QTYPES;
     if ($question->qtype != 'random' && $QTYPES[$question->qtype]->show_analysis_of_responses()) {
         $restoredstate = clone $state;
         restore_question_state($question, $restoredstate);
         $responsedetails = $QTYPES[$question->qtype]->get_actual_response_details($question, $restoredstate);
         foreach ($responsedetails as $responsedetail) {
             $responsedetail->questionid = $question->id;
             $this->add_response_detail_to_array($responsedetail);
         }
     }
 }
 function other_cols($colname, $attempt)
 {
     global $QTYPES;
     static $states = array();
     if (preg_match('/^qsanswer([0-9]+)$/', $colname, $matches)) {
         if ($attempt->uniqueid == 0) {
             return '-';
         }
         $questionid = $matches[1];
         if (isset($this->gradedstatesbyattempt[$attempt->uniqueid][$questionid])) {
             $stateforqinattempt = $this->gradedstatesbyattempt[$attempt->uniqueid][$questionid];
         } else {
             return '-';
         }
         $question = $this->questions[$questionid];
         restore_question_state($question, $stateforqinattempt);
         if (!$this->is_downloading() || $this->is_downloading() == 'xhtml') {
             $formathtml = true;
         } else {
             $formathtml = false;
         }
         $summary = $QTYPES[$question->qtype]->response_summary($question, $stateforqinattempt, QUIZ_REPORT_RESPONSES_MAX_LEN_TO_DISPLAY, $formathtml);
         if (!$this->is_downloading()) {
             if ($summary) {
                 $summary = link_to_popup_window('/mod/quiz/reviewquestion.php?attempt=' . $attempt->attempt . '&amp;question=' . $question->id, 'reviewquestion', $summary, 450, 650, get_string('reviewresponse', 'quiz'), 'none', true);
                 if (question_state_is_graded($stateforqinattempt) && $question->maxgrade > 0) {
                     $grade = $stateforqinattempt->grade / $question->maxgrade;
                     $qclass = question_get_feedback_class($grade);
                     $feedbackimg = question_get_feedback_image($grade);
                     $questionclass = "que";
                     return "<span class=\"{$questionclass}\"><span class=\"{$qclass}\">" . $summary . "</span></span>{$feedbackimg}";
                 } else {
                     return $summary;
                 }
             } else {
                 return '';
             }
         } else {
             return $summary;
         }
     } else {
         return NULL;
     }
 }