You are here

function question_process_responses in Quiz 6.6

Same name and namespace in other branches
  1. 6.5 includes/moodle/lib/questionlib.php \question_process_responses()

Processes an array of student responses, grading and saving them as appropriate

Parameters

object $question Full question object, passed by reference:

object $state Full state object, passed by reference:

object $action object with the fields ->responses which: is an array holding the student responses, ->action which specifies the action, e.g., QUESTION_EVENTGRADE, and ->timestamp which is a timestamp from when the responses were submitted by the student.

object $cmoptions:

object $attempt The attempt is passed by reference so that: during grading its ->sumgrades field can be updated

Return value

boolean Indicates success/failure

2 calls to question_process_responses()
get_question_states in includes/moodle/lib/questionlib.php
Loads the most recent state of each question session from the database or create new one.
regrade_question_in_attempt in includes/moodle/lib/questionlib.php
For a given question in an attempt we walk the complete history of states and recalculate the grades as we go along.

File

includes/moodle/lib/questionlib.php, line 1288

Code

function question_process_responses(&$question, &$state, $action, $cmoptions, &$attempt) {
  global $QTYPES;

  // if no responses are set initialise to empty response
  if (!isset($action->responses)) {
    $action->responses = array(
      '' => '',
    );
  }

  // make sure these are gone!
  unset($action->responses['submit'], $action->responses['validate']);

  // Check the question session is still open
  if (question_state_is_closed($state)) {
    return true;
  }

  // If $action->event is not set that implies saving
  if (!isset($action->event)) {
    debugging('Ambiguous action in question_process_responses.', DEBUG_DEVELOPER);
    $action->event = QUESTION_EVENTSAVE;
  }

  // If submitted then compare against last graded
  // responses, not last given responses in this case
  if (question_isgradingevent($action->event)) {
    $state->responses = $state->last_graded->responses;
  }

  // Check for unchanged responses (exactly unchanged, not equivalent).
  // We also have to catch questions that the student has not yet attempted
  $sameresponses = $QTYPES[$question->qtype]
    ->compare_responses($question, $action, $state);
  if (!empty($state->last_graded) && $state->last_graded->event == QUESTION_EVENTOPEN && question_isgradingevent($action->event)) {
    $sameresponses = false;
  }

  // If the response has not been changed then we do not have to process it again
  // unless the attempt is closing or validation is requested
  if ($sameresponses and QUESTION_EVENTCLOSE != $action->event and QUESTION_EVENTVALIDATE != $action->event) {
    return true;
  }

  // Roll back grading information to last graded state and set the new
  // responses
  $newstate = clone $state->last_graded;
  $newstate->responses = $action->responses;
  $newstate->seq_number = $state->seq_number + 1;
  $newstate->changed = true;

  // will assure that it gets saved to the database
  $newstate->last_graded = clone $state->last_graded;
  $newstate->timestamp = $action->timestamp;
  $state = $newstate;

  // Set the event to the action we will perform. The question type specific
  // grading code may override this by setting it to QUESTION_EVENTCLOSE if the
  // attempt at the question causes the session to close
  $state->event = $action->event;
  if (!question_isgradingevent($action->event)) {

    // Grade the response but don't update the overall grade
    if (!$QTYPES[$question->qtype]
      ->grade_responses($question, $state, $cmoptions)) {
      return false;
    }

    // Temporary hack because question types are not given enough control over what is going
    // on. Used by Opaque questions.
    // TODO fix this code properly.
    if (!empty($state->believeevent)) {

      // If the state was graded we need to ...
      if (question_state_is_graded($state)) {
        question_apply_penalty_and_timelimit($question, $state, $attempt, $cmoptions);

        // update the attempt grade
        $attempt->sumgrades -= (double) $state->last_graded->grade;
        $attempt->sumgrades += (double) $state->grade;

        // and update the last_graded field.
        unset($state->last_graded);
        $state->last_graded = clone $state;
        unset($state->last_graded->changed);
      }
    }
    else {

      // Don't allow the processing to change the event type
      $state->event = $action->event;
    }
  }
  else {

    // grading event
    // Unless the attempt is closing, we want to work out if the current responses
    // (or equivalent responses) were already given in the last graded attempt.
    if (QUESTION_EVENTCLOSE != $action->event && QUESTION_EVENTOPEN != $state->last_graded->event && $QTYPES[$question->qtype]
      ->compare_responses($question, $state, $state->last_graded)) {
      $state->event = QUESTION_EVENTDUPLICATE;
    }

    // If we did not find a duplicate or if the attempt is closing, perform grading
    if (!$sameresponses and QUESTION_EVENTDUPLICATE != $state->event or QUESTION_EVENTCLOSE == $action->event) {
      if (!$QTYPES[$question->qtype]
        ->grade_responses($question, $state, $cmoptions)) {
        return false;
      }

      // Calculate overall grade using correct penalty method
      question_apply_penalty_and_timelimit($question, $state, $attempt, $cmoptions);
    }

    // If the state was graded we need to ...
    if (question_state_is_graded($state)) {

      // update the attempt grade
      $attempt->sumgrades -= (double) $state->last_graded->grade;
      $attempt->sumgrades += (double) $state->grade;

      // and update the last_graded field.
      unset($state->last_graded);
      $state->last_graded = clone $state;
      unset($state->last_graded->changed);
    }
  }
  $attempt->timemodified = $action->timestamp;
  return true;
}