function quiz_take_quiz

mooc7 quiz.module quiz_take_quiz($quiz)

Handles quiz taking.

This gets executed when the main quiz node is first loaded.

Parameters

$quiz: The quiz node.

Return value

Content array.

Related topics

1 call to quiz_take_quiz()
quiz_take in modules/contrib/quiz/quiz.module
Primary quiz-taking view on 'Take' tab.

File

modules/contrib/quiz/quiz.module, line 1865
Quiz Module

Code

function quiz_take_quiz($quiz) {
  global $user;
  $allow_skipping = $quiz->allow_skipping;

  if (!isset($quiz)) {
    drupal_not_found();
    return;
  }

  // If anonymous user and no unique hash, refresh with a unique string to
  // prevent caching.
  if (!$user->uid && arg(4) != NULL) {
    drupal_goto('node/' . $quiz->nid . '/take/' . md5(mt_rand() . time()));
  }

  // Make sure we use the same revision of the quiz throughout the quiz taking
  // session.
  if (isset($_SESSION['quiz_' . $quiz->nid]['quiz_vid']) && $quiz->vid != $_SESSION['quiz_' . $quiz->nid]['quiz_vid'] && variable_get('quiz_auto_revisioning')) {
    $quiz = node_load($quiz->nid, $_SESSION['quiz_' . $quiz->nid]['quiz_vid']);
  }

  // If the session has no data for this quiz.
  if (!isset($_SESSION['quiz_' . $quiz->nid]['quiz_questions'])) {
    // We delete questions in progress from old revisions.
    _quiz_delete_old_in_progress($quiz, $user->uid);

    // See if the current user has progress for this revision of the quiz stored
    // in the database
    $rid = $user->uid > 0 ? _quiz_active_result_id($user->uid, $quiz->nid, $quiz->vid) : 0;

    // Are we resuming an in-progress quiz?
    if ($quiz->allow_resume && $rid > 0) {
      _quiz_resume_existing_quiz($quiz, $user->uid, $rid);

    }

    // First time running through quiz.
    elseif (quiz_start_check($quiz, $rid)) {
      _quiz_take_quiz_init($quiz);

    }

    else {
      return array('body' => array('#markup' => t('This quiz is closed')));
    }
  }

  $q_passed_validation = FALSE;

  if (quiz_availability($quiz) !== TRUE) {
    drupal_set_message(t('This quiz is not available anymore.'), 'error');
    return array('body' => array('#markup' => t('This quiz is closed')));
  }

  if (isset($_SESSION['quiz_' . $quiz->nid]['question_duration'])) {
    $_SESSION['quiz_' . $quiz->nid]['question_duration'] -= REQUEST_TIME - $_SESSION['quiz_' . $quiz->nid]['question_start_time'];
  }

  if (!isset($_POST['op'])) {
    // @todo Starting new quiz... Do we need to show instructions here?
  }
  elseif (isset($_POST['question_nid']) && ($_POST['question_nid'] != $_SESSION['quiz_' . $quiz->nid]['quiz_questions'][0]['nid'])) {
    // The user has pressed the navigation buttons multiple times...
  }
  elseif (isset($_SESSION['quiz_' . $quiz->nid]['question_duration']) && $_SESSION['quiz_' . $quiz->nid]['question_duration'] < -2) {
    // Timed quiz where the time has gone out 2 seconds ago. Do not store the users results...
  }
  // Workaround to show question and feedback in separage pages, when the
  // feedback time it set to show after each questions
  elseif ($_POST['op'] == t('Next question')) {
    drupal_goto("node/{$quiz->nid}/take");
  }
  // We maintain two lists: previous questions and upcomming questions.
  // When we go backward, we pop one from the previous and prepend it to the
  // upcomming.
  // @todo This can be maintained more efficiently with a single array of all
  //   questions and then a pointer to the current question. That makes
  //   rewinding much easier.
  // Check for answer submission.
  elseif ($_POST['op'] == t('Finish') || $_POST['op'] == t('Next') || $_POST['op'] == t('Back') && $quiz->backwards_navigation) {
    // Previous quiz questions: Questions that have been asked already. We save
    // a record of all of them so that a user can navigate backward all the way
    // to the beginning of the quiz.
    $_SESSION['quiz_' . $quiz->nid]['quiz_questions'][0]['rid'] = $_SESSION['quiz_' . $quiz->nid]['result_id'];
    $_SESSION['quiz_' . $quiz->nid]['previous_quiz_questions'][] = $_SESSION['quiz_' . $quiz->nid]['quiz_questions'][0];
    $former_question_array = array_shift($_SESSION['quiz_' . $quiz->nid]['quiz_questions']);
    $former_question = node_load($former_question_array['nid'], $former_question_array['vid']);

    // Call hook_evaluate_question().
    $types = _quiz_get_question_types();
    $module = $types[$former_question->type]['module'];
    $result = module_invoke($module, 'evaluate_question', $former_question, $_SESSION['quiz_' . $quiz->nid]['result_id']);
    $q_passed_validation = $result->is_valid;
    $check_jump = TRUE;
    if ($q_passed_validation === TRUE) {
      quiz_store_question_result($quiz, $result, array('set_msg' => TRUE, 'question_data' => $former_question_array));
    }
    elseif ($quiz->allow_jumping && _quiz_is_int($_POST['jump_to_question'])) {
      $_POST['op'] = t('Leave blank');
      $allow_skipping = TRUE;
      $jumping = TRUE;
    }

    // Stash feedback in the session, since the $_POST gets cleared.
    if ($quiz->feedback_time == QUIZ_FEEDBACK_QUESTION && $_POST['op'] != t('Back') && $q_passed_validation === TRUE) {
      // Invoke hook_get_report().
      $report = module_invoke($module, 'get_report', $former_question_array['nid'], $former_question_array['vid'], $_SESSION['quiz_' . $quiz->nid]['result_id']);
      $path = drupal_get_path('module', 'quiz');
      require_once DRUPAL_ROOT . '/' . $path . '/quiz.pages.inc';
      if ($report) {
        $report_form = drupal_get_form('quiz_report_form', array($report), TRUE, TRUE, TRUE);
        $report_form['op'] = array(
          '#type' => 'submit',
          '#value' => t('Next question'),
        );
        return $report_form;
      }
    }

    if ($quiz->repeat_until_correct && $_POST['op'] != t('Back') && $q_passed_validation === TRUE) {
      // If the question was answered incorrectly, repeat it
      if ($result && !$result->is_correct && $result->is_evaluated) {
        $last_q = array_pop($_SESSION['quiz_' . $quiz->nid]['previous_quiz_questions']);
        array_unshift($_SESSION['quiz_' . $quiz->nid]['quiz_questions'], $last_q);
        drupal_set_message(t('The answer was incorrect. Please try again.'), 'error');
        unset($_SESSION['quiz_' . $quiz->nid]['feedback']);
      }
    }
    elseif ($_POST['op'] == t('Back') && $quiz->backwards_navigation) {
      $quiz_id = 'quiz_' . $quiz->nid;
      // We jump back two times. From the next question to the current, and then
      // from the current to the previous.
      for ($i = 0; $i < 2; $i++) {
        $last_q = array_pop($_SESSION[$quiz_id]['previous_quiz_questions']);
        array_unshift($_SESSION[$quiz_id]['quiz_questions'], $last_q);
      }
    }
    // If anonymous user, refresh url with unique hash to prevent caching.
    if (!$user->uid && $q_passed_validation === TRUE) {
      drupal_goto('node/' . $quiz->nid . '/take', array('query' => array('quizkey' => md5(mt_rand() . REQUEST_TIME))));
    }
  }
  // Check for a skip.
  if (isset($_POST['op']) && ($_POST['op'] == t('Leave blank') || $_POST['op'] == t('Leave blank and finish')) && $allow_skipping) {

    if (!isset($_SESSION['quiz_' . $quiz->nid]['result_id'])) {
      $_SESSION['quiz_' . $quiz->nid]['result_id'] = quiz_create_rid($quiz);
    }
    $q_passed_validation = TRUE;
    // Advance the question.
    if (!isset($jumping) || (isset($jumping) && !$jumping)) {
      $_SESSION['quiz_' . $quiz->nid]['previous_quiz_questions'][] = $_SESSION['quiz_' . $quiz->nid]['quiz_questions'][0];

      // Load the last asked question.
      $former_question_array = array_shift($_SESSION['quiz_' . $quiz->nid]['quiz_questions']);
      $former_question = node_load($former_question_array['nid'], $former_question_array['vid']);
    }
    // Call hook_skip_question().
    $module = quiz_question_module_for_type($former_question->type);
    if (!$module) {
      return array('body' => array('#markup' => ' '));
    }
    $result = module_invoke($module, 'skip_question', $former_question, $_SESSION['quiz_' . $quiz->nid]['result_id']);


    // Store that the question was skipped:
    quiz_store_question_result($quiz, $result, array('set_msg' => TRUE, 'question_data' => $former_question_array));
  }

  if (isset($check_jump) && $check_jump) {
    if ($quiz->allow_jumping && _quiz_is_int($_POST['jump_to_question'])) {
      quiz_jump_to($_POST['jump_to_question'], $quiz, $_SESSION['quiz_' . $quiz->nid]['result_id']);
    }
  }
  $show_validation_message = FALSE;

  // If this quiz is in progress, load the next questions and return it via the theme.
  if (!empty($_SESSION['quiz_' . $quiz->nid]['quiz_questions']) || is_string($q_passed_validation)) {

    // If we got no error when validating the question
    if (!is_string($q_passed_validation) || $_POST['op'] == t('Back') && $quiz->backwards_navigation) {
      $question_node = node_load($_SESSION['quiz_' . $quiz->nid]['quiz_questions'][0]['nid'], $_SESSION['quiz_' . $quiz->nid]['quiz_questions'][0]['vid']);
      if (isset($_SESSION['quiz_' . $quiz->nid]['quiz_questions'][0]['rid'])) {
        $question_node->rid = $_SESSION['quiz_' . $quiz->nid]['quiz_questions'][0]['rid'];
      }
      // We got an error message when trying to validate the previous answer
    }
    else {
      $question_node = $former_question;
      $show_validation_message = TRUE;
      array_unshift($_SESSION['quiz_' . $quiz->nid]['quiz_questions'], $former_question_array);
      if (is_array($_SESSION['quiz_' . $quiz->nid]['previous_quiz_questions'])) {
        array_pop($_SESSION['quiz_' . $quiz->nid]['previous_quiz_questions']);
      }
      // Avoid caching for anonymous users
      if (!$user->uid) {
        drupal_set_message($q_passed_validation, 'error');
        drupal_goto('node/' . $quiz->nid . '/take', array('query' => array('quizkey' => md5(mt_rand() . REQUEST_TIME))));
      }
    }

    // Added the progress info to the view.
    $number_of_questions = quiz_get_number_of_questions($quiz->vid);

    $question_number = $number_of_questions - count($_SESSION['quiz_' . $quiz->nid]['quiz_questions']);
    $question_node->question_number = $question_number;
    $content['progress']['#markup'] = theme('quiz_progress', array(
      'question_number' => $question_number,
      'num_questions' => $number_of_questions,
      'allow_jumping' => $quiz->allow_jumping,
      'time_limit' => $quiz->time_limit,
    ));
    $content['progress']['#weight'] = -50;
    if (count($_SESSION['quiz_' . $quiz->nid]['quiz_questions']) + count($_SESSION['quiz_' . $quiz->nid]['previous_quiz_questions']) > $number_of_questions) {
      drupal_set_message(t('At least one question have been deleted from the quiz after you started taking it. You will have to start over.'), 'warning', FALSE);
      unset($_SESSION['quiz_' . $quiz->nid]);
      drupal_goto('node/' . $quiz->nid . '/take');
    }

    if (isset($_SESSION['quiz_' . $quiz->nid]['question_duration'])) {
      $time = $_SESSION['quiz_' . $quiz->nid]['question_duration'];
      if ($time < 1 && $time > -2) {
        // The page was probably submitted by the js, we allow the data to be stored
        $time = 1;
      }
      db_update('quiz_node_results')->fields(array('time_left' => $time))->condition('result_id', $_SESSION['quiz_' . $quiz->nid]['result_id'])->execute();

      if ($time <= 1) {
        // Quiz has been timed out, run a loop to mark the remaining questions
        // as skipped.
        quiz_jump_to(count($_SESSION['quiz_' . $quiz->nid]['quiz_questions']) + count($_SESSION['quiz_' . $quiz->nid]['previous_quiz_questions']) + 1, $quiz, $_SESSION['quiz_' . $quiz->nid]['result_id']);
        $quiz_end = TRUE;
        unset($content['progress']);
        $show_validation_message = FALSE;
        drupal_set_message(t('You have run out of time.'), 'error');
      }
      else {
        // There is still time left, so let's go ahead and insert the countdown
        // javascript.
        if (function_exists('jquery_countdown_add') && variable_get('quiz_has_timer', 1)) {
          jquery_countdown_add('.countdown', array('until' => $time, 'onExpiry' => 'finished', 'compact' => TRUE, 'layout' => t('Time left') . ': {hnn}{sep}{mnn}{sep}{snn}'));
          // These are the two button op values that are accepted for answering
          // questions.
          $button_op1 = drupal_json_encode(t('Finish'));
          $button_op2 = drupal_json_encode(t('Next'));
          $js = "
            function finished() {
              // Find all buttons with a name of 'op'.
              var buttons = jQuery('input[type=submit][name=op], button[type=submit][name=op]');
              // Filter out the ones that don't have the right op value.
              buttons = buttons.filter(function() {
                return this.value == $button_op1 || this.value == $button_op2;
              });
              if (buttons.length == 1) {
                // Since only one button was found, this must be it.
                buttons.click();
              }
              else {
                // Zero, or more than one buttons were found; fall back on a page refresh.
                window.location = window.location.href;
              }
            }
          ";
          drupal_add_js($js, array('type' => 'inline', 'scope' => JS_DEFAULT));
        }
      }
      $_SESSION['quiz_' . $quiz->nid]['question_start_time'] = REQUEST_TIME;
    }
    if ($show_validation_message) {
      drupal_set_message($q_passed_validation, 'error');
    }

    // If we're not yet at the end.
    if (empty($quiz_end)) {
      $content['body']['question']['#markup'] = quiz_take_question_view($question_node, $quiz);
      $content['body']['question']['#weight'] = 0;
      // If we had feedback from the last question.
      if (isset($_SESSION['quiz_' . $quiz->nid]['feedback']) && $quiz->feedback_time == QUIZ_FEEDBACK_QUESTION) {
        $content['body']['feedback']['#markup'] = rawurldecode($_SESSION['quiz_' . $quiz->nid]['feedback']);
        $content['body']['feedback']['#weight'] = -100;
      }
      drupal_set_title($quiz->title);
      unset($_SESSION['quiz_' . $quiz->nid]['feedback']);
    }
  }
  // Else we're at the end of quiz.
  else {
    drupal_set_title($quiz->title);
    $quiz_end = TRUE;
  }

  // If we're at the end of the quiz.
  if (!empty($quiz_end)) {
    // IMPORTANT: Because of a bug _quiz_get_answers always have to be called before quiz_end_scoring... :/
    $questions = _quiz_get_answers($quiz, $_SESSION['quiz_' . $quiz->nid]['result_id']);
    $score = quiz_end_scoring($quiz, $_SESSION['quiz_' . $quiz->nid]['result_id']);
    if ($quiz->feedback_time == QUIZ_FEEDBACK_NEVER) {
      $content['body']['#markup'] = theme('quiz_no_feedback');
    }
    else {
      // Get the results and summary text for this quiz.
      $summary = _quiz_get_summary_text($quiz, $score);
      $data = array(
        'quiz' => $quiz,
        'questions' => $questions,
        'score' => $score,
        'summary' => $summary,
        'rid' => $_SESSION['quiz_' . $quiz->nid]['result_id'],
      );
      // Get the themed summary page.
      $content['body']['#markup'] = theme('quiz_take_summary', $data);
    }
    if ($score['is_evaluated']) {
      _quiz_maintain_results($quiz, $_SESSION['quiz_' . $quiz->nid]['result_id']);
    }

    // Remove session variables, save $rid
    $session_data = $_SESSION['quiz_' . $quiz->nid];
    unset($_SESSION['quiz_' . $quiz->nid]);
    // NOTE: End actions might redirect the user somewhere. Code below this line might not get executed...
    quiz_end_actions($quiz, $score, $session_data);
  }

  return $content;
}