View source
<?php
define('QUIZ_QUESTION_NUM_LATEST', 10);
function quiz_question_help($path, $args) {
if ($path == 'admin/help#quiz_quesion') {
return t('Support for Quiz question types.');
}
}
function quiz_question_menu() {
$items = array();
$items['quiz_question/%/%/revision_actions'] = array(
'title' => 'Revision actions',
'page callback' => 'drupal_get_form',
'page arguments' => array(
'quiz_question_revision_actions',
1,
2,
),
'access arguments' => array(
'manual quiz revisioning',
),
'file' => 'quiz_question.pages.inc',
'type' => MENU_NORMAL_ITEM,
);
$items['admin/quiz/settings/questions_settings'] = array(
'title' => 'Question configuration',
'description' => 'Configure the question types.',
'page callback' => 'drupal_get_form',
'page arguments' => array(
'quiz_question_config',
),
'access arguments' => array(
'administer quiz configuration',
),
'type' => MENU_NORMAL_ITEM,
);
return $items;
}
function quiz_question_theme() {
return array(
'quiz_question_creation_form' => array(
'render element' => 'form',
'file' => 'quiz_question.theme.inc',
),
'quiz_question_navigation_form' => array(
'render element' => 'form',
'file' => 'quiz_question.theme.inc',
),
);
}
function quiz_question_node_info() {
$node_info = array();
foreach (_quiz_question_get_implementations(NULL, TRUE) as $type => $definition) {
if (!isset($definition['node']) || $definition['node']) {
$node_info[$type] = array(
'name' => $definition['name'],
'base' => 'quiz_question',
'description' => $definition['description'],
);
}
}
return $node_info;
}
function quiz_question_access_to_score($vid, $rid) {
global $user;
$sql = 'SELECT * FROM {quiz_node_results_answers} WHERE result_id = :result_id AND question_vid = :question_vid';
$answer = db_query($sql, array(
':result_id' => $rid,
':question_vid' => $vid,
))
->fetch();
if (!$answer) {
return FALSE;
}
if (user_access('score any quiz')) {
return TRUE;
}
if (user_access('score taken quiz answer')) {
$uid = db_query('SELECT uid from {quiz_node_results} qnr WHERE qnr.result_id = :result_id', array(
':result_id' => $rid,
))
->fetchField();
if ($uid == $user->uid) {
return TRUE;
}
}
if (user_access('score own quiz')) {
return db_query('SELECT r.uid FROM {node_revision} r
JOIN {quiz_node_results} qnr ON (r.nid = qnr.nid)
WHERE qnr.result_id = :result_id
', array(
':result_id' => $rid,
))
->fetchField() == $user->uid;
}
}
function quiz_question_form_node_form_alter(&$form, &$form_state) {
$type = $form['#node']->type;
if (array_key_exists($type, _quiz_question_get_implementations())) {
$question = _quiz_question_get_instance($form['#node']);
$quiz_question_form = $question
->getNodeForm($form_state);
$form = array_merge($quiz_question_form, $form);
}
}
function quiz_question_validate($node, &$form) {
if (empty($node->body)) {
form_set_error('body', t('Question text is empty.'));
}
_quiz_question_get_instance($node)
->validateNode($form);
}
function quiz_question_answering_form($form, $form_state, $node, $include_nid_in_id = FALSE) {
$question = _quiz_question_get_instance($node);
$form = $question
->getAnsweringForm($form_state, isset($node->rid) ? $node->rid : NULL);
$quiz = quiz_type_access_load(arg(1));
$form['#attributes']['class'] = array(
'answering-form',
);
$is_last = _quiz_get_num_questions_left() < 2;
$form['navigation']['#theme'] = 'quiz_question_navigation_form';
if ($quiz->mark_doubtful) {
$form['is_doubtful'] = array(
'#type' => 'checkbox',
'#title' => t('doubtful'),
'#weight' => 1,
'#prefix' => '<div class="mark-doubtful checkbox enabled"><div class="toggle"><div></div></div>',
'#suffix' => '</div>',
'#default_value' => 0,
'#attached' => array(
'js' => array(
drupal_get_path('module', 'quiz') . '/theme/quiz_take.js',
),
),
);
if (isset($node->rid)) {
$form['is_doubtful']['#default_value'] = db_query('SELECT is_doubtful FROM {quiz_node_results_answers} WHERE result_id = :result_id AND question_nid = :question_nid AND question_vid = :question_vid', array(
':result_id' => $node->rid,
':question_nid' => $node->nid,
':question_vid' => $node->vid,
))
->fetchField();
}
}
if (!empty($quiz->backwards_navigation) && !empty($node->question_number)) {
$form['navigation']['back'] = array(
'#type' => 'submit',
'#value' => t('Back'),
'#attributes' => array(
'class' => array(
'q-back-button',
),
),
);
if ($is_last) {
$form['navigation']['#last'] = TRUE;
}
}
$form['navigation']['submit'] = array(
'#type' => 'submit',
'#value' => $is_last ? t('Finish') : t('Next'),
);
if ($is_last && $quiz->backwards_navigation && !$quiz->repeat_until_correct) {
$form['#attributes']['class'][] = 'quiz-answer-confirm';
$form['#attributes']['data-confirm-message'] = t("By proceeding you won't be able to go back and edit your answers.");
$form['#attached'] = array(
'js' => array(
drupal_get_path('module', 'quiz') . '/theme/quiz_confirm.js',
),
);
}
if ($quiz->allow_skipping) {
$form['navigation']['op'] = array(
'#type' => 'submit',
'#value' => $is_last ? t('Leave blank and finish') : t('Leave blank'),
'#attributes' => array(
'class' => array(
'q-skip-button',
),
),
'#access' => $node->type == 'quiz_directions' ? FALSE : TRUE,
);
if ($quiz->allow_jumping) {
$form['jump_to_question'] = array(
'#type' => 'hidden',
'#default_value' => 0,
);
}
}
$form['navigation']['submit-hidden'] = $form['navigation']['submit'];
$form['navigation']['submit-hidden']['#weight'] = -20;
$form['navigation']['default_submit'] = $form['navigation']['submit'];
$form['navigation']['default_submit']['#type'] = 'hidden';
return $form;
}
function _quiz_question_teaser_content($node) {
$content['question_type'] = array(
'#markup' => '<div class="question_type_name">' . node_type_get_type($node)->name . '</div>',
'#weight' => -100,
);
return $content;
}
function quiz_question_evaluate_question($question, $result_id, $answer = NULL) {
if (empty($answer) && isset($_POST['tries'])) {
$answer = $_POST['tries'];
}
if (empty($answer) && isset($_FILES['files']['type']['tries']) && !empty($_FILES['files']['type']['tries'])) {
$answer = $_FILES;
}
unset($_POST['tries']);
$response = _quiz_question_response_get_instance($result_id, $question, $answer);
if ($result_id && isset($answer)) {
$response
->delete();
$response
->saveResult();
}
return $response
->toBareObject();
}
function quiz_question_skip_question($question, $result_id) {
unset($_POST['tries']);
_quiz_question_response_get_instance($result_id, $question)
->delete();
$response = new stdClass();
$response->nid = $question->nid;
$response->vid = $question->vid;
$response->rid = $result_id;
$response->is_skipped = TRUE;
if (isset($_POST['is_doubtful'])) {
$response->is_doubtful = $_POST['is_doubtful'];
}
else {
$response->is_doubtful = db_query('SELECT is_doubtful FROM {quiz_node_results_answers} WHERE result_id = :result_id AND question_nid = :question_nid AND question_vid = :question_vid', array(
':result_id' => $result_id,
':question_nid' => $question->nid,
':question_vid' => $question->vid,
))
->fetchField();
}
return $response;
}
function quiz_question_list_questions($count = 0, $offset = 0) {
$query = db_select('node', 'n')
->fields('n', array(
'nid',
'vid',
))
->innerjoin('node_revision', 'nr')
->condition('type', array_keys(_quiz_question_get_implementations()), 'IN')
->execute();
$questions = array();
$question_format = isset($question->body[LANGUAGE_NONE][0]['format']) ? $question->body[LANGUAGE_NONE][0]['format'] : NULL;
while ($question = $query
->fetch()) {
$question->question = check_markup($question->body, $question_format);
$questions[] = $question;
}
return $questions;
}
function quiz_question_get_report($nid, $vid, $rid) {
$response_instance = _quiz_question_response_get_instance($rid, NULL, NULL, $nid, $vid);
if (!$response_instance) {
drupal_set_message(t('Unable to load question with nid %nid and vid %vid', array(
'%nid' => $nid,
'%vid' => $vid,
)), 'error');
return FALSE;
}
$result = $response_instance
->getReport();
$response_instance->question->answers[$result['answer_id']] = $result;
$response_instance->question->correct = $result['is_correct'];
return $response_instance->question;
}
function quiz_question_has_been_answered($node) {
$question_instance = _quiz_question_get_instance($node, true);
return $question_instance
->hasBeenAnswered();
}
function quiz_question_quiz_question_score($quiz, $question_nid, $question_vid = NULL, $rid = NULL) {
if (!isset($quiz) && !isset($rid)) {
return quiz_question_get_max_score($question_nid, $question_vid);
}
$dummy_node = new stdClass();
$dummy_node->nid = $question_nid;
$dummy_node->vid = $question_vid;
$question = _quiz_question_get_instance($dummy_node, TRUE);
if (!$question) {
return FALSE;
}
$score = new stdClass();
$score->possible = $question
->getMaximumScore();
$score->question_nid = $question->node->nid;
$score->question_vid = $question->node->vid;
if (isset($rid)) {
$response = _quiz_question_response_get_instance($rid, $question->node);
$score->attained = $score->possible > 0 ? $response
->getScore() : 0;
$score->possible = $response
->getMaxScore();
$score->is_evaluated = $response
->isEvaluated();
}
return $score;
}
function quiz_question_delete_result($rid, $nid, $vid) {
$response = _quiz_question_response_get_instance($rid, NULL, NULL, $nid, $vid);
if ($response) {
$response
->delete();
}
else {
drupal_set_message(t('Unable to delete result. A constructor could not be found for the question-type'), 'error');
}
}
function quiz_question_config($form, $context) {
$q_types = _quiz_question_get_implementations();
$form = array();
$form['#validate'] = array();
foreach ($q_types as $type => $values) {
$function = $type . '_config';
if (function_exists($function) && ($admin_form = $function())) {
$form[$type] = $admin_form;
$form[$type]['#type'] = 'fieldset';
$form[$type]['#title'] = $values['name'];
$form[$type]['#collapsible'] = TRUE;
$form[$type]['#collapsed'] = TRUE;
if (isset($admin_form['#validate']) && is_array($admin_form['#validate'])) {
$form['#validate'] = array_merge($form['#validate'], $admin_form['#validate']);
unset($form[$type]['#validate']);
}
}
}
return system_settings_form($form);
}
function quiz_question_node_revision_delete($node) {
$q_types = _quiz_question_get_implementations();
foreach ($q_types as $q_type => $info) {
if ($node->type == $q_type) {
_quiz_delete_question($node, TRUE);
}
}
}
function quiz_question_node_presave($node) {
$q_types = _quiz_question_get_implementations();
foreach ($q_types as $q_type => $info) {
if ($node->type == $q_type) {
if (drupal_strlen($node->title) == 0 || !user_access('edit question titles')) {
$body = field_get_items('node', $node, 'body');
$markup = strip_tags(check_markup($body[0]['value'], $body[0]['format']));
if (drupal_strlen($markup) > variable_get('quiz_autotitle_length', 50)) {
$node->title = drupal_substr($markup, 0, variable_get('quiz_autotitle_length', 50) - 3) . '...';
}
else {
$node->title = $markup;
}
}
}
}
}
function quiz_question_node_insert($node) {
if ($node->type == 'quiz') {
quiz_question_refresh_latest_quizzes($node->nid);
}
if (array_key_exists($node->type, _quiz_question_get_implementations())) {
_quiz_question_get_instance($node)
->save(TRUE);
if (isset($node->quiz_nid) && $node->quiz_nid > 0) {
quiz_question_refresh_latest_quizzes($node->quiz_nid);
}
}
}
function quiz_question_node_update($node) {
if ($node->type == 'quiz') {
quiz_question_refresh_latest_quizzes($node->nid);
}
if (array_key_exists($node->type, _quiz_question_get_implementations())) {
_quiz_question_get_instance($node)
->save();
}
}
function quiz_question_node_delete($node) {
if ($node->type == 'quiz') {
quiz_question_remove_latest_quizzes($node->nid);
}
if (array_key_exists($node->type, _quiz_question_get_implementations())) {
_quiz_delete_question($node, FALSE);
}
}
function quiz_question_view($node, $view_mode) {
if ($view_mode == 'search_index' && !variable_get('quiz_index_questions', 1)) {
$node->body = '';
$node->content = array();
$node->title = '';
$node->taxonomy = array();
return $node;
}
$content = '';
if (!_quiz_is_taking_context()) {
switch ($view_mode) {
case 'teaser':
$node->content['question_teaser'] = _quiz_question_teaser_content($node);
break;
default:
$content = _quiz_question_get_instance($node, TRUE) ? _quiz_question_get_instance($node, TRUE)
->getNodeView() : array();
}
}
if (!empty($content)) {
$node->content = isset($node->content) ? $node->content + $content : $content;
}
return $node;
}
function quiz_question_node_view($node) {
if (_quiz_is_taking_context() && array_key_exists($node->type, _quiz_question_get_implementations())) {
if (empty($node->no_answer_form)) {
$form = drupal_get_form('quiz_question_answering_form', $node);
$form_markup = drupal_render($form);
if (!empty($form_markup)) {
$node->content['question_form'] = array(
'#markup' => $form_markup,
'#weight' => 30,
);
}
}
}
}
function _quiz_delete_question(&$node, $only_this_version) {
_quiz_question_get_instance($node, TRUE)
->delete($only_this_version);
$select_sql = 'SELECT parent_vid FROM {quiz_node_relationship}';
if ($only_this_version) {
$select_sql .= ' WHERE child_nid = :child_nid AND child_vid = :child_vid';
$filter_arg = array(
':child_nid' => $node->nid,
':child_vid' => $node->vid,
);
}
else {
$select_sql .= ' WHERE child_nid = :child_nid';
$filter_arg = array(
':child_nid' => $node->nid,
);
}
$res = db_query($select_sql, $filter_arg);
$update = db_update('quiz_node_relationship')
->fields(array(
'question_status' => QUESTION_NEVER,
))
->condition('child_nid', $node->nid);
if ($only_this_version) {
$update = $update
->condition('child_vid', $node->vid);
}
$update
->execute();
$quizzes_to_update = array();
while ($quizzes_to_update[] = $res
->fetchField()) {
}
quiz_update_max_score_properties($quizzes_to_update);
}
function quiz_question_load($nodes) {
foreach ($nodes as $nid => &$node) {
$node_additions = _quiz_question_get_instance($node, TRUE) ? _quiz_question_get_instance($node, TRUE)
->getNodeProperties() : array();
foreach ($node_additions as $property => &$value) {
$node->{$property} = $value;
}
}
}
function _quiz_question_get_instance(&$node, $use_cached = FALSE) {
static $question_instances = array();
$using_dummy_node = FALSE;
if (is_object($node)) {
$vid = isset($node->vid) ? $node->vid : 0;
if ($use_cached && isset($question_instances[$vid])) {
return $question_instances[$vid];
}
if (!isset($node->type)) {
$sql = 'SELECT n.type, r.nid, r.vid, r.title, n.uid, p.max_score
FROM {node_revision} r
JOIN {node} n ON r.nid = n.nid
JOIN {quiz_question_properties} p ON r.vid = p.vid
WHERE r.vid = :vid';
$node = db_query($sql, array(
':vid' => $node->vid,
))
->fetch();
$using_dummy_node = TRUE;
}
$name = $node->type;
}
elseif (is_array($node)) {
$name = $node['type'];
$vid = $node['vid'];
if ($use_cached && isset($question_instances[$vid])) {
return $question_instances[$vid];
}
}
$info = _quiz_question_get_implementations();
$constructor = $info[$name]['question provider'];
if (empty($constructor)) {
return FALSE;
}
$to_return = new $constructor($node);
if (!$to_return instanceof QuizQuestion) {
drupal_set_message(t('The question-type %name isn\'t a QuizQuestion. It needs to extend the QuizQuestion class.', array(
'%name' => $name,
)), 'error', FALSE);
}
if ($using_dummy_node) {
$props = $to_return
->getNodeProperties();
foreach ($props as $key => $value) {
$to_return->node->{$key} = $value;
}
}
$question_instances[$vid] = $to_return;
return $to_return;
}
function _quiz_question_response_get_instance($rid, $question, $answer = NULL, $nid = NULL, $vid = NULL) {
static $quiz_responses = array();
if (is_object($question) && isset($quiz_responses[$rid][$question->vid])) {
$quiz_responses[$rid][$question->vid]
->refreshQuestionNode($question);
if ($quiz_responses[$rid][$question->vid]->is_skipped !== FALSE) {
return $quiz_responses[$rid][$question->vid];
}
}
elseif (isset($quiz_responses[$rid][$vid])) {
if ($quiz_responses[$rid][$vid]->is_skipped !== FALSE) {
return $quiz_responses[$rid][$vid];
}
}
if (!isset($quiz_responses[$rid])) {
$quiz_responses[$rid] = array();
}
if (!isset($question)) {
$dummy_node = new stdClass();
$dummy_node->nid = $nid;
$dummy_node->vid = $vid;
$question = _quiz_question_get_instance($dummy_node, TRUE)->node;
}
if (!$question) {
return FALSE;
}
$info = _quiz_question_get_implementations();
$constructor = $info[$question->type]['response provider'];
$to_return = new $constructor($rid, $question, $answer);
if (!$to_return instanceof QuizQuestionResponse) {
drupal_set_message(t('The question-response isn\'t a QuizQuestionResponse. It needs to extend the QuizQuestionResponse interface, or extend the abstractQuizQuestionResponse class.'), 'error', FALSE);
}
$quiz_responses[$rid][$question->vid] = $to_return;
return $to_return;
}
function _quiz_question_get_implementations($name = NULL, $reset = FALSE) {
static $info = array();
if (empty($info) || $reset) {
$qtypes = module_invoke_all('quiz_question_info');
foreach ($qtypes as $type => $definition) {
if (!empty($definition['question provider'])) {
$info[$type] = $definition;
}
}
}
return $info;
}
function quiz_question_refresh_latest_quizzes($nid) {
global $user;
db_delete('quiz_question_latest_quizzes')
->condition('uid', $user->uid)
->condition('quiz_nid', $nid)
->execute();
$id = db_insert('quiz_question_latest_quizzes')
->fields(array(
'uid' => $user->uid,
'quiz_nid' => $nid,
))
->execute();
$min_id = db_select('quiz_question_latest_quizzes', 'lq')
->fields('lq', array(
'id',
))
->condition('uid', $user->uid)
->orderBy('id', 'DESC')
->range(QUIZ_QUESTION_NUM_LATEST - 1, 1)
->execute()
->fetchField();
if ($min_id) {
db_delete('quiz_question_latest_quizzes')
->condition('id', $min_id, '<')
->condition('uid', $user->uid)
->execute();
}
}
function quiz_question_remove_latest_quizzes($nid) {
db_delete('quiz_question_latest_quizzes')
->condition('quiz_nid', $nid)
->execute();
}
function quiz_question_get_max_score($nid, $vid) {
return db_query('SELECT max_score
FROM {quiz_question_properties}
WHERE nid = :nid AND vid = :vid', array(
':nid' => $nid,
':vid' => $vid,
))
->fetchField();
}
function quiz_question_field_extra_fields() {
$extra = array();
$question_types = array_keys(_quiz_question_get_implementations());
if (!empty($question_types)) {
foreach ($question_types as $type_name) {
$extra['node'][$type_name]['form']['add_directly'] = array(
'label' => t('Add directly'),
'description' => t('Fieldset for adding a question directly into quizzes'),
'weight' => -3,
);
}
}
return $extra;
}
function quiz_question_report_form($question, $showpoints, $showfeedback, $allow_scoring = FALSE) {
$answer = $question->answers[0];
$response_instance = _quiz_question_response_get_instance($answer['result_id'], $question, $answer);
if (!isset($response_instance->question->score_weight)) {
$vid = db_query('SELECT vid FROM {quiz_node_results}
WHERE result_id = :rid', array(
':rid' => $answer['result_id'],
))
->fetchField();
$qnr_max_score = db_query('SELECT qnr.max_score FROM {quiz_node_relationship} qnr
WHERE qnr.child_vid = :child_vid AND qnr.parent_vid = :parent_vid', array(
':child_vid' => $question->vid,
':parent_vid' => $vid,
))
->fetchField();
if ($qnr_max_score === FALSE) {
$qnr_max_score = db_query('SELECT qt.max_score FROM {quiz_node_results} qnr
JOIN {quiz_node_results_answers} qnra ON (qnr.result_id = qnra.result_id)
JOIN {quiz_terms} qt ON (qt.vid = qnr.vid AND qt.tid = qnra.tid)
WHERE qnr.result_id = :rid AND qnra.question_nid = :qnid AND qnra.question_vid = :qvid', array(
':rid' => $answer['result_id'],
':qnid' => $question->nid,
':qvid' => $question->vid,
))
->fetchField();
}
$response_instance->question->score_weight = $qnr_max_score == 0 || $response_instance->question->max_score == 0 ? 0 : $qnr_max_score / $response_instance->question->max_score;
}
return $response_instance
->getReportForm($showpoints, $showfeedback, $allow_scoring);
}
function quiz_question_add_body_field($type) {
node_types_rebuild();
$node_type = node_type_get_type($type);
if (!$node_type) {
watchdog('quiz', 'Attempt to add body field was failed as question content type %type is not defined.', array(
'%type' => $type,
), WATCHDOG_ERROR);
watchdog('quiz', '<pre>' . print_r(node_type_get_types(), 1), array(), WATCHDOG_ERROR);
return;
}
node_add_body_field($node_type, 'Question');
$instance = field_read_instance('node', 'body', $type);
$instance['widget']['weight'] = -10;
$instance['widget']['settings']['rows'] = 6;
$instance['display']['question'] = array(
'label' => 'hidden',
'type' => 'text_default',
'weight' => 1,
'settings' => array(),
'module' => 'text',
);
field_update_instance($instance);
}
function quiz_question_node_access_records($node) {
$grants = array();
$question_types = _quiz_question_get_implementations();
$question_types = array_keys($question_types);
if (in_array($node->type, $question_types)) {
$grants[] = array(
'realm' => 'quiz_question',
'gid' => 1,
'grant_view' => 1,
'grant_update' => 0,
'grant_delete' => 0,
'priority' => 2,
);
}
return $grants;
}
function quiz_question_node_grants($account, $op) {
$grants = array();
if ($op == 'view') {
if (user_access('view quiz question outside of a quiz')) {
$grants['quiz_question'][] = 1;
}
}
return $grants;
}