error('Questions missing'); } // Load the question type specific information if (!get_question_options($closequestions)) { error('Could not load question options'); } // Restore the question sessions if (!($closestates = get_question_states($closequestions, $quiz, $attempt))) { error('Could not restore question sessions'); } $success = true; foreach ($closequestions as $key => $question) { $action->event = QUESTION_EVENTCLOSE; $action->responses = $closestates[$key]->responses; $action->timestamp = $closestates[$key]->timestamp; if (question_process_responses($question, $closestates[$key], $action, $quiz, $attempt)) { save_question_session($question, $closestates[$key]); } else { $success = false; } } if (!$success) { $pagebit = ''; if ($page) { $pagebit = '&page=' . $page; } print_error('errorprocessingresponses', 'question', $CFG->wwwroot . '/mod/quiz/attempt.php?q=' . $quiz->id . $pagebit); } 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
/** * 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; }
/** * 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; }
$historylength++; $curstate =& $states[$historylength][$id]; $curstate->changed = false; // 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 ? QUESTION_EVENTCLOSE : QUESTION_EVENTSUBMIT; if ($actions = question_extract_responses($questions, $form, $event)) { $actions[$id]->timestamp = 0; // We do not care about timelimits here if (!question_process_responses($questions[$id], $curstate, $actions[$id], $quiz, $attempt)) { unset($SESSION->quizpreview); print_error('errorprocessingresponses', 'question', $url->out()); } if (!$curstate->changed) { // 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]; }
/// We have been asked to finish attempt, so do that ////////////////////// /// Now load the state of every question, reloading the ones we messed around /// with above. $attemptobj->preload_question_states(); $attemptobj->load_question_states(); /// Move each question to the closed state. $success = true; $attempt = $attemptobj->get_attempt(); foreach ($attemptobj->get_questions() as $id => $question) { $state = $attemptobj->get_question_state($id); $action = new stdClass(); $action->event = QUESTION_EVENTCLOSE; $action->responses = $state->responses; $action->responses['_flagged'] = $state->flagged; $action->timestamp = $state->timestamp; if (question_process_responses($attemptobj->get_question($id), $state, $action, $attemptobj->get_quiz(), $attempt)) { save_question_session($attemptobj->get_question($id), $state); } else { $success = false; } } if (!$success) { print_error('errorprocessingresponses', 'question', $attemptobj->attempt_url(0, $page)); } /// Log the end of this attempt. add_to_log($attemptobj->get_courseid(), 'quiz', 'close attempt', 'review.php?attempt=' . $attemptobj->get_attemptid(), $attemptobj->get_quizid(), $attemptobj->get_cmid()); /// Update the quiz attempt record. $attempt->timemodified = $timenow; $attempt->timefinish = $timenow; $DB->update_record('quiz_attempts', $attempt); if (!$attempt->preview) {
function evaluate_quiz($acode, $jobid, $newattempt, $blended) { global $USER; global $CFG; mtrace("Evaluation QUIZ Processing..." . "<BR><BR>"); try { print "New Attempt is: " . $newattempt . "<BR/>"; $detected_userid = find_userid($acode, $jobid); if ($detected_userid == null or $detected_userid == '') { throw new EvaluationError(get_string('ErrorUserIDEmpty', 'blended'), EvaluationError::USERID_IS_EMPTY); } $user_reg = blended_get_user($detected_userid, $blended); if ($user_reg == null) { throw new EvaluationError(get_string('ErrorUserNotInCourse', 'blended'), EvaluationError::USER_NOT_IN_THIS_COURSE); } $userid = $user_reg->id; mtrace('Obtained USERID value: ' . $userid . " OK. <BR/>"); $quiz = get_quiz($acode); $attempts = quiz_get_user_attempts($quiz->id, $userid, 'all', true); mtrace("Searching quiz... Success." . "<BR/>"); $uniqueid = get_uniqueid($acode); mtrace('Obtained uniqueid: OK. <BR/>'); $timestamp = get_timestamp($acode); mtrace('Obtained timestamp: OK. <BR/>'); if (!get_record('quiz_attempts', 'uniqueid', $uniqueid)) { $newattempt = true; } else { $newattempt = false; mtrace("User {$userid} had opened this attempt already."); } $attemptnumber = 1; if ($newattempt == false) { mtrace('Obtaining user attempt...<BR/>'); set_attempt_unfinished($uniqueid); $attempt = quiz_get_user_attempt_unfinished($quiz->id, $userid); } elseif ($newattempt == true) { mtrace('Creating new attempt...<BR/>'); $attempt = create_new_attempt($quiz, $attemptnumber, $userid, $acode, $uniqueid, $timestamp); // Save the attempt if (!insert_record('quiz_attempts', $attempt)) { throw new EvaluationError(get_string('ErrorCouldNotCreateAttempt', 'blended'), EvaluationError::CREATE_QUIZ_ATTEMPT_ERROR); } // Actualizamos el estado de las imágenes para indicar que ya está creado un nuevo attempt update_images_status($acode, $jobid); } update_question_attempts($uniqueid); // /* mtrace('<BR>Getting questions and question options... '); $questions = get_questions($attempt, $quiz); if (!get_question_options($questions)) { error('Could not load question options'); } mtrace('Success! <BR>'); // print ("<BR>He obtenido questions: "); //print_object($questions); $lastattemptid = false; // if ($attempt->attempt > 1 and $quiz->attemptonlast and !$attempt->preview) { // Find the previous attempt // if (!$lastattemptid = get_field('quiz_attempts', 'uniqueid', 'quiz', $attempt->quiz, 'userid', $attempt->userid, 'attempt', $attempt->attempt-1)) { // error('Could not find previous attempt to build on'); // } //} //print ('He obtenido lastattemptid'); mtrace('Getting question states... '); if (!($states = get_question_states($questions, $quiz, $attempt, $lastattemptid))) { error('Could not restore question sessions'); } mtrace('Success! <BR>'); mtrace('Getting responses... <BR>'); $responses = get_responses($acode, $jobid, $attempt); //print('Estas son las responses:'); //print_object($responses); //$timestamp=time(); $event = 8; $actions = question_extract_responses($questions, $responses, $event); $questionids = get_questionids($acode); // print $questionids; $questionidarray = explode(',', $questionids); $success = true; mtrace('<BR> Processing responses and saving session... '); foreach ($questionidarray as $i) { if (!isset($actions[$i])) { $actions[$i]->responses = array('' => ''); $actions[$i]->event = QUESTION_EVENTOPEN; } $actions[$i]->timestamp = $timestamp; if (question_process_responses($questions[$i], $states[$i], $actions[$i], $quiz, $attempt)) { save_question_session($questions[$i], $states[$i]); } else { $success = false; } } mtrace('Success! <BR>'); // Set the attempt to be finished $timestamp = time(); //$attempt->timefinish = $timestamp; // Update the quiz attempt and the overall grade for the quiz mtrace('<BR> Finishing the attempt... '); // print_object ($attempt); if (set_field('quiz_attempts', 'timefinish', $timestamp, 'uniqueid', $uniqueid) == false) { throw new EvaluationError('Unable to finish the quiz attempt!', EvaluationError::FINISH_QUIZ_ATTEMPT_ERROR); } mtrace('Success! <BR>'); if ($attempt->attempt > 1 || $attempt->timefinish > 0 and !$attempt->preview) { mtrace('<BR> Saving quiz grade... '); quiz_save_best_grade($quiz, $userid); } mtrace('Success! <BR>'); // */ mtrace("Process Done. <BR><BR>"); mtrace("<center> Your quiz has been succesfully evaluated!! </center>"); } catch (EvaluationError $e) { throw $e; } return; }