diff --git a/quiz.install b/quiz.install index d910607..ba9b4ed 100644 --- a/quiz.install +++ b/quiz.install @@ -222,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'), @@ -789,6 +795,18 @@ function quiz_update_7407(&$sandbox) { return t('Added new auto update max score field to the quiz_node_relationship table'); } +/** + * 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); +} /** diff --git a/quiz.module b/quiz.module index ab679e8..eaf9aa1 100644 --- a/quiz.module +++ b/quiz.module @@ -231,6 +231,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; } @@ -544,7 +545,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( @@ -559,6 +560,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', @@ -734,7 +739,8 @@ function quiz_insert($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, )) ->execute(); @@ -792,7 +798,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) @@ -939,6 +946,7 @@ function _quiz_get_node_defaults() { 'quiz_open' => _quiz_form_prepare_date(), 'quiz_close' => _quiz_form_prepare_date(NULL, variable_get('quiz_default_close', 30)), 'mark_doubtful' => 0, + 'single_page' => 0, ); } @@ -1169,6 +1177,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'), @@ -1884,7 +1898,7 @@ function quiz_take_quiz($quiz) { } // If the session has no data for this quiz. - if (!isset($_SESSION['quiz_' . $quiz->nid]['quiz_questions'])) { + if ($quiz->single_page || !isset($_SESSION['quiz_' . $quiz->nid]['quiz_questions'])) { // We delete questions in progress from old revisions. _quiz_delete_old_in_progress($quiz, $user->uid); @@ -1893,7 +1907,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); } @@ -1902,6 +1916,11 @@ function quiz_take_quiz($quiz) { elseif (quiz_start_check($quiz, $rid) && (_quiz_take_quiz_init($quiz) === FALSE)) { return array('body' => array('#markup' => '')); } + + elseif ($quiz->single_page) { + _quiz_take_quiz_init($quiz); + $questions = quiz_build_question_list($quiz); + } else { //return array('body' => array('#markup' => t('This quiz is closed'))); @@ -1922,7 +1941,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) { @@ -1949,19 +1968,50 @@ 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']); - // 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)); + if (!$quiz->single_page) { + // 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; + } } - elseif ($quiz->allow_jumping && _quiz_is_int($_POST['jump_to_question'])) { - $_POST['op'] = t('Leave blank'); - $allow_skipping = TRUE; - $jumping = TRUE; + else { + //storage of all the validation errors + $q_passed_validations = array(); + + foreach ($questions as $question) { + $former_question = node_load($question['nid'], $question['vid']); + $_POST['tries'] = isset($_POST['tries_' . $question['nid']]) ? $_POST['tries_' . $question['nid']] : NULL; + // Call hook_evaluate_question() for each 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; + if ($q_passed_validation === TRUE) { + quiz_store_question_result($quiz, $result, array('set_msg' => TRUE, 'question_data' => $former_question_array)); + } + else { + $q_passed_validations[] = $q_passed_validation; + } + } + + //takes all the validation errors and put it into a single string because thats what the error handling code expects + if($q_passed_validations) { + $q_passed_validation = t('Please fix the errors in your submission.'); + $quiz_end = FALSE; + } + else { + $quiz_end = TRUE; + } } // Stash feedback in the session, since the $_POST gets cleared. @@ -2000,7 +2050,9 @@ function quiz_take_quiz($quiz) { } // 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)))); + if (!$quiz->single_page) { + drupal_goto('node/' . $quiz->nid . '/take', array('query' => array('quizkey' => md5(mt_rand() . REQUEST_TIME)))); + } } } // Check for a skip. @@ -2074,7 +2126,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); @@ -2140,7 +2193,17 @@ 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); + if (!$quiz->single_page) { + $content['body']['question']['#markup'] = quiz_take_question_view($question_node, $quiz); + } + else { + $question_node_multi = array(); + foreach ($questions as $question) { + array_push($question_node_multi, node_load($question['nid'], $question['vid'])); + } + + $content['body']['question']['#markup'] = quiz_node_view_multi($question_node_multi, $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) { @@ -2221,6 +2284,7 @@ function quiz_jump_to($question_num, $quiz, $rid) { function _quiz_take_quiz_init($quiz) { // 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 FALSE; @@ -2230,7 +2294,7 @@ function _quiz_take_quiz_init($quiz) { 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/' . $quiz->nid . '/questions'))), 'error'); return FALSE; } - + // Initialize session variables. $_SESSION['quiz_' . $quiz->nid]['result_id'] = quiz_create_rid($quiz); $_SESSION['quiz_' . $quiz->nid]['quiz_questions'] = $questions; @@ -2238,6 +2302,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; @@ -2264,6 +2329,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, 'teaser'); + 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 @@ -2654,17 +2738,17 @@ function quiz_update_max_score_properties($quizzes_to_update) { if (!empty($results_to_update)) { db_update('quiz_node_results') ->expression('score', - 'ROUND( - 100 * ( - SELECT COALESCE (SUM(a.points_awarded), 0) - FROM {quiz_node_results_answers} a - WHERE a.result_id = {quiz_node_results}.result_id - ) / ( - SELECT max_score - FROM {quiz_node_properties} qnp - WHERE qnp.vid = {quiz_node_results}.vid - ) - )') + 'ROUND( + 100 * ( + SELECT COALESCE (SUM(a.points_awarded), 0) + FROM {quiz_node_results_answers} a + WHERE a.result_id = {quiz_node_results}.result_id + ) / ( + SELECT max_score + FROM {quiz_node_properties} qnp + WHERE qnp.vid = {quiz_node_results}.vid + ) + )') ->condition('vid', $results_to_update, 'IN') ->execute(); } @@ -3215,6 +3299,7 @@ function _quiz_resume_existing_quiz($quiz, $uid, $rid) { if ($quiz->time_limit > 0) { $_SESSION['quiz_' . $quiz->nid]['question_duration'] = $quiz->time_limit; } + drupal_set_message(t('Resuming a previous quiz in-progress.'), 'status'); } diff --git a/quiz.pages.inc b/quiz.pages.inc index c16144a..19cbb34 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']) { @@ -658,6 +664,42 @@ 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->body, $matches); + $form_free_body = preg_replace('/]*?>/', '', $matches[1]); + $item_list[] = array( + 'data' => str_replace('name="tries', 'name="tries_' . $node->nid . '', $form_free_body), + ); + } + else { + break; + } + } + $form_a = '
'; + $form_b = '
'; + $body = array( + array('#markup' => $form_a), + array('#theme' => 'item_list', '#items' => $item_list), + array('#markup' => $form_b), + ); + + return drupal_render($body); +} + +/** * Theme the stats on the views page * * @param $node