You are here

function quiz_take_quiz in Quiz 7.4

Same name and namespace in other branches
  1. 8.4 quiz.module \quiz_take_quiz()
  2. 5.2 quiz.module \quiz_take_quiz()
  3. 5 quiz.module \quiz_take_quiz()
  4. 6.6 quiz.module \quiz_take_quiz()
  5. 6.2 quiz.module \quiz_take_quiz()
  6. 6.3 quiz.module \quiz_take_quiz()
  7. 6.4 quiz.module \quiz_take_quiz()
  8. 6.5 quiz.module \quiz_take_quiz()
  9. 7.6 quiz.module \quiz_take_quiz()
  10. 7 quiz.module \quiz_take_quiz()
  11. 7.5 quiz.module \quiz_take_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

2 calls to quiz_take_quiz()
quiz_take in ./quiz.module
Primary quiz-taking view on 'Take' tab.
quiz_take_pane_content_type_render in plugins/content_types/quiztake_pane.inc
Run-time rendering of the body of the block (content type) See ctools_plugin_examples for more advanced info

File

./quiz.module, line 1975
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);
    }
    elseif (quiz_start_check($quiz, $rid) && _quiz_take_quiz_init($quiz) === FALSE) {
      return array(
        'body' => array(
          '#markup' => '',
        ),
      );
    }
    else {

      //return array('body' => array('#markup' => t('This quiz is closed')));
    }
  }
  $q_passed_validation = FALSE;
  $repeat_until_correct_error = 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'];
  }

  // Check to see if the question navigation form was submitted using the enter key
  // in a browser that doesn't pass a default submit operation value when this happens.
  if (!isset($_POST['op']) && isset($_POST['default_submit'])) {
    $_POST['op'] = $_POST['default_submit'];
  }

  // For quiz progress block
  // To be able to jump from one question to another
  if (isset($_GET['jump_to_question'])) {
    if ($quiz->allow_jumping || _quiz_is_int($_GET['jump_to_question'])) {
      quiz_jump_to($_GET['jump_to_question'], $quiz, $_SESSION['quiz_' . $quiz->nid]['result_id']);

      // Doing drupal_goto() to prevent showing message last question was skipped
      // and also for skip to work as expected.
      drupal_goto("node/{$quiz->nid}/take");
    }
  }
  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...
  }
  elseif ($_POST['op'] == t('Next question')) {
    drupal_goto("node/{$quiz->nid}/take");
  }
  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;
    }
    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');
        $repeat_until_correct_error = TRUE;
        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);
      }
    }

    // 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 && $repeat_until_correct_error === FALSE) {

      // 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->type != 'quiz_directions') {
        $allow_scoring = quiz_allow_score_access($quiz);
        $report_form = drupal_get_form('quiz_report_form', array(
          $report,
        ), TRUE, TRUE, $allow_scoring);
        $label = _quiz_get_num_questions_left() < 1 ? t('See results') : t('Next question');
        $report_form['op'] = array(
          '#type' => 'submit',
          '#value' => $label,
        );
        return $report_form;
      }
    }

    // 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('The number of questions for this quiz have changed. 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) {

        // 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 = "\n            function finished() {\n              // Find all buttons with a name of 'op'.\n              var buttons = jQuery('input[type=submit][name=op], button[type=submit][name=op]');\n              // Filter out the ones that don't have the right op value.\n              buttons = buttons.filter(function() {\n                return this.value == {$button_op1} || this.value == {$button_op2};\n              });\n              if (buttons.length == 1) {\n                // Since only one button was found, this must be it.\n                buttons.click();\n              }\n              else {\n                // Zero, or more than one buttons were found; fall back on a page refresh.\n                window.location = window.location.href;\n              }\n            }\n          ";
          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 {
    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;
}