commit 5a21a3168ddf4a61e57f952eaee437eb93d04e29 Author: Devin Zuczek Date: Thu Mar 13 15:46:58 2014 -0400 Mantis #19939: patching for quiz single page diff --git a/quiz.install b/quiz.install index f077d18..9867176 100644 --- a/quiz.install +++ b/quiz.install @@ -228,6 +228,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'), @@ -796,6 +802,19 @@ function quiz_update_7407(&$sandbox) { } /** + * Add single page column. + */ +function quiz_update_7408(&$sandbox) { + $spec = array( + 'type' => 'int', + 'size' => 'small', + 'not null' => TRUE, + 'default' => 0, + ); + db_add_field('quiz_node_properties', 'single_page', $spec); +} + +/** * Adding userpoints tid column */ function quiz_update_7409(&$sandbox) { diff --git a/quiz.module b/quiz.module index c923075..459d8fd 100644 --- a/quiz.module +++ b/quiz.module @@ -267,6 +267,7 @@ function quiz_access_my_results($quiz) { * True if access, false otherwise */ function quiz_access_my_result($rid) { + global $user; if (!user_access('view own quiz results')) { return FALSE; } @@ -580,7 +581,7 @@ function quiz_theme($existing, $type, $theme, $path) { 'file' => 'quiz.pages.inc', ), 'quiz_progress' => array( - 'variables' => array('question_number' => NULL, 'num_questions' => NULL, 'allow_jumping' => NULL, 'time_limit' => NULL), + 'variables' => array('question_number' => NULL, 'num_questions' => NULL, 'allow_jumping' => NULL, 'time_limit' => NULL, 'quiz' => NULL), 'file' => 'quiz.pages.inc', ), 'quiz_no_feedback' => array( @@ -595,6 +596,10 @@ function quiz_theme($existing, $type, $theme, $path) { 'file' => 'quiz.pages.inc', 'variables' => array('question_node' => NULL), ), + 'quiz_multi_question_node' => array( + 'file' => 'quiz.pages.inc', + 'variables' => array('question_node' => NULL), + ), 'question_selection_table' => array( 'file' => 'quiz.admin.inc', 'render element' => 'form', @@ -794,6 +799,7 @@ function quiz_insert($node) { 'show_passed' => $node->show_passed, 'mark_doubtful' => $node->mark_doubtful, 'max_score' => $max_score, + 'single_page' => $node->single_page, )) ->execute(); @@ -852,7 +858,8 @@ function quiz_update($node) { 'allow_resume' => $node->allow_resume, 'allow_jumping' => $node->allow_jumping, 'show_passed' => $node->show_passed, - 'mark_doubtful' => $node->mark_doubtful + 'mark_doubtful' => $node->mark_doubtful, + 'single_page' => $node->single_page, )) ->condition('vid', $node->vid) ->condition('nid', $node->nid) @@ -1001,6 +1008,7 @@ function _quiz_get_node_defaults() { 'quiz_close' => _quiz_form_prepare_date(NULL, variable_get('quiz_default_close', 30)), 'mark_doubtful' => 0, 'max_score' => 0, + 'single_page' => 0, ); } @@ -1231,6 +1239,12 @@ function quiz_form(&$node, &$form_state) { '#description' => t('The difference between "random order" and "random questions" is that with "random questions" questions are drawn randomly from a pool of questions. With "random order" the quiz will always consist of the same questions. With "Categorized random questions" you can choose several terms questions should be drawn from, and you can also choose how many questions that should be drawn from each, and max score for each term.'), '#default_value' => $node->randomization, ); + $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']['feedback'] = array( '#type' => 'fieldset', '#title' => t('Feedback'), @@ -1983,7 +1997,7 @@ function quiz_take_quiz($quiz) { $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) { + if (!$quiz->single_page && $quiz->allow_resume && $rid > 0) { _quiz_resume_existing_quiz($quiz, $user->uid, $rid); } @@ -1998,6 +2012,18 @@ function quiz_take_quiz($quiz) { } } + //initialization for multipage + if ($quiz->single_page) { + if (!isset($_SESSION['quiz_' . $quiz->nid]['quiz_questions'])) { + _quiz_take_quiz_init($quiz); + } + $questions = quiz_build_question_list($quiz); + $question_nodes = array(); + foreach($questions as $question) { + $question_nodes[] = node_load($question['nid'], $question['vid']); + } + } + $q_passed_validation = FALSE; $repeat_until_correct_error = FALSE; @@ -2030,7 +2056,7 @@ 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'])) { + elseif (!$quiz->single_page && (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) { @@ -2038,7 +2064,7 @@ function quiz_take_quiz($quiz) { } // 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')) { + elseif ($_POST['op'] == t('Continue')) { drupal_goto("node/{$quiz->nid}/take"); } // We maintain two lists: previous questions and upcomming questions. @@ -2057,6 +2083,7 @@ function quiz_take_quiz($quiz) { $former_question_array = array_shift($_SESSION['quiz_' . $quiz->nid]['quiz_questions']); $former_question = node_load($former_question_array['nid'], $former_question_array['vid']); + if (!$quiz->single_page) { // Call hook_evaluate_question(). $types = _quiz_get_question_types(); $module = $types[$former_question->type]['module']; @@ -2071,9 +2098,38 @@ function quiz_take_quiz($quiz) { $allow_skipping = TRUE; $jumping = TRUE; } + } + else { + //storage of all the validation errors + $results = array(); + $quiz_end = TRUE; + $_SESSION['quiz_' . $quiz->nid]['previous_result_id'] = $_SESSION['quiz_' . $quiz->nid]['result_id']; + + foreach ($question_nodes as $question_node) { + $former_question = node_load($question_node->nid, $question_node->vid); + $_POST['tries'] = isset($_POST['tries_' . $question_node->nid]) ? $_POST['tries_' . $question_node->nid] : NULL; + // Call hook_evaluate_question() for each question. + $types = _quiz_get_question_types(); + $module = $types[$former_question->type]['module']; + $results[$question_node->nid] = module_invoke($module, 'evaluate_question', $former_question, $_SESSION['quiz_' . $quiz->nid]['result_id']); + $q_passed_validation = $results[$question_node->nid]->is_valid; + + // If validation passes + if ($results[$question_node->nid]->is_valid === TRUE) { + quiz_store_question_result($quiz, $results[$question_node->nid], array('set_msg' => TRUE, 'question_data' => $former_question_array)); + } + // If validation didn't pass + else { + // takes all the validation errors and put it into a single string because thats what the error handling code expects + $q_passed_validations = t('Please fix the errors in your submission.'); + $quiz_end = FALSE; + } + } + } if ($quiz->repeat_until_correct && $_POST['op'] != t('Back') && $q_passed_validation === TRUE) { // If the question was answered incorrectly, repeat it + if(!$quiz->single_page) { 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); @@ -2082,6 +2138,19 @@ function quiz_take_quiz($quiz) { unset($_SESSION['quiz_' . $quiz->nid]['feedback']); } } + // Handler for single page repeat until correct + else { + foreach ($results as $result) { + if ($result && !$result->is_correct && $result->is_evaluated) { + drupal_set_message(t('An answer was incorrect. Please try again.'), 'error'); + $repeat_until_correct_error = TRUE; + $quiz_end = FALSE; + unset($_SESSION['quiz_' . $quiz->nid]['feedback']); + break; + } + } + } + } 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 @@ -2109,11 +2178,51 @@ function quiz_take_quiz($quiz) { } } + // 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) { + if(!$quiz->single_page) { + // 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('Continue'), + ); + return $report_form; + } + } + else { + $path = drupal_get_path('module', 'quiz'); + require_once DRUPAL_ROOT . '/' . $path . '/quiz.pages.inc'; + $_SESSION['quiz_' . $quiz->nid]['quiz_questions'] = array(); + + $form = array(); + foreach($question_nodes as $question_node) { + $report = module_invoke($module, 'get_report', $question_node->nid, $question_node->vid, $_SESSION['quiz_' . $quiz->nid]['result_id']); + if($report) { + $report_form = drupal_get_form('quiz_report_form', array($report), TRUE, TRUE, TRUE); + } + $form[] = $report_form; + } + $form[count($form)-1]['op'] = array( + '#type' => 'submit', + '#value' => t('Continue'), + ); + return $form; + + } + } + // If anonymous user, refresh url with unique hash to prevent caching. if (!$user->uid && $q_passed_validation === TRUE) { + if (!$quiz->single_page) { 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) { @@ -2153,6 +2262,7 @@ function quiz_take_quiz($quiz) { // If we got no error when validating the question if (!is_string($q_passed_validation) || $_POST['op'] == t('Back') && $quiz->backwards_navigation) { + if(!$quiz->single_page) { $question_node = node_load( $_SESSION['quiz_' . $quiz->nid]['quiz_questions'][0]['nid'], $_SESSION['quiz_' . $quiz->nid]['quiz_questions'][0]['vid'] @@ -2160,6 +2270,17 @@ function quiz_take_quiz($quiz) { if (isset($_SESSION['quiz_' . $quiz->nid]['quiz_questions'][0]['rid'])) { $question_node->rid = $_SESSION['quiz_' . $quiz->nid]['quiz_questions'][0]['rid']; } + } + else { + if($question_nodes) { + foreach($question_nodes as $question_node) { + if (isset($_SESSION['quiz_' . $quiz->nid]['previous_result_id'])) { + $question_node->rid = $_SESSION['quiz_' . $quiz->nid]['previous_result_id']; + } + } + } + } + // We got an error message when trying to validate the previous answer } else { @@ -2185,7 +2306,8 @@ function quiz_take_quiz($quiz) { 'question_number' => $question_number, 'num_questions' => $number_of_questions, 'allow_jumping' => $quiz->allow_jumping, - 'time_limit' => $quiz->time_limit)); + 'time_limit' => $quiz->time_limit, + 'quiz' => $quiz)); $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); @@ -2251,7 +2373,12 @@ function quiz_take_quiz($quiz) { // If we're not yet at the end. if (empty($quiz_end)) { + if (!$quiz->single_page) { $content['body']['question']['#markup'] = quiz_take_question_view($question_node, $quiz); + } + else { + $content['body']['question']['#markup'] = quiz_node_view_multi($question_nodes, $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) { @@ -2349,6 +2476,7 @@ function _quiz_take_quiz_init($quiz) { $_SESSION['quiz_' . $quiz->nid]['question_number'] = 0; $_SESSION['quiz_' . $quiz->nid]['question_start_time'] = REQUEST_TIME; $_SESSION['quiz_' . $quiz->nid]['quiz_vid'] = $quiz->vid; + $_SESSION['current_quiz_id'] = $quiz->nid; if ($quiz->time_limit > 0) { $_SESSION['quiz_' . $quiz->nid]['question_duration'] = $quiz->time_limit; @@ -2375,6 +2503,25 @@ function quiz_take_question_view($question_node, $quiz_node) { } /** + * Create the view for a multi-question the user is about to take. + * + * @param $question_nodes + * The question nodes that should be rendered. + * @param $quiz_node + * The quiz node. + * @return + * A string containing the body of the node. + */ +function quiz_node_view_multi($question_nodes = array(), $quiz_node) { + foreach ($question_nodes as &$question_node) { + node_build_content($question_node, 'question'); + module_invoke_all('node_build_alter', $question_node, FALSE); + //$question_node->body = drupal_render($question_node->content); + } + return theme('quiz_multi_question_node', array('question_node' => $question_nodes)); +} + +/** * Store a quiz question result. * * @param $quiz diff --git a/quiz.pages.inc b/quiz.pages.inc index 909e92e..f67198f 100644 --- a/quiz.pages.inc +++ b/quiz.pages.inc @@ -470,6 +470,7 @@ function theme_quiz_score_incorrect() { function theme_quiz_progress($variables) { $question_number = $variables['question_number']; $num_of_question = $variables['num_questions']; + $quiz = $variables['quiz']; // 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; @@ -483,7 +484,12 @@ function theme_quiz_progress($variables) { $output = ''; $output .= '
'; - $output .= t('Question !x of @y', array('!x' => $current_question, '@y' => $num_of_question)); + if ($quiz->single_page) { + $output .= format_plural($num_of_question, '1 question', '@count questions'); + } + else { + $output .= t('Question %x of %y', array('%x' => $current_question, '%y' => $num_of_question)); + } $output .= '
' . "\n"; // Add div to be used by jQuery countdown if ($variables['time_limit']) { @@ -568,7 +574,7 @@ function theme_quiz_take_summary($variables) { $output .= '
' . $summary['result'] . '
' . "\n"; } // Get the feedback for all questions. These are included here to provide maximum flexibility for themers - if ($quiz->display_feedback) { + if ($quiz->display_feedback || ($quiz->feedback_time == QUIZ_FEEDBACK_END && $quiz->pass_rate > 0 && $score['percentage_score'] >= $quiz->pass_rate)) { $form = drupal_get_form('quiz_report_form', $questions); $output .= drupal_render($form); } @@ -660,6 +666,41 @@ function theme_quiz_single_question_node($variables) { } /** + * Theme the multi question node. + * + * @param $nodes + * The question nodes + * @return + * Themed html feedback + */ +function theme_quiz_multi_question_node($variables) { + $nodes = $variables['question_node']; + $qno = 0; + + foreach ($nodes as $node) { + $qno++; + if (isset($node)) { + preg_match('|]*?>(.*?)|si', $node->content['question_form']['#markup'], $matches); + $form_free_body = preg_replace('/]*?>/', '', $matches[1]); + $node->content['question_form']['#markup'] = str_replace('name="tries', 'name="tries_' . $node->nid . '', $form_free_body); + $item_list[] = drupal_render($node->content); + } + else { + break; + } + } + $form_header = '
'; + $form_footer = '
'; + $body = array( + array('#markup' => $form_header), + array('#theme' => 'item_list', '#items' => $item_list), + array('#markup' => $form_footer), + ); + + return drupal_render($body); +} + +/** * Theme the stats on the views page * * @param $node