View source
<?php
include drupal_get_path('module', 'quiz') . '/quiz_datetime.inc';
define('QUESTION_RANDOM', 0);
define('QUESTION_ALWAYS', 1);
define('QUESTION_NEVER', 2);
define('QUIZ_NAME', _quiz_get_quiz_name());
define('QUIZ_FEEDBACK_END', 0);
define('QUIZ_FEEDBACK_QUESTION', 1);
define('QUIZ_FEEDBACK_NEVER', 2);
function _quiz_get_feedback_options() {
return array(
QUIZ_FEEDBACK_END => t('At the end of the @quiz', array(
'@quiz' => QUIZ_NAME,
)),
QUIZ_FEEDBACK_QUESTION => t('After each question'),
QUIZ_FEEDBACK_NEVER => t('Do not show'),
);
}
define('QUIZ_PERM_ADMIN_CONFIG', 'administer quiz configuration');
function quiz_perm() {
return array(
QUIZ_PERM_ADMIN_CONFIG,
'access quiz',
'create quiz',
'administer quiz',
'user results',
);
}
function quiz_access($op, $node) {
global $user;
if ($op == 'view') {
return user_access('access quiz');
}
if ($op == 'create') {
return user_access('create quiz');
}
if ($op == 'update' || $op == 'delete') {
if (user_access('create quiz') && $user->uid == $node->uid) {
return TRUE;
}
}
if (user_access('administer quiz')) {
return TRUE;
}
}
function quiz_node_info() {
return array(
'quiz' => array(
'name' => t('@quiz', array(
"@quiz" => QUIZ_NAME,
)),
'module' => 'quiz',
'description' => 'Create interactive quizzes for site visitors',
),
);
}
function quiz_menu($may_cache) {
$items = array();
if ($may_cache) {
$access = user_access(QUIZ_PERM_ADMIN_CONFIG);
$items[] = array(
'path' => 'admin/settings/quiz',
'title' => t('@quiz Configuration', array(
'@quiz' => QUIZ_NAME,
)),
'description' => t('Configure @quiz options.', array(
'@quiz' => QUIZ_NAME,
)),
'callback' => 'drupal_get_form',
'callback arguments' => array(
'quiz_admin_settings',
),
'access' => user_access(QUIZ_PERM_ADMIN_CONFIG),
'type' => MENU_NORMAL_ITEM,
);
$items[] = array(
'path' => 'node/add/quiz',
'title' => t('@quiz', array(
'@quiz' => QUIZ_NAME,
)),
'access' => user_access('create quiz'),
);
$items[] = array(
'path' => 'admin/quiz/results',
'title' => t('@quiz Results', array(
'@quiz' => QUIZ_NAME,
)),
'callback' => 'quiz_admin',
'access' => user_access('administer quiz'),
'type' => MENU_NORMAL_ITEM,
);
}
else {
drupal_add_css(drupal_get_path('module', 'quiz') . '/quiz.css', 'module', 'all');
if (arg(0) == 'node' && is_numeric(arg(1))) {
$node = node_load(arg(1));
if ($node->type == 'quiz') {
$items[] = array(
'path' => 'node/' . arg(1) . '/questions',
'title' => t('Manage questions'),
'callback' => 'quiz_questions',
'access' => user_access('create quiz'),
'type' => MENU_LOCAL_TASK,
);
if (user_access('administer quiz')) {
$items[] = array(
'path' => 'node/' . arg(1) . '/quiz/admin',
'title' => t('Quiz Admin', array(
'@quiz' => QUIZ_NAME,
)),
'callback' => 'theme_quiz_view',
'callback arguments' => array(
node_load(arg(1)),
),
'access' => user_access('administer quiz'),
'type' => MENU_LOCAL_TASK,
);
}
}
}
else {
$items[] = array(
'path' => 'user/' . arg(1) . '/myresults',
'title' => t('My Results'),
'callback' => 'quiz_get_user_results',
'access' => user_access('user results'),
'type' => MENU_LOCAL_TASK,
);
$items[] = array(
'path' => 'user/quiz/' . arg(2) . '/userresults',
'title' => t('User Results'),
'callback' => 'quiz_user_results',
'access' => user_access('user results'),
'type' => MENU_CALLBACK,
);
$items[] = array(
'path' => 'admin/quiz/' . arg(2) . '/view',
'title' => t('View @quiz', array(
'@quiz' => QUIZ_NAME,
)),
'callback' => 'quiz_admin_results',
'access' => user_access('administer quiz'),
'type' => MENU_CALLBACK,
);
$items[] = array(
'path' => 'admin/quiz/' . arg(2) . '/delete',
'title' => t('Delete @quiz', array(
'@quiz' => QUIZ_NAME,
)),
'callback' => 'quiz_admin_result_delete',
'access' => user_access('administer quiz'),
'type' => MENU_CALLBACK,
);
}
}
return $items;
}
function quiz_form(&$node) {
$form['title'] = array(
'#type' => 'textfield',
'#title' => t('Title'),
'#default_value' => $node->title,
'#description' => t('The name of the @quiz.', array(
'@quiz' => QUIZ_NAME,
)),
'#required' => TRUE,
);
$form['body'] = array(
'#type' => 'textarea',
'#title' => t('Description'),
'#default_value' => $node->body,
'#description' => t('A description of what the @quiz entails', array(
'@quiz' => QUIZ_NAME,
)),
'#required' => TRUE,
);
$form['body_filter']['format'] = filter_form($node->format);
$form['shuffle'] = array(
'#type' => 'checkbox',
'#title' => t('Shuffle questions'),
'#default_value' => isset($node->shuffle) ? $node->shuffle : 1,
'#description' => t('Whether to shuffle/randomize the questions on the @quiz', array(
'@quiz' => QUIZ_NAME,
)),
);
$form['backwards_navigation'] = array(
'#type' => 'checkbox',
'#title' => t('Backwards navigation'),
'#default_value' => $node->backwards_navigation,
'#description' => t('Whether to allow user to go back and revisit their answers'),
);
$form['feedback_time'] = array(
'#title' => t('Feedback Time'),
'#type' => 'radios',
'#default_value' => isset($node->feedback_time) ? $node->feedback_time : QUIZ_FEEDBACK_END,
'#options' => _quiz_get_feedback_options(),
'#description' => t('Indicates at what point feedback for each question will be given to the user'),
);
$form['quiz_availability'] = array(
'#type' => 'fieldset',
'#title' => t('Availability options'),
'#collapsed' => FALSE,
'#collapsible' => TRUE,
);
$form['quiz_availability']['quiz_always'] = array(
'#type' => 'checkbox',
'#title' => t('Always Available'),
'#default_value' => $node->quiz_always,
'#description' => t('Click this option to ignore the open and close dates.'),
);
$form['quiz_availability']['quiz_open'] = array(
'#type' => 'date',
'#title' => t('Open Date'),
'#default_value' => _quiz_form_prepare_date($node->quiz_open),
'#description' => t('The date this @quiz will become available.', array(
'@quiz' => QUIZ_NAME,
)),
);
$form['quiz_availability']['quiz_close'] = array(
'#type' => 'date',
'#title' => t('Close Date'),
'#default_value' => _quiz_form_prepare_date($node->quiz_close, variable_get('quiz_default_close', 30)),
'#description' => t('The date this @quiz will cease to be available.', array(
'@quiz' => QUIZ_NAME,
)),
);
$options = array(
t('Unlimited'),
);
for ($i = 1; $i < 10; $i++) {
$options[$i] = $i;
}
$form['takes'] = array(
'#type' => 'select',
'#title' => t('Number of takes'),
'#default_value' => $node->takes,
'#options' => $options,
'#description' => t('The number of times a user is allowed to take the @quiz', array(
'@quiz' => QUIZ_NAME,
)),
);
$form['summaryoptions'] = array(
'#type' => 'fieldset',
'#title' => t('@quiz Summary Options', array(
'@quiz' => QUIZ_NAME,
)),
'#collapsible' => TRUE,
'#collapsed' => FALSE,
);
if (variable_get('quiz_use_passfail', 1)) {
if (!$node->nid) {
$node->pass_rate = variable_get('quiz_default_pass_rate', 75);
}
$form['summaryoptions']['pass_rate'] = array(
'#type' => 'textfield',
'#title' => t('Pass rate for @quiz (%)', array(
'@quiz' => QUIZ_NAME,
)),
'#default_value' => $node->pass_rate,
'#description' => t('Pass rate for the @quiz as a percentage score. (For personality quiz enter 0, and use result options.)', array(
'@quiz' => QUIZ_NAME,
)),
'#required' => FALSE,
);
$form['summaryoptions']['summary_pass'] = array(
'#type' => 'textarea',
'#title' => t('Summary text if passed.'),
'#default_value' => $node->summary_pass,
'#cols' => 60,
'#description' => t("Summary for when the user gets enough correct answers to pass the @quiz. Leave blank if you don't want to give different summary text if they passed or if you are not using the 'percent to pass' option above. If you don't use the 'Percentage needed to pass' field above, this text will not be used.", array(
'@quiz' => QUIZ_NAME,
)),
);
}
else {
$form['summaryoptions']['pass_rate'] = array(
'#type' => 'hidden',
'#value' => variable_get('quiz_default_pass_rate', 75),
'#required' => FALSE,
);
}
$form['summaryoptions']['summary_default'] = array(
'#type' => 'textarea',
'#title' => t('Default summary text.'),
'#default_value' => $node->summary_default,
'#cols' => 60,
'#description' => t("Default summary. Leave blank if you don't want to give a summary."),
);
$form['resultoptions'] = array(
'#type' => 'fieldset',
'#title' => t('!quiz Results', array(
'!quiz' => QUIZ_NAME,
)),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
'#tree' => TRUE,
);
$options = $node->resultoptions;
$num_options = max(3, !empty($options) ? count($options) : 5);
for ($i = 0; $i < $num_options; $i++) {
$option = count($options) > 0 ? array_shift($options) : null;
$form['resultoptions'][$i] = array(
'#type' => 'fieldset',
'#title' => t('Result Option ') . ($i + 1),
'#collapsible' => TRUE,
'#collapsed' => FALSE,
);
$form['resultoptions'][$i]['option_name'] = array(
'#type' => 'textfield',
'#title' => t('The name of the result'),
'#description' => t('Not displayed on personality !quiz.', array(
'!quiz' => QUIZ_NAME,
)),
'#default_value' => $option['option_name'],
'#maxlength' => 40,
'#size' => 40,
);
$form['resultoptions'][$i]['option_start'] = array(
'#type' => 'textfield',
'#title' => t('Percentage Start Range'),
'#description' => t('Show this result for scored quizzes in this range (0-100). Leave blank for personality quizzes.'),
'#default_value' => $option['option_start'],
'#size' => 5,
);
$form['resultoptions'][$i]['option_end'] = array(
'#type' => 'textfield',
'#title' => t('Percentage End Range'),
'#description' => t('Show this result for scored quizzes in this range (0-100). Leave blank for personality quizzes.'),
'#default_value' => $option['option_end'],
'#size' => 5,
);
$form['resultoptions'][$i]['option_summary'] = array(
'#type' => 'textarea',
'#title' => t('Display text for the result'),
'#default_value' => $option['option_summary'],
'#description' => t('Result summary. This is the summary that is displayed when the user falls in this result set determined by his/her responses.'),
);
if ($option['option_id']) {
$form['resultoptions'][$i]['option_id'] = array(
'#type' => 'hidden',
'#value' => $option['option_id'],
);
}
}
return $form;
}
function _quiz_form_prepare_date($time = '', $offset = 0) {
if ($time == '') {
$time = time();
$time = strtotime("+{$offset} days", $time);
}
$time_array = array();
if (is_array($time)) {
$time_array = $time;
}
elseif (is_numeric($time)) {
$time_array = array(
'day' => _quiz_date('j', $time),
'month' => _quiz_date('n', $time),
'year' => _quiz_date('Y', $time),
);
}
return $time_array;
}
function quiz_get_number_of_questions($vid, $nid) {
return db_result(db_query("SELECT count(*) + " . "(SELECT number_of_random_questions FROM {quiz_node_properties} WHERE vid = %d AND nid = %d) " . "FROM {quiz_node_relationship} qnr " . "WHERE qnr.parent_vid = %d " . "AND qnr.parent_nid = %d " . "AND question_status = %d", $vid, $nid, $vid, $nid, QUESTION_ALWAYS));
}
function quiz_get_pass_rate($nid, $vid) {
return db_result(db_query('SELECT pass_rate FROM {quiz_node_properties} WHERE nid = %d AND vid = %d', $nid, $vid));
}
function quiz_validate($node) {
if (!$node->nid && empty($_POST)) {
return;
}
if (mktime(0, 0, 0, $node->quiz_open['month'], $node->quiz_open['day'], $node->quiz_open['year']) > mktime(0, 0, 0, $node->quiz_close['month'], $node->quiz_close['day'], $node->quiz_close['year'])) {
form_set_error('quiz_close', t('Please make sure the close date is after the open date.'));
}
if (!is_numeric($node->pass_rate)) {
form_set_error('pass_rate', t('The pass rate value must be a number between 0% and 100%.'));
}
if ($node->pass_rate > 100) {
form_set_error('pass_rate', t('The pass rate value must not be more than 100%.'));
}
if ($node->pass_rate < 0) {
form_set_error('pass_rate', t('The pass rate value must not be less than 0%.'));
}
$taken_values = array();
$num_options = 0;
foreach ($node->resultoptions as $option) {
if (!empty($option['option_name'])) {
$num_options++;
if (empty($option['option_summary'])) {
form_set_error('option_summary', t('Option has no summary text.'));
}
if ($node->pass_rate && (isset($option['option_start']) || isset($option['option_end']))) {
foreach (array(
'option_start' => 'start',
'option_end' => 'end',
) as $bound => $bound_text) {
if (!is_numeric($option[$bound])) {
form_set_error($bound, t('The range %start value must be a number between 0% and 100%.', array(
'%start' => $bound_text,
)));
}
if ($option[$bound] < 0) {
form_set_error($bound, t('The range %start value must not be less than 0%.', array(
'%start' => $bound_text,
)));
}
if ($option[$bound] > 100) {
form_set_error($bound, t('The range %start value must not be more than 100%.', array(
'%start' => $bound_text,
)));
}
}
if ($option['option_start'] > $option['option_end']) {
form_set_error('option_start', t('The start must be less than the end of the range.'));
}
$option_range = range($option['option_start'], $option['option_end']);
if ($intersect = array_intersect($taken_values, $option_range)) {
form_set_error('option_start', t('The ranges must not overlap each other. (%intersect)', array(
'%intersect' => implode(',', $intersect),
)));
}
else {
$taken_values = array_merge($taken_values, $option_range);
}
}
}
else {
if (!empty($option['option_summary'])) {
form_set_error('option_summary', t('Option has a summary, but no name.'));
}
}
}
if ($node->pass_rate == 0 && !$num_options) {
form_set_error('pass_rate', t('Unscored quiz, but no result options defined.'));
}
}
function quiz_update_quiz_question_relationship($old_quiz_vid, $new_quiz_vid, $quiz_nid) {
$sql = "INSERT INTO {quiz_node_relationship} " . "(parent_nid, parent_vid, child_nid, child_vid, question_status) " . "SELECT src.parent_nid, %d, src.child_nid, src.child_vid, src.question_status " . "FROM {quiz_node_relationship} AS src " . "WHERE src.parent_vid = %d AND src.parent_nid = %d AND src.question_status != %d";
db_query($sql, $new_quiz_vid, $old_quiz_vid, $quiz_nid, QUESTION_NEVER);
}
function quiz_insert($node) {
quiz_translate_form_date($node, 'quiz_open');
quiz_translate_form_date($node, 'quiz_close');
$sql = "INSERT INTO {quiz_node_properties} " . "(vid, nid, number_of_random_questions, shuffle, backwards_navigation, quiz_open, quiz_close, takes, pass_rate, summary_pass, summary_default, quiz_always, feedback_time, tid) " . "VALUES(%d, %d, %d, %d, %d, %d, %d, %d, %d, '%s', '%s', %d, %d, %d)";
db_query($sql, $node->vid, $node->nid, $node->number_of_random_questions, $node->shuffle, $node->backwards_navigation, $node->quiz_open, $node->quiz_close, $node->takes, $node->pass_rate, $node->summary_pass, $node->summary_default, $node->quiz_always, $node->feedback_time, $node->tid);
_quiz_insert_resultoptions($node);
}
function _quiz_insert_resultoptions($node) {
foreach ($node->resultoptions as $option) {
if ($option['option_name']) {
$option['nid'] = $node->nid;
$option['vid'] = $node->vid;
_quiz_insert_result_option($option);
}
}
}
function _quiz_insert_result_option($option) {
if (empty($option['option_id'])) {
$option['option_id'] = db_next_id('{quiz_node_result_options}' . '_option_id');
$sql = "INSERT INTO {quiz_node_result_options} (nid, vid, option_id, option_name, option_summary, option_start, option_end)" . " VALUES(%d, %d, %d, '%s', '%s', %d, %d)";
$values = array(
$option['nid'],
$option['vid'],
$option['option_id'],
$option['option_name'],
$option['option_summary'],
$option['option_start'],
$option['option_end'],
);
db_query($sql, $values);
}
}
function quiz_update($node) {
if ($node->revision) {
quiz_insert($node);
quiz_update_quiz_question_relationship($node->old_vid, $node->vid, $node->nid);
}
else {
quiz_translate_form_date($node, 'quiz_open');
quiz_translate_form_date($node, 'quiz_close');
$sql = "\n UPDATE {quiz_node_properties} SET\n vid = %d,\n shuffle = %d,\n backwards_navigation = %d,\n quiz_open = %d,\n quiz_close = %d,\n takes = %d,\n pass_rate = %d,\n summary_pass = '%s',\n summary_default = '%s',\n quiz_always = %d,\n feedback_time = %d\n WHERE\n vid = %d AND\n nid = %d\n ";
db_query($sql, $node->vid, $node->shuffle, $node->backwards_navigation, $node->quiz_open, $node->quiz_close, $node->takes, $node->pass_rate, $node->summary_pass, $node->summary_default, $node->quiz_always, $node->feedback_time, $node->vid, $node->nid);
}
_quiz_update_resultoptions($node);
}
function _quiz_update_resultoptions($node) {
foreach ($node->resultoptions as $option) {
if (!empty($option['option_name']) && empty($option['option_id'])) {
$option['nid'] = $node->nid;
$option['vid'] = $node->vid;
_quiz_insert_result_option($option);
}
else {
$sql = "UPDATE {quiz_node_result_options} SET option_name='%s', option_summary='%s', option_start = %d, " . " option_end = %d WHERE nid=%d AND vid=%d AND option_id=%d";
$values = array(
$option['option_name'],
$option['option_summary'],
$option['option_start'],
$option['option_end'],
$node->nid,
$node->vid,
$option['option_id'],
);
db_query($sql, $values);
}
}
}
function quiz_delete($node) {
db_query('DELETE FROM {quiz_node_properties} WHERE vid = %d AND nid = %d', $node->vid, $node->nid);
db_query('DELETE FROM {quiz_node_relationship} WHERE parent_nid = %d', $node->nid);
db_query('DELETE FROM {quiz_node_results} WHERE vid = %d AND nid = %d', $node->vid, $node->nid);
db_query('DELETE FROM {quiz_node_result_options} WHERE vid = %d AND nid = %d', $node->vid, $node->nid);
}
function quiz_load($node) {
$quiz_vid = $node->vid;
$additions = db_fetch_object(db_query('SELECT qnp.* FROM {quiz_node_properties} qnp WHERE qnp.vid = %d AND qnp.nid = %d ORDER BY qnp.property_id DESC', $quiz_vid, $node->nid));
$results = db_query('
SELECT nr.nid, qnr.question_status
FROM {quiz_node_relationship} qnr
INNER JOIN {node_revisions} nr ON (qnr.parent_vid = nr.vid AND qnr.parent_nid = nr.nid)
WHERE qnr.parent_vid = %d AND qnr.parent_nid = %d', $quiz_vid, $node->nid);
while ($question = db_fetch_object($results)) {
$additions->status[$question->child_nid] = $question->status;
}
$result_options = db_query('SELECT * FROM {quiz_node_result_options} WHERE nid = %d AND vid= %d', $node->nid, $node->vid);
while ($option = db_fetch_array($result_options)) {
$additions->resultoptions[$option['option_id']] = $option;
}
return $additions;
}
function quiz_view($node, $teaser = FALSE, $page = FALSE) {
if (!$teaser) {
$node->content['body']['#value'] = quiz_take_quiz();
}
else {
$node = node_prepare($node, $teaser);
}
return $node;
}
function theme_quiz_availability($node) {
$status = _quiz_availability($node);
$output = '<div class="quiz_availability"><p>';
switch ($status) {
case 'future':
$output .= t('This quiz will not be available until %time.', array(
'%time' => format_date($node->quiz_open),
));
break;
case 'open':
$output .= t('This quiz closes %time.', array(
'%time' => format_date($node->quiz_close),
));
break;
case 'closed':
$output .= t('This quiz is no longer available.');
break;
}
$output .= '</p></div>' . "\n";
return $output;
}
function theme_quiz_view($node, $teaser = FALSE, $page = FALSE) {
$output = '';
$output .= '<h3>' . t('@quiz Options', array(
'@quiz' => QUIZ_NAME,
)) . '</h3>';
$header = array(
t('# of Random Questions'),
t('Shuffle?'),
t('Feedback'),
t('Number of takes'),
);
$shuffle = $node->shuffle == 1 ? t('Yes') : t('No');
$feedback_options = _quiz_get_feedback_options();
$feedback = $feedback_options[$node->feedback_time];
$takes = $node->takes == 0 ? t('Unlimited') : check_plain($node->takes);
$rows = array();
$rows[] = array(
check_plain($node->number_of_random_questions),
$shuffle,
$feedback,
$takes,
);
$output .= theme('table', $header, $rows);
$output .= '<h3>' . t('@quiz start/end', array(
'@quiz' => QUIZ_NAME,
)) . '</h3>';
if (!$node->quiz_always) {
if (is_array($node->quiz_open)) {
quiz_translate_form_date($node, 'quiz_open');
}
if (is_array($node->quiz_close)) {
quiz_translate_form_date($node, 'quiz_close');
}
$output .= '<p>' . format_date($node->quiz_open) . ' — ' . format_date($node->quiz_close) . '</p>';
$output .= '<p><strong>' . t('Days @quiz live for: ', array(
'@quiz' => QUIZ_NAME,
)) . '</strong> ' . floor(($node->quiz_close - $node->quiz_open) / 60 / 60 / 24) . '</p>';
$remaining = floor(($node->quiz_close - time()) / 60 / 60 / 24);
$remaining = $remaining < 0 ? 'Expired' : $remaining;
$output .= '<p><strong>Days remaining:</strong> ' . $remaining . '</p>';
$elapsed = floor((time() - $node->quiz_open) / 60 / 60 / 24);
$elapsed = $elapsed < 0 ? -$elapsed . ' days to go' : $elapsed;
$output .= '<p><strong>Days since start:</strong> ' . $elapsed . '</p>';
}
else {
$output .= '<p>' . t('This Quiz is always available.') . '</p>' . "\n";
}
if (function_exists(taxonomy_node_get_terms)) {
$output .= '<h3>' . t('Taxonomy selection') . '</h3>';
$terms = array();
foreach (taxonomy_node_get_terms($node->nid) as $term) {
$terms[] = check_plain($term->name);
}
if (!empty($terms)) {
$terms = implode(', ', $terms);
$output .= "<p>{$terms}</p>";
}
else {
$output .= '<p>' . t('No selected terms found') . '</p>';
}
}
if ($node->pass_rate || $node->summary_default || $node->summary_pass) {
if ($node->pass_rate) {
$output .= '<h3>' . t('Pass / fail and summary options') . '</h3>' . "\n";
$output .= '<p><strong>' . t('Percentage needed to pass:') . '</strong> ' . check_plain($node->pass_rate) . '</p>' . "\n";
$output .= '<div><strong>' . t('Summary text if the user passed:') . '</strong> ';
$output .= $node->summary_pass ? check_markup($node->summary_pass) : t('No text defined.');
$output .= '</div>' . "\n";
}
$output .= '<div><strong>' . t('Default summary text:') . '</strong> ';
$output .= $node->summary_default ? check_markup($node->summary_default) : t('No text defined.');
$output .= '</div>' . "\n";
}
if (count($node->resultoptions)) {
$scored_quiz = $node->pass_rate > 0;
$output .= '<h3>' . t('!quiz Results', array(
'!quiz' => QUIZ_NAME,
)) . '</h3>';
$header = array(
t('Name') => 'option_name',
t('Summary') => 'option_summary',
);
if ($scored_quiz) {
$header = array_merge($header, array(
t('Start') => 'option_start',
t('End') => 'option_end',
));
}
$values = array_values($header);
foreach ($node->resultoptions as $option) {
$row = array();
foreach ($values as $field) {
$row[] = $option[$field];
}
$option_rows[] = $row;
}
$output .= theme('table', array_keys($header), $option_rows);
}
if (is_numeric(arg(1))) {
$output .= '<h3>' . t('@quiz Questions', array(
'@quiz' => QUIZ_NAME,
)) . '</h3>';
$questions = _quiz_get_questions($node->vid);
$output .= theme('quiz_question_table', $questions, $node->nid);
}
return $output;
}
function quiz_get_user_results() {
global $user;
$results = array();
$dbresult = db_query("SELECT\n n.nid as nid,\n n.title as title,\n u.name as name,\n qnrs.result_id as result_id,\n qnrs.time_start,\n qnrs.time_end\n FROM {node} n, {quiz_node_properties} qnp, {quiz_node_results} qnrs, {users} u\n WHERE\n n.type = 'quiz'\n AND\n n.nid = qnp.nid\n AND\n qnrs.nid = qnp.nid\n AND\n u.uid = qnrs.uid\n AND\n u.uid = " . $user->uid . "\n ORDER BY qnrs.result_id ASC");
while ($line = db_fetch_array($dbresult)) {
$results[$line['result_id']] = $line;
}
return theme('quiz_get_user_results', $results);
}
function quiz_take_quiz() {
global $user;
if (arg(0) == 'node' && is_numeric(arg(1)) && user_access('access quiz')) {
if ($quiz = node_load(arg(1))) {
if (!$user->uid && arg(4) == NULL) {
drupal_goto('node/' . $quiz->nid . '/quiz/start/' . md5(mt_rand() . time()));
}
if ($user->uid && $quiz->takes != 0) {
$times = db_num_rows(db_query("SELECT result_id " . "FROM {quiz_node_results} " . "WHERE nid = %d AND vid = %d AND uid = %d", $quiz->nid, $quiz->vid, $user->uid));
if ($times >= $quiz->takes) {
drupal_set_message(t('You have already taken this @quiz %d times.', array(
'@quiz' => QUIZ_NAME,
'%d' => $times,
)), 'status');
return;
}
}
if (!isset($_SESSION['quiz_' . $quiz->nid]['quiz_questions'])) {
if ($rid = quiz_start_actions($quiz)) {
$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,
'!add more questions' => l('add more questions', 'node/' . arg(1) . '/questions'),
)), 'error');
return '';
}
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('assign questions', 'node/' . arg(1) . '/questions'),
)), 'error');
return '';
}
$_SESSION['quiz_' . $quiz->nid]['quiz_questions'] = $questions;
$_SESSION['quiz_' . $quiz->nid]['result_id'] = $rid;
$_SESSION['quiz_' . $quiz->nid]['question_number'] = 0;
}
else {
return '';
}
}
if ($_POST['op'] == t('Back')) {
unset($_POST['tries']);
array_unshift($_SESSION['quiz_' . $quiz->nid]['quiz_questions'], $_SESSION['quiz_' . $quiz->nid]['previous_quiz_questions']);
}
if ($_POST['op'] == t('Submit')) {
if (!isset($_POST['tries'])) {
drupal_set_message(t('You must select an answer before you can progress to the next question!'), 'error');
}
else {
unset($_SESSION['quiz_' . $quiz->nid]['previous_quiz_questions']);
$_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(array(
'nid' => $former_question_array['nid'],
));
$result = module_invoke($former_question->type, 'evaluate_question', $former_question, $_SESSION['quiz_' . $quiz->nid]['result_id']);
quiz_store_question_result($former_question_array['nid'], $former_question_array['vid'], $_SESSION['quiz_' . $quiz->nid]['result_id'], $result);
if ($quiz->feedback_time == QUIZ_FEEDBACK_QUESTION) {
$report = module_invoke($former_question->type, 'get_report', $former_question_array['nid'], $former_question_array['vid'], $_SESSION['quiz_' . $quiz->nid]['result_id']);
$_SESSION['quiz_' . $quiz->nid]['feedback'] = rawurlencode(quiz_get_feedback($quiz, $report));
}
if (!$user->uid) {
drupal_goto('node/' . $quiz->nid . '/quiz/start/' . md5(mt_rand() . time()));
}
}
}
if (isset($_SESSION['quiz_' . $quiz->nid]['feedback']) && $quiz->feedback_time == QUIZ_FEEDBACK_QUESTION) {
$output .= rawurldecode($_SESSION['quiz_' . $quiz->nid]['feedback']);
}
if (!empty($_SESSION['quiz_' . $quiz->nid]['quiz_questions'])) {
$question_node = node_load(array(
'nid' => $_SESSION['quiz_' . $quiz->nid]['quiz_questions'][0]['nid'],
));
$output .= theme('quiz_take_question', $quiz, $question_node);
unset($_SESSION['quiz_' . $quiz->nid]['feedback']);
return $output;
}
else {
$score = quiz_end_actions($quiz, $_SESSION['quiz_' . $quiz->nid]['result_id']);
if ($quiz->feedback_time == QUIZ_FEEDBACK_NEVER) {
$output = theme('quiz_no_feedback');
}
else {
$questions = _quiz_get_answers($_SESSION['quiz_' . $quiz->nid]['result_id']);
$summary = _quiz_get_summary_text($quiz, $score);
$output .= theme('quiz_take_summary', $quiz, $questions, $score, $summary);
}
unset($_SESSION['quiz_' . $quiz->nid]);
return $output;
}
}
}
drupal_not_found();
}
function quiz_store_question_result($nid, $vid, $rid, $is_correct) {
$result = db_query("SELECT result_id FROM {quiz_node_results_answers} WHERE question_nid = %d AND question_vid = %d AND result_id = %d", $nid, $vid, $rid);
if (db_num_rows($result)) {
db_query("UPDATE {quiz_node_results_answers} " . "SET is_correct = %d, points_awarded = %d, answer_timestamp = %d " . "WHERE question_nid = %d AND question_vid = %d AND result_id = %d", $is_correct, $is_correct ? 1 : 0, time(), $nid, $vid, $rid);
}
else {
db_query("INSERT INTO {quiz_node_results_answers} " . "(question_nid, question_vid, result_id, is_correct, points_awarded, answer_timestamp) " . "VALUES (%d, %d, %d, %d, %d, %d)", $nid, $vid, $rid, $is_correct, $is_correct ? 1 : 0, time());
}
}
function quiz_end_actions($quiz, $rid) {
$score = quiz_calculate_score($quiz, $rid);
db_query("UPDATE {quiz_node_results} " . "SET time_end = %d, score = %d " . "WHERE result_id = %d", time(), $score['percentage_score'], $_SESSION['quiz_' . $quiz->nid]['result_id']);
return $score;
}
function quiz_get_feedback($quiz, $report) {
return theme('quiz_question_feedback', $quiz, $report);
}
function quiz_get_answers($answers, $tried) {
$selected = array();
if (is_array($answers)) {
foreach ($answers as $key => $answer) {
if (($key = array_search($answer['aid'], $tried)) !== FALSE) {
$selected[] = $answer;
unset($tried[$key]);
}
}
}
return $selected;
}
function quiz_get_corrects($answers) {
if (is_array($answers)) {
foreach ($answers as $answer) {
if ($answer['is_correct'] > 0) {
$corrects[] = $answer;
}
}
}
return $corrects;
}
function _quiz_get_summary_text($quiz, $score) {
if ($score['result_option']) {
return $score['result_option'];
}
$admin = arg(3) == 'view';
if ($quiz->pass_rate > 0) {
$score_result = _quiz_pick_result_option($quiz->nid, $quiz->vid, $score['percentage_score']);
}
if ($quiz->pass_rate > 0 && $score['percentage_score'] >= $quiz->pass_rate) {
if ($admin) {
$summary = t('The user passed this quiz.');
}
else {
if (trim($quiz->summary_pass) != '') {
$summary = !empty($score_result) ? $score_result : check_markup($quiz->summary_pass, $quiz->format, FALSE);
}
}
}
else {
if ($admin) {
if ($quiz->pass_rate > 0) {
$summary = t('The user failed this quiz.');
}
}
else {
if (trim($quiz->summary_pass) != '') {
$summary = !empty($score_result) ? $score_result : check_markup($quiz->summary_default, $quiz->format, FALSE);
}
}
}
return $summary;
}
function _quiz_pick_result_option($qnid, $qvid, $score) {
return db_result(db_query('SELECT option_summary FROM {quiz_node_result_options} WHERE nid = %d AND vid = %d AND %d BETWEEN option_start AND option_end', $qnid, $qvid, $score));
}
function quiz_start_actions($quiz) {
if (!$quiz->quiz_always == 1) {
if (gmmktime() >= $quiz->quiz_close || gmmktime() < $quiz->quiz_open) {
drupal_set_message(t('This @quiz is not currently available.', array(
'@quiz' => QUIZ_NAME,
)), 'status');
if (!user_access('create quiz')) {
return FALSE;
}
}
}
global $user;
if ($user->uid) {
$passed = db_result(db_query("SELECT result_id " . "FROM {quiz_node_results} qnrs " . "INNER JOIN {quiz_node_properties} USING (vid, nid) " . "WHERE qnrs.vid = %d AND qnrs.nid = %d AND qnrs.uid =%d " . "AND score >= pass_rate", $quiz->vid, $quiz->nid, $user->uid));
if ($passed) {
drupal_set_message(t('You have already passed this @quiz.', array(
'@quiz' => QUIZ_NAME,
)), 'status');
}
}
$rid = db_next_id('{quiz_node_results}_result_id');
$result = db_query("INSERT INTO {quiz_node_results} " . "(result_id, nid, vid, uid, time_start) " . "VALUES (%d, %d, %d, %d, %d)", $rid, $quiz->nid, $quiz->vid, $user->uid, time());
if ($result) {
return $rid;
}
else {
drupal_set_message(t('There was a problem starting the @quiz. Please try again later.', array(
'@quiz' => QUIZ_NAME,
), 'error'));
return FALSE;
}
}
function quiz_calculate_score($quiz, $rid) {
if ($quiz->pass_rate > 0) {
$score = db_fetch_array(db_query("SELECT count(*) as question_count, sum(is_correct) as num_correct " . "FROM {quiz_node_results_answers} " . "WHERE result_id = %d", $rid));
if ($score['question_count'] > 0) {
$score['percentage_score'] = round($score['num_correct'] * 100 / $score['question_count']);
}
}
else {
$score = db_fetch_array(db_query("SELECT " . "(SELECT count(*) FROM {quiz_node_results_answers} WHERE result_id = %d) as question_count," . "(SELECT option_summary " . "FROM {quiz_multichoice_user_answers} " . "LEFT JOIN {quiz_multichoice_answers} USING (answer_id) " . "LEFT JOIN {quiz_node_result_options} ON (result_option = option_id) " . "WHERE result_id = %d " . "GROUP BY result_option ORDER BY COUNT(result_option) desc LIMIT 1) as result_option", $rid, $rid));
}
return $score;
}
function quiz_build_question_list($quiz) {
$questions = array();
$result = db_query("SELECT child_nid as nid, child_vid as vid FROM {quiz_node_relationship} WHERE parent_vid = %d AND parent_nid = %d AND question_status = %d", $quiz->vid, $quiz->nid, QUESTION_ALWAYS);
while ($question_node = db_fetch_array($result)) {
$questions[] = $question_node;
}
if ($quiz->number_of_random_questions > 0) {
$questions = array_merge($questions, _quiz_get_random_questions($quiz->number_of_random_questions, $quiz->tid));
if ($quiz->number_of_random_questions > count($questions)) {
return FALSE;
}
}
if ($quiz->shuffle == 1) {
shuffle($questions);
}
return $questions;
}
function _quiz_get_random_questions($num_random, $tid) {
$questions = array();
if ($num_random > 0) {
if ($tid > 0) {
$term = taxonomy_get_term($tid);
$tree = taxonomy_get_tree($term->vid, $term->tid);
$term_ids[] = $term->tid;
if (is_array($tree)) {
foreach ($tree as $term) {
$term_ids[] = $term->tid;
}
}
$term_ids = implode(',', $term_ids);
$result = db_query_range("SELECT n.nid, n.vid " . "FROM {node} n " . "INNER JOIN {term_node} tn USING (nid)" . "WHERE n.status = 1 AND tn.tid IN ({$term_ids}) " . "AND n.type IN ('" . implode("','", _quiz_get_question_types()) . "') " . "ORDER BY RAND()", 0, $num_random);
}
else {
$result = db_query_range("SELECT child_nid as nid, child_vid as vid FROM {quiz_node_relationship} WHERE parent_vid = %d AND parent_nid = %d AND question_status = %d ORDER BY RAND()", $quiz->vid, $quiz->nid, QUESTION_RANDOM, 0, $quiz->number_of_random_questions);
}
while ($question_node = db_fetch_array($result)) {
$questions[] = $question_node;
}
}
return $questions;
}
function quiz_help($section) {
switch ($section) {
case 'admin/help#quiz':
return t('
<h3>Description</h3>
<p>The quiz module allows users to administer a quiz, as a sequence of questions, and track the answers given. It allows for the creation of questions (and their answers), and organizes these questions into a quiz. Finally, it provides a mechanism for ensuring question quality through a combination of community revision and moderation. Its target audience includes educational institutions, online training programs, employers, and people who just want to add a fun activity for their visitors to their Drupal site.</p>
<h3>Creating Your First Quiz</h3>
<p>Creating an initial quiz requires three steps:</p>
<ol>
<li>Create at least one taxonomy vocabulary and assign it to the quiz and question type modules</li>
<li>Create a series of questions</li>
<li>Create a quiz based on the series of questions</li>
</ol>
<h4>Setting Quiz Permissions</h4>
<p>The quiz module and included multichoice module both have a plethora of permission options.<br />Unless you take care setting your permissions, the quiz module might not do everything you want it to do.</p>
<h5><strong><a href="@admin-access#module-quiz">Quiz Permissions</a></strong></h5>
<dl><dt><strong>access quiz</strong></dt>
<dd>allows users to take quizzes</dd>
<dt><strong>administer quiz</strong></dt>
<dd>allows users to edit quizzes</dd>
<dt><strong>administer quiz configuration</strong></dt>
<dd>allows users to use global admin settings, as well as override userresults if they don\'t have that permission. Can also delete quizzes</dd>
<dt><strong>create quiz</strong></dt>
<dd>Can create a new quiz</dd>
<dt><strong>user results</strong></dt>
<dd>Can view other user\'s results.</dd></dl>
<h5><strong><a href="@admin-access#module-multichoice">Multichoice Permissions</a></strong></h5>
<dl><dt><strong>allow any number of answers</strong></dt>
<dd>can submit questions with more than one correct answer.</dd>
<dt><strong>allow feedback</strong></dt>
<dd>Can create feedback when creating a new multichoice question.</dd>
<dt><strong>allow multiple correct answers</strong></dt>
<dd></dd>
<dt><strong>allow user titles</strong></dt>
<dd>Allows users to pick a name for their questions. Otherwise this is auto generated. The question name is never seen on the @quiz.</dd>
<dt><strong>create multichoice</strong></dt>
<dd>users can create multichoice questions</dd>
<dt><strong>edit own multichoice</strong></dt>
<dd>can edit their own multi-choice questions.</dd></dl>
<h4>Setting up a vocabulary</h4>
<ol>
<li>If not already enabled, go to the <a href="@admin-modules">Administer >> Site building >> Modules</a> section of the control panel and check the <strong>enable</strong> checkbox to enable the <strong>taxonomy module</strong>.</li>
<li>If you do not already have a taxonomy <strong>vocabulary</strong> suitable for quizzes, go to <a href="@admin-taxonomy">Administer >> Content management >> Categories</a> and create a vocabulary for quizzes (for example, <strong>Quiz Topics</strong>). Ensure that under <strong>Types</strong>, both <strong>quiz</strong> and all question types (for example, <strong>multichoice</strong>) are selected. Depending on your needs, you may wish to create a hierarchical vocabulary, so that topics can be sub-divided into smaller areas, and/or enable multiple select to associate quizzes and questions with more than one category.</li>
<li>Add a series of <strong>terms</strong> to the vocabulary to which questions and quizzes can be assigned. For example:
<ul>
<li>Literature
<ul>
<li>Children\'s books</li>
<li>Poetry</li>
<li>Shakespeare</li>
</ul>
</li>
<li>Mathematics
<ul>
<li>Algebra</li>
<li>Trigonometry</li>
<li>Calculus</li>
</ul>
</li>
<li>Information Technology
<ul>
<li>Hardware</li>
<li>Programming</li>
<li>Databases</li>
</ul>
</li>
</li>
</ol>
<h4>Creating quiz questions</h4>
<ol>
<li>Begin by clicking <a href="@create-content">Create content</a>, and then select a question type node (for example, <a href="@multichoice">multichoice</a>)</li>
<li>Fill out the question form. The presented interface will vary depending on the question type, but for multiple choice questions:
<dl>
<dt><strong>Title</strong></dt>
<dd>Question title. This will be displayed as the heading of the question.</dd>
<dt><strong>Taxonomy selection</strong></dt>
<dd>Any taxonomy vocabularies that are assigned to the question type will be displayed.</dd>
<dt><strong>Question</strong></dt>
<dd>The actual question text (for example, <strong>What is 2+2?</strong>).</dd>
<dt><strong>Multiple Answers</strong></dt>
<dd>Whether or not the question has multiple correct answers, such as a "Select all that apply" question.</dd>
<dt><strong>Correct</strong></dt>
<dd>Indicates that given answer is a correct answer.</dd>
<dt><strong>Answer</strong></dt>
<dd>An answer choice (for example, <strong>4</strong>). If more answers are required, check <strong>I need more answers</strong> and click the <b>Preview</b> button.</dd>
<dt><strong>Feedback</strong></dt>
<dd>Feedback, if supplied, will be provided to the user at the end of the quiz.</dd>
</dl>
</li>
<li>Repeat for each question you would like included on the quiz.</li>
</ol>
<h4>Creating the quiz</h4>
<ol>
<li>Go to <a href="@create-quiz">Create content >> Quiz</a> to access the quiz creation form.</li>
<li>Fill out the form to set the @quiz options:
<dl>
<dt><strong>Title</strong></dt>
<dd>Quiz title. This will be displayed as the heading of the quiz.</dd>
<dt><strong>Taxonomy selection</strong></dt>
<dd>Any taxonomy vocabularies that are assigned to the quiz type will be displayed. Select from the terms displayed in order to assign the quiz to vocabulary terms.</dd>
<dt><strong>Shuffle questions</strong></dt>
<dd>Whether or not to shuffle (randomize) the questions.</dd>
<dt><strong>Number of takes</strong></dt>
<dd>Number of takes to allow user. Varies from 1-9 or Unlimited times.</dd>
</dl>
</li>
<li>Once the quiz has been created, click the <b>add questions</b> tab to assign questions to the quiz.</li>
<li>Select a radio button next to each question indicating if the question should appear (Randomly, Always, or Never), and click <strong>Submit questions</strong>.</li>
<li>Repeat process until satisfied with question selection.</li>
</ol>
', array(
'@quiz' => QUIZ_NAME,
'@admin-access' => url('admin/user/access'),
'@admin-modules' => url('admin/build/modules'),
'@admin-taxonomy' => url('admin/content/taxonomy'),
'@create-content' => url('node/add'),
'@multichoice' => url('node/add/multichoice'),
'@create-quiz' => url('node/add/quiz'),
));
case 'node/add#quiz':
return t('A collection of questions designed to create interactive tests');
default:
break;
}
}
function _quiz_get_question_types() {
return module_implements('list_questions');
}
function _quiz_get_vocabularies() {
$vocabularies = array();
foreach (_quiz_get_question_types() as $type) {
foreach (taxonomy_get_vocabularies($type) as $vid => $vocabulary) {
$vocabularies[$vid] = $vocabulary;
}
}
return $vocabularies;
}
function _quiz_taxonomy_select($value = 0) {
$options = array();
foreach (_quiz_get_vocabularies() as $vid => $vocabulary) {
$temp = taxonomy_form($vid, $value);
$options = array_merge($options, $temp['#options']);
}
return $options;
}
function _quiz_get_questions($quiz_vid = NULL, $include_all_types = TRUE, $nid_keys = FALSE) {
$quiz = node_load((int) arg(1));
$filter_types = '';
$questions = array();
$where_add = array();
$where_sql = '';
if ($include_all_types === TRUE) {
$types = _quiz_get_question_types();
if (count($types)) {
$str_types = "'" . implode("','", $types) . "'";
$where_add[] = 'question.type IN ( ' . $str_types . ' )';
}
}
if (!is_null($quiz_vid)) {
$where_add[] = 'qnr.parent_vid = ' . (int) $quiz_vid;
$where_add[] = 'qnr.parent_nid = ' . $quiz->nid;
}
$where_add[] = 'question.status = 1';
if (count($where_add)) {
$where_sql = ' WHERE ';
foreach ($where_add as $where) {
$where_sql .= $where . ' AND ';
}
$where_sql = trim($where_sql, ' AND ');
}
$result = db_query('
SELECT DISTINCT question.nid, question.vid, question.type, nr.body, nr.format, qnr.question_status
FROM {node} question
INNER JOIN {node_revisions} nr ON question.nid = nr.nid
LEFT JOIN {quiz_node_relationship} qnr
ON nr.vid = qnr.child_vid
AND qnr.parent_vid = %d
AND qnr.question_status != %d
' . $where_sql, $quiz_vid, QUESTION_NEVER);
if ($nid_keys === FALSE) {
while ($node = db_fetch_object($result)) {
$questions[] = quiz_node_map($node);
}
}
else {
while ($node = db_fetch_object($result)) {
$n = quiz_node_map($node);
$questions[$n->nid] = $n;
}
}
return $questions;
}
function quiz_questions_form() {
$quiz = node_load(arg(1));
drupal_set_title(check_plain($quiz->title));
$form['additional_questions'] = array(
'#type' => 'fieldset',
'#title' => t('Create additional questions'),
'#theme' => 'additional_questions',
);
foreach (_quiz_get_question_types() as $type) {
$form['additional_questions'][$type] = array(
'#type' => 'markup',
'#value' => l(t($type), 'node/add/' . $type . '/' . $quiz->nid, array(
'title' => t('Go to ' . $type . ' administration'),
)) . ' ',
);
}
$form['filtered_question_list_always'] = array(
'#type' => 'fieldset',
'#title' => t("Questions 'always' on this quiz"),
'#theme' => 'quiz_filtered_questions',
'#collapsible' => TRUE,
'question_status' => array(
'#tree' => TRUE,
),
);
$form['filtered_random_question_list']['num_random_questions'] = array(
'#type' => 'textfield',
'#title' => t('Number questions to randomize'),
'#size' => 3,
'#default_value' => $quiz->number_of_random_questions,
'#description' => t('The number of randomly selected questions to assign to this quiz.'),
);
$form['filtered_random_question_list']['random_term_id'] = array(
'#type' => 'select',
'#title' => t('Terms'),
'#size' => 1,
'#options' => _quiz_taxonomy_select($quiz->tid),
'#default_value' => $quiz->tid,
'#description' => t('Randomly select from questions with this term, or choose from the random question pool below'),
);
$form['filtered_question_list_random'] = array(
'#type' => 'fieldset',
'#title' => t("Questions 'random' on this quiz"),
'#theme' => 'quiz_filtered_questions',
'#collapsible' => TRUE,
'question_status' => array(
'#tree' => TRUE,
),
);
$form['filtered_question_list'] = array(
'#type' => 'fieldset',
'#title' => t("Questions 'never' on this quiz"),
'#theme' => 'quiz_filtered_questions',
'#collapsible' => TRUE,
'question_status' => array(
'#tree' => TRUE,
),
);
$questions = array_merge(_quiz_get_unused_questions($quiz->vid), _quiz_get_questions($quiz->vid));
foreach ($questions as $question) {
switch ($question->status) {
case QUESTION_RANDOM:
$_form =& $form['filtered_question_list_random'];
break;
case QUESTION_ALWAYS:
$_form =& $form['filtered_question_list_always'];
break;
case QUESTION_NEVER:
$_form =& $form['filtered_question_list'];
break;
}
$_form['question_status'][$question->nid] = array(
'#type' => 'radios',
'#options' => array(
QUESTION_RANDOM => '',
QUESTION_ALWAYS => '',
QUESTION_NEVER => '',
),
'#default_value' => $question->status,
);
$_form['question'][$question->nid] = array(
'#type' => 'markup',
'#value' => $question->question,
);
$_form['type'][$question->nid] = array(
'#type' => 'markup',
'#value' => $question->type,
);
}
$form['filtered_question_list_always']['#title'] .= ' (' . count($form['filtered_question_list_always']['type']) . ')';
$form['new_revision'] = array(
'#type' => 'checkbox',
'#default_value' => in_array('revision', variable_get('node_options_quiz', array())),
'#title' => t('New Revision'),
'#description' => t('Allow question status changes to create a new revision of the quiz?'),
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Submit questions'),
);
$form['timestamp'] = array(
'#type' => 'hidden',
'#value' => time(),
);
return $form;
}
function _quiz_get_unused_questions($quiz_vid = NULL, $nid_keys = FALSE) {
$quiz = node_load((int) arg(1));
$types = _quiz_get_question_types();
$where_sql = '';
$questions = array();
if (count($types)) {
$where_sql = "AND question.type IN ('" . implode("','", $types) . "')";
}
$result = db_query('
SELECT DISTINCT question.nid, question.vid, question.type, nr.body, nr.format
FROM {node} question
LEFT JOIN {node_revisions} nr ON (question.nid = nr.nid)
WHERE question.status = 1
AND (question.vid NOT IN
(SELECT DISTINCT qnr.child_vid
FROM {quiz_node_relationship} qnr
WHERE qnr.parent_vid = %d
AND qnr.question_status != ' . QUESTION_NEVER . '))
' . $where_sql, $quiz_vid);
if ($nid_keys === FALSE) {
while ($node = db_fetch_object($result)) {
$questions[] = quiz_node_map($node);
}
}
else {
while ($node = db_fetch_object($result)) {
$n = quiz_node_map($node);
$questions[$n->nid] = $n;
}
}
return $questions;
}
function quiz_questions() {
return drupal_get_form('quiz_questions_form');
}
function quiz_questions_form_submit($form_id, $values) {
$quiz = node_load(arg(1));
if (!quiz_update_questions($values['question_status'], $values['new_revision'])) {
form_set_error('', t('Either no questions were selected, or there was a problem updating your @quiz. Please try again.', array(
'@quiz' => QUIZ_NAME,
)));
return;
}
if (empty($values['random_term_id'])) {
$assigned_random = 0;
if (is_array($values['question_status'])) {
foreach ($values['question_status'] as $id => $status) {
if (QUESTION_ALWAYS == $status) {
$assigned_random++;
}
}
}
if ($values['num_random_questions'] > $assigned_random) {
$values['num_random_questions'] = $assigned_random;
drupal_set_message(t('The number of random questions for this @quiz have been lowered to %anum to match the number of questions you assigned.', array(
'@quiz' => QUIZ_NAME,
'%anum' => $assigned_random,
), 'status'));
}
}
else {
$available_random = count(_quiz_get_random_questions($values['num_random_questions'], $values['random_term_id']));
if ($values['num_random_questions'] > $available_random) {
drupal_set_message(t('There are currently not enough questions assigned to this term (@random). Please lower the number of random quetions or assign more questions to this taxonomy term before taking this @quiz.', array(
'@random' => $available_random,
'@quiz' => QUIZ_NAME,
)), 'error');
}
}
$result = db_query("UPDATE {quiz_node_properties} SET number_of_random_questions = %d, tid = %d WHERE vid = %d AND nid = %d", $values['num_random_questions'], $values['random_term_id'], $quiz->vid, $quiz->nid);
if (!$result) {
drupal_set_message(t('There was an error updating the @quiz.', array(
'@quiz' => QUIZ_NAME,
)), 'error');
}
else {
drupal_set_message(t('Questions updated successfully.'));
}
}
function quiz_get_num_questions($nid, $vid, $type) {
return db_num_rows(db_query("SELECT parent_vid FROM {quiz_node_relationship} WHERE parent_vid = %d AND parent_nid = %d AND question_status = %d", $vid, $nid, $type));
}
function quiz_node_map($node) {
$new_question = new stdClass();
$new_question->question = check_markup($node->body, $node->format);
$new_question->nid = $node->nid;
$new_question->vid = $node->vid;
$new_question->type = $node->type;
$new_question->status = isset($node->question_status) ? $node->question_status : QUESTION_NEVER;
return $new_question;
}
function quiz_update_questions($submitted_questions, $revision = FALSE) {
$quiz = node_load(arg(1));
$return = true;
if (empty($submitted_questions)) {
return FALSE;
}
$questions_in_db = _quiz_get_unused_questions($quiz->vid, TRUE) + _quiz_get_questions($quiz->vid, TRUE, TRUE);
$status_in_db = array();
foreach ($questions_in_db as $question) {
$status_in_db[$question->nid] = $question->status;
}
$changes = array_diff_assoc($submitted_questions, $status_in_db);
if (!empty($changes)) {
drupal_execute('node_form', array(
'revision' => '1',
), $quiz);
drupal_set_message(t('A new revision of the @quiz has been created.', array(
'@quiz' => QUIZ_NAME,
)));
}
$inserts = array();
$updates = array();
foreach ($changes as $nid => $status) {
if ($status_in_db[$nid] == QUESTION_NEVER) {
$inserts[$nid] = $status;
}
else {
$updates[$nid] = $status;
}
}
foreach ($inserts as $nid => $status) {
$child_vid = $questions_in_db[$nid]->vid;
$sql = "INSERT INTO {quiz_node_relationship} (parent_nid, parent_vid, child_nid, child_vid, question_status) " . "SELECT src.nid, src.vid, %d, %d, %d FROM {node} AS src WHERE src.nid = %d";
$result = db_query($sql, $nid, $child_vid, $status, $quiz->nid);
if (!$result) {
return FALSE;
}
}
foreach ($updates as $nid => $status) {
$child_vid = $questions_in_db[$nid]->vid;
$sql = "UPDATE {quiz_node_relationship} SET question_status = %d " . "WHERE parent_vid = (SELECT src.vid FROM {node} AS src WHERE src.nid = %d) AND parent_nid = %d AND child_vid = %d";
$result = db_query($sql, $status, $quiz->nid, $quiz->nid, $child_vid);
if (!$result) {
return FALSE;
}
}
return TRUE;
}
function quiz_admin_settings() {
$form = array();
$form['quiz_default_close'] = array(
'#type' => 'textfield',
'#title' => t('Default number of days before a @quiz is closed', array(
'@quiz' => QUIZ_NAME,
)),
'#default_value' => variable_get('quiz_default_close', 30),
'#description' => t('Supply a number of days to calculate the default close date for new quizzes.'),
);
$form['quiz_use_passfail'] = array(
'#type' => 'checkbox',
'#title' => t('Display pass/fail options in the @quiz form', array(
'@quiz' => QUIZ_NAME,
)),
'#default_value' => variable_get('quiz_use_passfail', 1),
'#description' => t('Check this to display the pass/fail options in the @quiz form. If you want to prohibit other quiz creators from changing the default pass/fail percentage set below, uncheck this option.', array(
'@quiz' => QUIZ_NAME,
)),
);
$form['quiz_default_pass_rate'] = array(
'#type' => 'textfield',
'#title' => t('Default percentage needed to pass a @quiz', array(
'@quiz' => QUIZ_NAME,
)),
'#default_value' => variable_get('quiz_default_pass_rate', 75),
'#description' => t('Supply a number between 1 and 100 to set as the default percentage correct needed to pass a quiz. Set to 0 if you want to ignore pass/fail summary information by default.'),
);
$form['quiz_name'] = array(
'#type' => 'textfield',
'#title' => t('Assessment name'),
'#default_value' => QUIZ_NAME,
'#description' => t('How do you want to refer to quizzes across the site (for example: quiz, test, assessment). This will affect display text but will not affect menu paths.'),
'#required' => TRUE,
);
return system_settings_form($form);
}
function quiz_settings_form_validate($form_id, $form_values) {
if (!is_numeric($form_values['num_random_questions']) || $form_values['num_random_questions'] < 0) {
form_set_error('num_random_questions', t('The number of random questions must be at least 0.'));
}
if (!is_numeric($form_values['quiz_default_close']) || $form_values['quiz_default_close'] <= 0) {
form_set_error('quiz_default_close', t('The default number of days before a quiz is closed must be a number greater than 0.'));
}
if (!is_numeric($form_values['quiz_default_pass_rate'])) {
form_set_error('quiz_default_pass_rate', t('The pass rate value must be a number between 0% and 100%.'));
}
if ($form_values['quiz_default_pass_rate'] > 100) {
form_set_error('quiz_default_pass_rate', t('The pass rate value must not be more than 100%.'));
}
if ($form_values['quiz_default_pass_rate'] < 0) {
form_set_error('quiz_default_pass_rate', t('The pass rate value must not be less than 0%.'));
}
}
function quiz_admin() {
$results = _quiz_get_results();
return theme('quiz_admin', $results);
}
function _quiz_get_results($nid = '', $uid = 0) {
$results = array();
$args = array();
$sql = "SELECT n.nid as nid,\n n.title as title,\n u.name as name,\n qnrs.result_id as result_id,\n qnrs.time_start,\n qnrs.time_end\n FROM {node} n, {quiz_node_properties} qnp, {quiz_node_results} qnrs, {users} u\n WHERE\n n.type = 'quiz'\n AND\n n.nid = qnp.nid\n AND\n qnrs.nid = qnp.nid\n AND\n u.uid = qnrs.uid";
if ($nid) {
$sql .= " AND qnrs.nid = %d";
$args[] = $nid;
}
if ($uid != 0) {
$sql .= " AND qnrs.uid = %d";
$args[] = $uid;
}
$sql .= " ORDER BY qnrs.result_id ASC";
$dbresult = db_query($sql, $args);
while ($line = db_fetch_array($dbresult)) {
$results[$line['result_id']] = $line;
}
return $results;
}
function quiz_user_results() {
$result = db_fetch_object(db_query('SELECT qnp.nid FROM {quiz_node_properties} qnp, {quiz_node_results} qnrs WHERE qnrs.nid = qnp.nid AND qnrs.result_id = %d', arg(2)));
if ($result->nid) {
$quiz = node_load($result->nid);
$questions = _quiz_get_answers(arg(2));
$score = quiz_calculate_score($quiz, arg(2));
$summary = _quiz_get_summary_text($quiz, $score);
return theme('quiz_user_summary', $quiz, $questions, $score, $summary);
}
else {
drupal_not_found();
}
}
function quiz_admin_results() {
$result = db_fetch_object(db_query('SELECT qnp.nid FROM {quiz_node_properties} qnp, {quiz_node_results} qnrs WHERE qnrs.nid = qnp.nid AND qnrs.result_id = %d', arg(2)));
if ($result->nid) {
$quiz = node_load($result->nid);
$questions = _quiz_get_answers(arg(2));
$score = quiz_calculate_score($quiz, arg(2));
$summary = _quiz_get_summary_text($quiz, $score);
return theme('quiz_admin_summary', $quiz, $questions, $score, $summary);
}
else {
drupal_not_found();
}
}
function quiz_admin_result_delete() {
return drupal_get_form('quiz_admin_result_delete_form');
}
function quiz_admin_result_delete_form() {
$form['del_rid'] = array(
'#type' => 'hidden',
'#value' => arg(2),
);
return confirm_form($form, t('Are you sure you want to delete this @quiz result?', array(
'@quiz' => QUIZ_NAME,
)), 'admin/quiz/results', t('This action cannot be undone.'), t('Delete'), t('Cancel'));
}
function quiz_admin_result_delete_form_submit($form_id, $form_values) {
db_query("DELETE FROM {quiz_node_results} WHERE result_id = %d", $form_values['del_rid']);
db_query("DELETE FROM {quiz_node_results_answers} WHERE result_id = %d", $form_values['del_rid']);
drupal_set_message(t('Deleted result.'));
return "admin/quiz/results";
}
function _quiz_get_answers($rid) {
$questions = array();
$ids = db_query("SELECT question_nid, question_vid, type FROM {quiz_node_results_answers} " . "LEFT JOIN {node} ON (question_nid = nid AND question_vid = vid) WHERE result_id = %d ORDER BY answer_timestamp", $rid);
while ($line = db_fetch_object($ids)) {
$questions[$line->question_nid] = module_invoke($line->type, 'get_report', $line->question_nid, $line->question_vid, $rid);
}
return $questions;
}
function _quiz_get_quiz_name() {
return variable_get('quiz_name', 'Quiz');
}
function _quiz_availability($node) {
if (!$node->quiz_always) {
if ($node->quiz_open > time()) {
$status = 'future';
}
elseif ($node->quiz_open < time() && $node->quiz_close > time()) {
$status = 'open';
}
else {
$status = 'closed';
}
}
else {
$status = 'open';
}
return $status;
}
function _quiz_status_access($node) {
$access = FALSE;
$status = _quiz_availability($node);
switch ($status) {
case 'closed':
case 'future':
$access = user_access('administer quiz');
break;
case 'open':
$access = user_access('access quiz') && $node->status;
break;
}
return $access;
}
function theme_quiz_admin($results) {
$output = '';
$rows = array();
while (list($key, $result) = each($results)) {
$rows[] = array(
l('view', 'admin/quiz/' . $result['result_id'] . '/view') . ' | ' . l('delete', 'admin/quiz/' . $result['result_id'] . '/delete'),
check_plain($result['title']),
check_plain($result['name']),
$result['result_id'],
format_date($result['time_start'], 'small'),
$result['time_end'] > 0 ? format_date($result['time_end'], 'small') : t('In Progress'),
);
}
$header = array(
t('Action'),
t('@quiz Title', array(
'@quiz' => QUIZ_NAME,
)),
t('Username'),
t('Result<br />ID'),
t('Time Started'),
t('Finished?'),
);
if (!empty($rows)) {
$output .= theme('table', $header, $rows);
}
else {
$output .= t('No @quiz results found.', array(
'@quiz' => QUIZ_NAME,
));
}
return $output;
}
function theme_quiz_get_user_results($results) {
$output = '';
$rows = array();
while (list($key, $result) = each($results)) {
$rows[] = array(
l('view', 'user/quiz/' . $result['result_id'] . '/userresults'),
check_plain($result['title']),
check_plain($result['name']),
$result['result_id'],
format_date($result['time_start'], 'small'),
$result['time_end'] > 0 ? format_date($result['time_end'], 'small') : t('In Progress'),
);
}
$header = array(
t('Action'),
t('@quiz Title', array(
'@quiz' => QUIZ_NAME,
)),
t('Username'),
t('Result<br />ID'),
t('Time Started'),
t('Finished?'),
);
if (!empty($rows)) {
$output .= theme('table', $header, $rows);
}
else {
$output .= t('No @quiz results found.', array(
'@quiz' => QUIZ_NAME,
));
}
return $output;
}
function theme_quiz_filtered_questions($form) {
$quiz_id = is_numeric(arg(1)) ? arg(1) : NULL;
$header = array(
t('Random'),
t('Always'),
t('Never'),
t('Question'),
t('Type'),
t('Edit'),
);
$rows = array();
while (list($nid, $values) = each($form['question_status'])) {
if (is_numeric($nid)) {
$rows[] = array(
drupal_render($form['question_status'][$nid][QUIZ_FEEDBACK_END]),
drupal_render($form['question_status'][$nid][QUIZ_FEEDBACK_QUESTION]),
drupal_render($form['question_status'][$nid][QUIZ_FEEDBACK_NEVER]),
drupal_render($form['question'][$nid]),
drupal_render($form['type'][$nid]),
l(t('Edit'), 'node/' . $nid . '/edit/' . $quiz_id),
);
}
}
if (!empty($rows)) {
$output .= theme('table', $header, $rows);
}
else {
$output .= t('No questions found.');
}
return $output;
}
function theme_quiz_question_table($questions, $quiz_id = NULL) {
$output = '';
$rows = array();
$status_descriptions = array(
t('Random'),
t('Always'),
t('Never'),
);
while (list($key, $question) = each($questions)) {
$rows[] = array(
$status_descriptions[$question->status],
$question->question,
$question->type,
l(t('Edit'), 'node/' . $question->nid . '/edit/' . $quiz_id),
);
}
$header = array(
t('Status'),
t('Question'),
t('Type'),
t('Edit'),
);
if (!empty($rows)) {
$output .= theme('table', $header, $rows);
}
else {
$output .= t('No questions found.');
}
return $output;
}
function theme_quiz_score_correct() {
return theme('image', drupal_get_path('module', 'quiz') . '/images/correct.gif', t('correct'));
}
function theme_quiz_score_incorrect() {
return theme('image', drupal_get_path('module', 'quiz') . '/images/incorrect.gif', t('incorrect'));
}
function theme_quiz_progress($question_number, $num_of_question) {
$current_question = $question_number + 1;
$output = '';
$output .= '<div id="quiz_progress">';
$output .= t('Question %x of %y', array(
'%x' => $current_question,
'%y' => $num_of_question,
));
$output .= '</div><br />' . "\n";
return $output;
}
function theme_quiz_take_question($quiz, $question_node) {
$number_of_questions = quiz_get_number_of_questions($quiz->vid, $quiz->nid);
$question_number = $number_of_questions - count($_SESSION['quiz_' . $quiz->nid]['quiz_questions']);
$question_node->question_number = $question_number;
drupal_set_title(check_plain($quiz->title));
$output = '';
$output .= theme('quiz_progress', $question_number, $number_of_questions);
$output .= module_invoke($question_node->type, 'render_question', $question_node);
return $output;
}
function theme_quiz_take_summary($quiz, $questions, $score, $summary) {
drupal_set_title(check_plain($quiz->title));
$output = '';
if ($score['percentage_score']) {
$output .= '<div id="quiz_score_possible">' . t('You got %num_correct of %question_count correct.', array(
'%num_correct' => $score['num_correct'],
'%question_count' => $score['question_count'],
)) . '</div>' . "\n";
$output .= '<div id="quiz_score_percent">' . t('Your score: %score%', array(
'%score' => $score['percentage_score'],
)) . '</div><br />' . "\n";
}
$output .= '<div id="quiz_summary">' . $summary . '</div><br />' . "\n";
$output .= theme('quiz_feedback', $questions, $quiz->pass_rate > 0, TRUE);
return $output;
}
function theme_quiz_admin_summary($quiz, $questions, $score, $summary) {
drupal_set_title(check_plain($quiz->title));
$output = '';
$output .= '<div id="quiz_score_possible">' . t('This person got %num_correct of %question_count correct.', array(
'%num_correct' => $score['num_correct'],
'%question_count' => $score['question_count'],
)) . '</div>' . "\n";
$output .= '<div id="quiz_score_percent">' . t('Total score: @score%', array(
'@score' => $score['percentage_score'],
)) . '</div><br />' . "\n";
$output .= '<div id="quiz_summary">' . $summary . '</div><br />' . "\n";
$output .= theme('quiz_feedback', $questions, TRUE, TRUE);
return $output;
}
function theme_quiz_user_summary($quiz, $questions, $score, $summary) {
drupal_set_title(check_plain($quiz->title));
$output = '';
$output .= '<div id="quiz_score_possible">' . t('You got %num_correct of %question_count correct.', array(
'%num_correct' => $score['num_correct'],
'%question_count' => $score['question_count'],
)) . '</div>' . "\n";
$output .= '<div id="quiz_score_percent">' . t('Your score was: @score%', array(
'@score' => $score['percentage_score'],
)) . '</div><br />' . "\n";
$output .= '<div id="quiz_summary">' . $summary . '</div><br />' . "\n";
$output .= theme('quiz_feedback', $questions, FALSE, TRUE);
return $output;
}
function theme_quiz_feedback($questions, $showpoints = TRUE, $showfeedback = FALSE) {
$header = array(
t('Question Result(s)'),
'',
);
foreach ($questions as $question) {
$cols = array();
$cols[] = array(
'data' => theme($question->type . '_report', $question, $showpoints, $showfeedback),
'class' => 'quiz_summary_qrow',
);
if ($showpoints) {
$theme = $question->correct ? 'quiz_score_correct' : 'quiz_score_incorrect';
$cols[] = array(
'data' => theme($theme),
'class' => 'quiz_summary_qcell',
);
}
$rows[] = array(
'data' => $cols,
'class' => 'quiz_summary_qrow',
);
}
return theme('table', $header, $rows);
}
function theme_quiz_question_feedback($quiz, $report) {
$output = '<div class="quiz-summary-question">';
$output .= theme($report->type . '_feedback', $quiz, $report);
$output .= '</div><br class="clear" />';
return $output;
}
function theme_quiz_questions($form) {
$output = '';
$output .= drupal_render($form);
return $output;
}
function theme_quiz_no_feedback() {
return t('Thanks for taking the quiz!');
}