From c39ca1ea279a78bb65a48d7f2039ca4f933860cf Mon Sep 17 00:00:00 2001
From: Scott Rigby <srigby+dlc-solutions.com>
Date: Tue, 17 Jan 2012 00:09:55 -0500
Subject: [PATCH] Issue #634110 by djdevin, scottrigby: rewrite
 quiz_take_quiz(), FAPI-ize multichoice, and fix
 theme_quiz_progress()

---
 question_types/multichoice/multichoice.classes.inc |   42 ++--
 .../quiz_question/quiz_question.core.inc           |   13 +-
 question_types/quiz_question/quiz_question.module  |  137 ++++++++--
 quiz.install                                       |   19 ++
 quiz.module                                        |  293 +++++++-------------
 quiz.pages.inc                                     |   13 +-
 6 files changed, 268 insertions(+), 249 deletions(-)

diff --git a/question_types/multichoice/multichoice.classes.inc b/question_types/multichoice/multichoice.classes.inc
index 7440a83..44a09bc 100755
--- a/question_types/multichoice/multichoice.classes.inc
+++ b/question_types/multichoice/multichoice.classes.inc
@@ -385,14 +385,11 @@ class MultichoiceQuestion extends QuizQuestion {
    * to an administrator or to a quiz taker.
    */
   public function getAnsweringForm(array $form_state = NULL, $rid) {
-    $form = parent::getAnsweringForm($form_state, $rid);
-    //$form['#theme'] = 'multichoice_answering_form';
-
     /* We use an array looking key to be able to store multiple answers in tries.
      * At the moment all user answers have to be stored in tries. This is something we plan
      * to fix in quiz 5.x.
      */
-    $form['tries[answer]'] = array(
+    $form_element = array(
       '#options' => array(),
       '#theme' => 'multichoice_alternative',
     );
@@ -404,36 +401,37 @@ class MultichoiceQuestion extends QuizQuestion {
       $short = $this->node->alternatives[$i];
       $answer_markup = check_markup($short['answer'], $short['answer_format']);
       if (drupal_strlen($answer_markup) > 0) {
-        $form['tries[answer]']['#options'][$short['id']] = $answer_markup;
+        $form_element['#options'][$short['id']] = $answer_markup;
       }
     }
     if ($this->node->choice_random) {
       // We save the choice order so that the order will be the same in the answer report
-      $form['tries[choice_order]'] = array(
+      $form_element['#choice_order'] = array(
         '#type' => 'hidden',
-        '#value' => implode(',', $this->shuffle($form['tries[answer]']['#options'])),
+        '#value' => implode(',', $this->shuffle($form_element['#options'])),
       );
     }
     if ($this->node->choice_multi) {
-      $form['tries[answer]']['#type'] = 'checkboxes';
-      $form['tries[answer]']['#title'] = t('Choose');
+      $form_element['#type'] = 'checkboxes';
+      $form_element['#title'] = t('Choose');
       if (isset($response)) {
         if (is_array($response->getResponse())) {
-          $form['tries[answer]']['#default_value'] = $response->getResponse();
+          $form_element['#default_value'] = $response->getResponse();
         }
       }
     }
     else {
-      $form['tries[answer]']['#type'] = 'radios';
-      $form['tries[answer]']['#title'] = t('Choose one');
+      $form_element['#type'] = 'radios';
+      $form_element['#title'] = t('Choose one');
+      //$form_element['#required'] = TRUE;
       if (isset($response)) {
         if (is_array($response->getResponse())) {
-          $form['tries[answer]']['#default_value'] = array_pop($response->getResponse());
+          $form_element['#default_value'] = array_pop($response->getResponse());
         }
       }
     }
 
-    return $form;
+    return parent::getAnsweringForm($form_state, $rid, $form_element);
   }
 
   /**
@@ -764,24 +762,26 @@ class MultichoiceResponse extends QuizQuestionResponse {
 
   /**
    * Constructor
+   * $tries = the FAPI element
    */
   public function __construct($result_id, stdClass $question_node, $tries = NULL) {
     parent::__construct($result_id, $question_node, $tries);
     $this->user_answer_ids = array();
     // tries is the tries part of the post data
     if (is_array($tries)) {
-      if (isset($tries['choice_order'])) {
-        $this->choice_order = $tries['choice_order'];
+      if ($this->node->choice_random && isset($tries['#options'])) {
+        $this->choice_order = array_keys($tries['#options']);
       }
-      unset($tries['choice_order']);
-      if (is_array($tries['answer'])) {
-        foreach ($tries['answer'] as $answer_id) {
+      if (is_array($tries['#value'])) {
+        // if values is multichoice
+        foreach ($tries['#value'] as $answer_id) {
           $this->user_answer_ids[] = $answer_id;
           $this->answer = $this->user_answer_ids; //@todo: Stop using user_answer_ids and only use answer instead...
         }
       }
-      elseif (isset($tries['answer'])) {
-        $this->user_answer_ids[] = $tries['answer'];
+      elseif (!empty($tries['#value'])) {
+        // if values is single choice
+        $this->user_answer_ids[] = $tries['#value'];
       }
     }
     // We load the answer from the database
diff --git a/question_types/quiz_question/quiz_question.core.inc b/question_types/quiz_question/quiz_question.core.inc
index 282241f..5375b76 100644
--- a/question_types/quiz_question/quiz_question.core.inc
+++ b/question_types/quiz_question/quiz_question.core.inc
@@ -428,19 +428,20 @@ abstract class QuizQuestion {
    *  user response must have a key named "tries". (This is a Quiz 3.x legacy AFAIK. I'm
    *  not thrilled about it...)
    */
-  public function getAnsweringForm(array $form_state = NULL, $rid) {
+  public function getAnsweringForm(array $form_state = NULL, $rid, array $answer_form_element = NULL) {
     //echo $this->node->type;exit;
     $form = array();
-    $form['question_nid'] = array(
-      '#type' => 'hidden',
-      '#value' => $this->node->nid,
-    );
+
     $body = field_get_items('node', $this->node, 'body');
-    $form['question'] = array(
+    $form['question_' . $this->node->nid] = array(
       '#markup' => check_markup($body[0]['value'], $body[0]['format']),
       '#prefix' => '<div class="quiz-question-body">',
       '#suffix' => '</div>',
     );
+
+    $form['answer_' . $this->node->nid] = $answer_form_element;
+    $form['answer_' . $this->node->nid]['#element_validate'][] = 'quiz_question_answering_element_validate';
+
     return $form;
   }
 
diff --git a/question_types/quiz_question/quiz_question.module b/question_types/quiz_question/quiz_question.module
index db2af6b..c302a90 100644
--- a/question_types/quiz_question/quiz_question.module
+++ b/question_types/quiz_question/quiz_question.module
@@ -148,22 +148,39 @@ function quiz_question_validate($node, &$form) {
 }
 
 /**
- * Get the form to show to the quiz taker.
+ * Get the Single/Multi form to show to the quiz taker.
+ *
+ * $nodes is an array either way, this function will work for all question on a single page or a single question per page
  */
-function quiz_question_answering_form($form, $form_state, $node, $include_nid_in_id = FALSE) {
-  $question = _quiz_question_get_instance($node);
-  $form = $question->getAnsweringForm($form_state, isset($node->rid) ? $node->rid : NULL);
+function quiz_question_answering_form($form_state, $nodes, $include_nid_in_id = FALSE) {
+  $form = array();
+
+  if (!is_array($nodes)) {
+    $nodes = array($nodes);
+  }
+
   $quiz = quiz_type_access_load(arg(1));
+  $rid = $_SESSION['quiz_' . $quiz->nid]['result_id'];
+  $form['#attributes']['class'] = 'answering-form';
+  foreach ($nodes as $node) {
+    // Get instance of question.
+    $question = _quiz_question_get_instance($node);
+    if ($form_state['post']['op'] != 'Back') {
+      $is_last = _quiz_is_last_question();
+    }
 
-  $form['#attributes']['class'] = array('answering-form');
+    $qform = $question->getAnsweringForm($form_state, $rid > 0 ? $rid : NULL);
+    $form[] = $qform;
+  }
 
-  $is_last = _quiz_is_last_question();
   $form['navigation']['#theme'] = 'quiz_question_navigation_form';
-  if (!empty($quiz->backwards_navigation) && !empty($node->question_number)) {
+  if (!empty($quiz->backwards_navigation) && $_SESSION['quiz_' . $quiz->nid]['current_quiz_question'] > 0) {
     $form['navigation']['back'] = array(
-      '#type' => 'submit',
+      '#type' => 'button',
       '#value' => t('Back'),
-      '#attributes' => array('class' => array('q-back-button')),
+      '#attributes' => array('class' => 'q-back-button'),
+      '#submit' => array('quiz_question_back'),
+      '#executes_submit_callback' => TRUE,
     );
     if ($is_last) {
       drupal_set_message(t('This is the last question. If you would like to go back and check some of your answers, click "Back", otherwise click the "Finish" button.'), 'status', FALSE);
@@ -173,21 +190,26 @@ function quiz_question_answering_form($form, $form_state, $node, $include_nid_in
   // Add navigation at the bottom:
   $form['navigation']['submit'] = array(
     '#type' => 'submit',
-    '#value' => $is_last ? t('Finish') : t('Next'),
+    '#value' => $quiz->single_page || $is_last ? t('Finish') : t('Next'),
   );
-  if ($node->allow_skipping) {
+  if ($node->allow_skipping && !$quiz->single_page) {
     $form['navigation']['op']  = array(
       '#type' => 'submit',
       '#value' => $is_last ? t('Skip and finish') : t('Skip'),
       '#attributes' => array('class' => array('q-skip-button')),
+      '#executes_submit_callback' => TRUE,
     );
-    if ($quiz->allow_jumping) {
-      $form['jump_to_question'] = array(
-        '#type' => 'hidden',
-        '#default_value' => 0,
-      );
-    }
+    $form['navigation']['skip']['#submit'][] = $is_last ? 'quiz_question_skip_finish' : 'quiz_question_skip';
+  }
+
+  $form['#submit'] = array('quiz_question_answering_form_submit');
+  if (count($_SESSION['quiz_' . $quiz->nid]['quiz_questions']) == $_SESSION['quiz_' . $quiz->nid]['current_quiz_question']+1 || $quiz->single_page) {
+    $form['navigation']['submit'] = array('#type' => 'submit', '#value' => 'Finish');
+  }
+  else {
+    $form['navigation']['submit'] = array('#type' => 'submit', '#value' => 'Next');
   }
+
   return $form;
 }
 
@@ -220,11 +242,6 @@ function _quiz_question_teaser_content($node) {
  *  Object with nid, vid, rid, score, is_correct flags set.
  */
 function quiz_question_evaluate_question($question, $result_id, $answer = NULL) {
-  if (empty($answer) && !empty($_POST['tries'])) {
-    // FIXME this use of POST array is hacky. We will try to use FAPI mor accurately in Quiz 5.x
-    $answer = $_POST['tries'];
-  }
-  unset($_POST['tries']);
   $response = _quiz_question_response_get_instance($result_id, $question, $answer);
 
   // If a result_id is set, we are taking a quiz.
@@ -896,3 +913,79 @@ function quiz_question_add_body_field($type) {
   $instance['widget']['settings']['rows'] = 6;
   field_update_instance($instance);
 }
+
+/**
+ * Validate an answer element.
+ */
+function quiz_question_answering_element_validate($element, &$form_state) {
+  $nid = str_replace('answer_', '', $element['#name']);
+  $quiz = node_load(intval(arg(1)), $_SESSION['quiz_' . arg(1)]['quiz_vid']);
+  $question = node_load($nid);
+  $types = _quiz_get_question_types();
+  $module = $types[$question->type]['module'];
+  $result = module_invoke($module, 'evaluate_question', $question, $_SESSION['quiz_'. $quiz->nid]['result_id'], $element);
+  if ($form_state['clicked_button']['#type'] == 'submit' && $result->is_valid !== TRUE) {
+    // If button clicked was "Next" or "Finish", and result was invalid.
+    form_error($element, $result->is_valid);
+  }
+  else {
+    quiz_store_question_result($quiz, $result, array('set_msg' => TRUE));
+  }
+}
+
+/**
+ * Grade and store submitted questions.
+ * Don't process back/skip here.
+ */
+function quiz_question_answering_form_submit($form, &$form_state) {
+  $quiz = quiz_type_access_load(arg(1));
+
+  if (in_array($form_state['values']['op'], array(t('Finish'), t('Skip and finish')))) {
+    // Quiz is finished!
+    $score = quiz_end_scoring($quiz, $_SESSION['quiz_'. $quiz->nid]['result_id']);
+
+    if ($score['is_evaluated']) {
+      _quiz_maintain_results($quiz, $_SESSION['quiz_'. $quiz->nid]['result_id']);
+    }
+
+    // If we're at the end of the quiz.
+    // 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']['#value'] = theme('quiz_no_feedback');
+    }
+    else {
+      // Get the results and summary text for this quiz.
+      $summary = _quiz_get_summary_text($quiz, $score);
+      // Get the themed summary page.
+      $content['body']['#value'] = theme('quiz_take_summary', $quiz, $questions, $score, $summary);
+    }
+
+    // Remove session variables, save $rid
+    $rid = $_SESSION['quiz_'. $quiz->nid]['result_id'];
+    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, $rid, $score);
+  }
+  else {
+    // Quiz not finished, question submitted. Advance question.
+    $_SESSION['quiz_'. $quiz->nid]['current_quiz_question']++;
+  }
+}
+
+function quiz_question_skip($form, &$form_state) {
+  $quiz = quiz_type_access_load(arg(1));
+  $_SESSION['quiz_'. $quiz->nid]['current_quiz_question']++;
+  drupal_goto("node/{$quiz->nid}/take");
+}
+
+function quiz_question_back($form, &$form_state) {
+  $quiz = quiz_type_access_load(arg(1));
+  $_SESSION['quiz_'. $quiz->nid]['current_quiz_question']--;
+  drupal_goto("node/{$quiz->nid}/take");
+}
+
+function quiz_question_skip_finish($form, &$form_state) {
+  quiz_question_answering_form_submit($form, $form_state);
+}
diff --git a/quiz.install b/quiz.install
index effd917..040c9fd 100644
--- a/quiz.install
+++ b/quiz.install
@@ -7,6 +7,19 @@
  */
 
 /**
+ * Implements hook_update_N().
+ *
+ * Add single_page column to the {quiz_node_properties} table.
+ */
+function quiz_update_7000() {
+  db_add_field('quiz_node_properties', 'single_page', array(
+    'type' => 'int',
+    'not null' => TRUE,
+    'default' => 0,
+  ));
+}
+
+/**
  * Implements hook_install().
  */
 function quiz_install() {
@@ -209,6 +222,12 @@ function quiz_schema() {
         'not null' => TRUE,
         'default' => 0,
       ),
+      'single_page' => array(
+        'type' => 'int',
+        'size' => 'small',
+        'not null' => TRUE,
+        'default' => 0,
+      ),
     ),
     'primary key' => array('vid'),
     // 'unique keys' => array('vid'),
diff --git a/quiz.module b/quiz.module
index a8986b9..dd9848d 100644
--- a/quiz.module
+++ b/quiz.module
@@ -482,9 +482,26 @@ function quiz_menu() {
     'file' => 'quiz.admin.inc',
   );
 
+  $items['node/%node/jump/%'] = array(
+    'page callback' => 'quiz_jump_to_question',
+    'type' => MENU_CALLBACK,
+    'access arguments' => array(1),
+    'access callback' => 'quiz_access_jump',
+    'page arguments' => array(1, 3),
+  );
+
   return $items;
 }
 
+function quiz_jump_to_question($node, $question_num) {
+  $_SESSION['quiz_' . $node->nid]['current_quiz_question'] = $question_num-1;
+  drupal_goto("node/{$node->nid}/take");
+}
+
+function quiz_access_jump($node) {
+  return $node->allow_jumping;
+}
+
 /**
  * Implements hook_theme().
  */
@@ -515,7 +532,7 @@ function quiz_theme() {
       'file' => 'quiz.pages.inc',
     ),
     'quiz_progress' => array(
-      'variables' => array('question_number' => NULL, 'num_questions' => NULL, 'allow_jumping' => NULL, 'time_limit' => NULL),
+      'arguments' => array('variables' => NULL),
       'file' => 'quiz.pages.inc',
     ),
     'quiz_no_feedback' => array(
@@ -848,6 +865,7 @@ function _quiz_get_node_defaults() {
     'allow_skipping' => 1,
     'allow_resume' => 1,
     'allow_jumping' => 0,
+    'single_page' => 0,
   );
 }
 
@@ -972,11 +990,13 @@ function quiz_start_quiz_button_form($form, &$form_state, $node) {
  * Primary quiz-taking view on 'Take' tab.
  */
 function quiz_take($node) {
-  //drupal_alter('quiz_take', $node);
-  if (isset($node->rendered_content)) {
-    return $node->rendered_content;
+  // Load sessioned revision of quiz.
+  $quiz = quiz_type_access_load($node->nid);
+  drupal_alter('quiz_take', $node);
+  if (isset($quiz->rendered_content)) {
+    return $quiz->rendered_content;
   }
-  $to_be_rendered = quiz_take_quiz($node);
+  $to_be_rendered = quiz_take_quiz($quiz);
   return drupal_render($to_be_rendered);
 }
 
@@ -1059,6 +1079,12 @@ function quiz_form(&$node, &$form_state) {
     '#default_value' => $node->allow_jumping,
     '#description' => t('Whether to allow users to jump between questions using a menu in the @quiz', array('@quiz' => QUIZ_NAME)),
   );
+  $form['taking']['single_page'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Show all questions on a single page'),
+    '#default_value' => $node->single_page,
+    '#description' => t('Whether to show all questions on a single page in the @quiz', array('@quiz' => QUIZ_NAME)),
+  );
   $form['taking']['backwards_navigation'] = array(
     '#type' => 'checkbox',
     '#title' => t('Backwards navigation'),
@@ -1522,15 +1548,15 @@ function _quiz_delete_users_results($uid) {
  *   A quiz node object or FALSE if a load failed.
  */
 function quiz_type_access_load($arg) {
-  static $quiz_nodes = array();
-  if (isset($quiz_nodes[$arg])) {
-    return $quiz_nodes[$arg];
+  // node_load has a static cache already
+  $quiz_session = $_SESSION['quiz_' . $arg];
+  if ($quiz_session && $quiz_session['quiz_vid']) {
+    $node = node_load($arg, $quiz_session['quiz_vid']);
   }
-  $to_return = (($node = node_load($arg)) && $node->type == 'quiz') ? $node : FALSE;
-  if ($to_return) {
-    $quiz_nodes[$arg] = $to_return;
+  else {
+    $node = node_load(intval($arg));
   }
-  return $to_return;
+  return $node->type == 'quiz' ? $node : FALSE;
 }
 
 /**
@@ -1757,6 +1783,15 @@ function quiz_take_quiz($quiz) {
     return;
   }
 
+  if ($_SESSION['quiz_' . $quiz->nid]['result_id'] > 0) {
+    // Check if this result was deleted. If so, wipe out the session and set $rid = 0.
+    $sql = "select 1 from {quiz_node_results} where result_id = %d";
+    if (!db_result(db_query($sql, $_SESSION['quiz_' . $quiz->nid]['result_id']))) {
+      $_SESSION['quiz_' . $quiz->nid]['result_id'] = 0;
+      unset($_SESSION['quiz_' . $quiz->nid]);
+    }
+  }
+
   // If anonymous user and no unique hash, refresh with a unique string to
   // prevent caching.
   if (!$quiz->name && arg(4) == NULL) {
@@ -1765,12 +1800,10 @@ function quiz_take_quiz($quiz) {
 
   // 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']) {
-    $quiz = node_load($quiz->nid, $_SESSION['quiz_' . $quiz->nid]['quiz_vid']);
-  }
+  // @see quiz_type_access_load
 
   // If the session has no data for this quiz.
-  if (!isset($_SESSION['quiz_' . $quiz->nid]['quiz_questions'])) {
+  if (!isset($_SESSION['quiz_'. $quiz->nid]['current_quiz_question'])) {
     // We delete questions in progress from old revisions.
     _quiz_delete_old_in_progress($quiz, $user->uid);
 
@@ -1785,9 +1818,29 @@ function quiz_take_quiz($quiz) {
     }
 
     // First time running through quiz.
-    elseif (quiz_start_check($quiz, $rid)) {
-      _quiz_take_quiz_init($quiz);
+    else if (quiz_start_check($quiz, $rid)) {
+      // Create question list.
+      $questions = quiz_build_question_list($quiz);
+      if ($questions === FALSE) {
+        drupal_set_message(t('Not enough random questions were found. Please add more questions before trying to take this @quiz.',
+          array('@quiz' => QUIZ_NAME)), 'error');
+        return array('body' => array('#value' => ' '));
+      }
 
+      if (count($questions) == 0) {
+        drupal_set_message(t('No questions were found. Please !assign_questions before trying to take this @quiz.',
+          array('@quiz' => QUIZ_NAME, '!assign_questions' => l(t('assign questions'), 'node/'. arg(1) .'/questions'))), 'error');
+        return array('body' => array('#value' => t('Please assign questions...')));
+      }
+
+      // Initialize session variables.
+      $_SESSION['quiz_'. $quiz->nid]['result_id'] = quiz_create_rid($quiz);
+      $_SESSION['quiz_'. $quiz->nid]['quiz_questions'] = $questions;
+      $_SESSION['quiz_'. $quiz->nid]['current_quiz_question'] = 0; //index of quiz_questions
+      $_SESSION['quiz_'. $quiz->nid]['question_number'] = 0; //????
+      $_SESSION['quiz_'. $quiz->nid]['question_start_time'] = time();
+      $_SESSION['quiz_'. $quiz->nid]['question_duration'] = $quiz->time_limit;
+      $_SESSION['quiz_'. $quiz->nid]['quiz_vid'] = $quiz->vid;
     }
 
     else {
@@ -1805,40 +1858,14 @@ function quiz_take_quiz($quiz) {
   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...
-  }
-  // 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_array = $_SESSION['quiz_'. $quiz->nid]['quiz_questions'][$_SESSION['quiz_'. $quiz->nid]['current_quiz_question']];
     $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('Skip');
-      $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().
@@ -1853,80 +1880,35 @@ function quiz_take_quiz($quiz) {
     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);
+        // Don't do anything with the index
         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('Skip') || $_POST['op'] == t('Skip 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) && !$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) {
+  if (!empty($_SESSION['quiz_'. $quiz->nid]['quiz_questions'])) {
+      if ($quiz->single_page) {
+        $question_node_multi = array();
+        $questions = $_SESSION['quiz_'. $quiz->nid]['quiz_questions'];
+        foreach($questions as $question) {
+          array_push($question_node_multi,node_load($question['nid'], $question['vid']));
+        }
+      }
+      else {
       $question_node = node_load(
-        $_SESSION['quiz_' . $quiz->nid]['quiz_questions'][0]['nid'],
-        $_SESSION['quiz_' . $quiz->nid]['quiz_questions'][0]['vid']
+          $_SESSION['quiz_'. $quiz->nid]['quiz_questions'][$_SESSION['quiz_'. $quiz->nid]['current_quiz_question']]['nid'],
+          $_SESSION['quiz_'. $quiz->nid]['quiz_questions'][$_SESSION['quiz_'. $quiz->nid]['current_quiz_question']]['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
+      if (isset($_SESSION['quiz_'. $quiz->nid]['quiz_questions'][0]['rid'])) {
+        $question_node->rid = $_SESSION['quiz_'. $quiz->nid]['quiz_questions'][0]['rid'];
     }
     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_shift($_SESSION['quiz_' . $quiz->nid]['previous_quiz_questions']);
-      }
       // Avoid caching for anonymous users
       if (!$user->uid) {
         drupal_goto('node/' . $quiz->nid . '/take', array('query' => array('quizkey' => md5(mt_rand() . REQUEST_TIME))));
@@ -1941,10 +1923,11 @@ function quiz_take_quiz($quiz) {
     $content['progress']['#markup'] = theme('quiz_progress', array(
       'question_number' => $question_number,
       'num_questions' => $number_of_questions,
+      'single_page' => $quiz->single_page,
       '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) {
+    if (count($_SESSION['quiz_'. $quiz->nid]['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');
@@ -1961,7 +1944,6 @@ function quiz_take_quiz($quiz) {
       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;
@@ -2005,7 +1987,13 @@ function quiz_take_quiz($quiz) {
 
     // If we're not yet at the end.
     if (empty($quiz_end)) {
-      $content['body']['question']['#markup'] = quiz_take_question_view($question_node, $quiz);
+      // Render Quiz and Feedback
+      if (!$quiz->single_page) {
+        $content['body']['question']['#value'] = drupal_get_form('quiz_question_answering_form', $question_node);
+      }
+      else {
+        $content['body']['question']['#value'] = drupal_get_form('quiz_question_answering_form', $question_node_multi);
+      }
       $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) {
@@ -2016,36 +2004,6 @@ function quiz_take_quiz($quiz) {
       unset($_SESSION['quiz_' . $quiz->nid]['feedback']);
     }
   }
-  // Else we're at the end of quiz.
-  else {
-    $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);
-      // Get the themed summary page.
-      $content['body']['#markup'] = theme('quiz_take_summary', array('quiz' => $quiz, 'questions' => $questions, 'score' => $score, 'summary' => $summary));
-    }
-    if ($score['is_evaluated']) {
-      _quiz_maintain_results($quiz, $_SESSION['quiz_' . $quiz->nid]['result_id']);
-    }
-
-    // Remove session variables, save $rid
-    $rid = $_SESSION['quiz_' . $quiz->nid]['result_id'];
-    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, $rid, $score);
-  }
-
   return $content;
 }
 
@@ -2056,23 +2014,8 @@ function quiz_take_quiz($quiz) {
  *  The question number we want to jump to
  */
 function quiz_jump_to($question_num, $quiz, $rid) {
-  $num_next = count($_SESSION['quiz_' . $quiz->nid]['quiz_questions']);
-  $num_previous = count($_SESSION['quiz_' . $quiz->nid]['previous_quiz_questions']);
-  if ($question_num > $num_next + $num_previous + 1) {
-    // Trying to jump too far. We allow skipping all questions because of jQuery countdown...
-    return;
-  }
-  if ($question_num <= $num_previous) {
-    for ($i = 0; $i < $num_previous - $question_num + 1; $i++) {
-      array_unshift($_SESSION['quiz_' . $quiz->nid]['quiz_questions'], array_pop($_SESSION['quiz_' . $quiz->nid]['previous_quiz_questions']));
-    }
-  }
-  elseif ($question_num > $num_previous + 1) {
-    for ($i = 0; $i < $question_num - $num_previous - 1; $i++) {
-      $_SESSION['quiz_' . $quiz->nid]['previous_quiz_questions'][] = array_shift($_SESSION['quiz_' . $quiz->nid]['quiz_questions']);
-    }
-    _quiz_cleanup_after_jumping($quiz, $rid);
-  }
+  $_SESSION['quiz_'. $quiz->nid]['current_quiz_question'] = $question_num-1;
+  _quiz_cleanup_after_jumping($quiz, $rid);
 }
 
 function _quiz_take_quiz_init($quiz) {
@@ -3046,23 +2989,14 @@ function _quiz_resume_existing_quiz($quiz, $uid, $rid) {
 
   // Adding data to the session.
   reset($questions);
-  $_SESSION['quiz_' . $quiz->nid]['previous_quiz_questions'] = array();
   $_SESSION['quiz_' . $quiz->nid]['quiz_questions'] = array();
   $next_number = count($already_answered) + 1;
   foreach ($questions as $question) {
-    if (in_array($question['vid'], $already_answered)) {
-      $question['rid'] = $rid;
-      $_SESSION['quiz_' . $quiz->nid]['previous_quiz_questions'][] = $question;
-    }
-    else {
-      $question['number'] = $next_number;
-      $next_number++;
-      $_SESSION['quiz_' . $quiz->nid]['quiz_questions'][] = $question;
+      $_SESSION['quiz_'. $quiz->nid]['quiz_questions'][] = $question;
     }
-  }
 
-  $_SESSION['quiz_' . $quiz->nid]['result_id'] = $rid;
-  $_SESSION['quiz_' . $quiz->nid]['question_number'] = count($_SESSION['quiz_' . $quiz->nid]['previous_quiz_questions']);
+  $_SESSION['quiz_'. $quiz->nid]['result_id'] = $rid;
+  $_SESSION['quiz_' . $quiz->nid]['current_quiz_question'] = 0;
 
   // Timed quizzes are likely to have expired by this point. But we let
   // the quiz_take_quiz handler handle that.
@@ -3614,29 +3548,7 @@ function _quiz_get_answers($quiz, $rid) {
  *  Result id for the result we want to clean up
  */
 function _quiz_cleanup_after_jumping($quiz, $rid) {
-  $sql = "INSERT IGNORE INTO {quiz_node_results_answers}
-          (result_id, question_nid, question_vid, is_skipped, answer_timestamp, number";
-  if ($quiz->randomization == 3) {
-    $sql .= ', tid';
-  }
-  $sql .= ') VALUES ';
-  $args = array();
-  $first = TRUE;
-  $time = REQUEST_TIME;
-  foreach ($_SESSION['quiz_' . $quiz->nid]['previous_quiz_questions'] as $previous_question) {
-    if (!$first) {
-      $sql .= ', ';
-    }
-    $sql .= '(%d, %d, %d, 1, ' . $time . ', %d';
-    array_push($args, $rid, $previous_question['nid'], $previous_question['vid'], $previous_question['number']);
-    if ($quiz->randomization == 3) {
-      $sql .= ', %d';
-      $args[] = $previous_question['tid'];
-    }
-    $sql .= ')';
-    $first = FALSE;
-  }
-  db_query($sql, $args);
+  // Do we need this? Using fapi...
 }
 
 /**
@@ -4231,5 +4143,6 @@ function _quiz_is_empty_html($html) {
  * Check if this is the last question of the quiz
  */
 function _quiz_is_last_question() {
-  return count($_SESSION['quiz_' . intval(arg(1))]['quiz_questions']) < 2;
+  $quiz_session = $_SESSION['quiz_' . intval(arg(1))];
+  return count($quiz_session['quiz_questions']) == $quiz_session['current_quiz_question'] + 1;
 }
diff --git a/quiz.pages.inc b/quiz.pages.inc
index 844c9ef..eb11ce4 100644
--- a/quiz.pages.inc
+++ b/quiz.pages.inc
@@ -447,14 +447,13 @@ function theme_quiz_score_incorrect() {
 function theme_quiz_progress($variables) {
   $question_number = $variables['question_number'];
   $num_of_question = $variables['num_questions'];
-  // TODO Number of parameters in this theme funcion does not match number of parameters found in hook_theme.
   // Determine the percentage finished (not used, but left here for other implementations).
   //$progress = ($question_number*100)/$num_of_question;
 
   // Get the current question # by adding one.
   $current_question = $question_number + 1;
 
-  if ($variables['allow_jumping']) {
+  if ($variables['allow_jumping']  && !$variables['single_page']) {
     $current_question = theme('quiz_jumper', array('current' => $current_question, 'num_questions' => $num_of_question));
   }
 
@@ -469,13 +468,8 @@ function theme_quiz_progress($variables) {
   return $output;
 }
 
-/**
- * @todo Please document this function.
- * @see http://drupal.org/node/1354
- */
 function theme_quiz_jumper($variables) {
-  $current = $variables['current'];
-  $num_questions = $variables['num_questions'];
+  $quiz_nid = arg(1);
   $output = '<select name="quiz-jumper" class="form-select" id="quiz-jumper">';
   for ($i = 1; $i <= $num_questions; $i++) {
     $extra = $i == $current ? ' selected="selected"' : '';
@@ -485,8 +479,7 @@ function theme_quiz_jumper($variables) {
   drupal_add_js("
     Drupal.behaviors.quizJumper = function () {
       $('#quiz-jumper:not(.quizJumper-processed)').show().addClass('quizJumper-processed').change(function(){
-        $('#edit-jump-to-question').val($(this).val());
-        $('#edit-submit').trigger('click');
+        window.location = Drupal.settings.basePath + '/node/$quiz_nid/jump/' + $(this).val();
       });
       $('#quiz-jumper-no-js:not(.quizJumper-processed)').hide().addClass('quizJumper-processed');
     };
-- 
1.7.5.4

