quiz_question.module in Quiz 6.6
Same filename and directory in other branches
- 8.6 question_types/quiz_question/quiz_question.module
- 8.4 question_types/quiz_question/quiz_question.module
- 8.5 question_types/quiz_question/quiz_question.module
- 6.3 question_types/quiz_question/quiz_question.module
- 6.4 question_types/quiz_question/quiz_question.module
- 6.5 question_types/quiz_question/quiz_question.module
- 7.6 question_types/quiz_question/quiz_question.module
- 7 question_types/quiz_question/quiz_question.module
- 7.4 question_types/quiz_question/quiz_question.module
- 7.5 question_types/quiz_question/quiz_question.module
Quiz Question module. This module provides the basic facilities for adding quiz question types to a quiz. While you can create standard Quiz question types simply by implementing the appropriate hooks, this module provides a framework that makes adding new types much easier.
File
question_types/quiz_question/quiz_question.moduleView source
<?php
/**
* Quiz Question module.
* This module provides the basic facilities for adding quiz question types to a quiz.
* While you can create standard Quiz question types simply by implementing the appropriate
* hooks, this module provides a framework that makes adding new types much easier.
* @file
*/
/**
* Implementation of hook_help().
*/
function quiz_question_help($path, $args) {
if ($path == 'admin/help#quiz_quesion') {
return t('Support for Quiz question types.');
}
}
/**
* Implementation of hook_autoload_info().
*/
function quiz_question_autoload_info() {
return array(
// Base interfaces and classes:
'QuizQuestion' => array(
'file' => 'quiz_question.core.inc',
),
'QuizQuestionResponse' => array(
'file' => 'quiz_question.core.inc',
),
'AbstractQuizQuestionResponse' => array(
'file' => 'quiz_question.core.inc',
),
);
}
/**
* Implementation of hook_menu().
*/
function quiz_question_menu() {
// Menu item for seeing references to a question.
$items['node/%quiz_question_type_access/references'] = array(
'title' => t('References'),
'page callback' => 'quiz_question_references',
'page arguments' => array(
1,
),
// FIXME give this a sensible callback
'access callback' => TRUE,
'type' => MENU_LOCAL_TASK,
'file' => 'quiz_question.pages.inc',
);
// Menu items for admin view of each question type.
$types = _quiz_question_get_implementations();
foreach ($types as $type => $definition) {
$items['admin/quiz/' . str_replace('_', '-', $type)] = array(
'title' => t('@name administration', array(
'@name' => $definition['name'],
)),
'description' => t('Configure the @name question type.', array(
'@name' => $definition['name'],
)),
'page callback' => 'drupal_get_form',
'page arguments' => array(
'quiz_question_type_settings_form',
$type,
),
'access arguments' => array(
'configure quiz question types',
),
'type' => MENU_NORMAL_ITEM,
);
}
return $items;
}
/**
* Implementation of hook_perm().
*/
function quiz_question_perm() {
return array(
// Manage questions:
'create quiz question',
'edit own quiz question',
'edit any quiz question',
'delete own quiz question',
'delete any quiz question',
'score quiz question',
'configure quiz question types',
);
}
/**
* Implementation of hook_theme().
*/
function quiz_question_theme() {
return array(
'quiz_question_report' => array(
'arguments' => array(
'show_points' => NULL,
'show_feedback' => NULL,
),
'file' => 'quiz_question.theme.inc',
),
'quiz_question_creation_form' => array(
'arguments' => array(
'form' => NULL,
),
'file' => 'quiz_question.theme.inc',
),
);
}
/**
* Implementation of hook_node_info().
*/
function quiz_question_node_info() {
$types = _quiz_question_get_implementations();
$info = array();
$defaults = array(
'module' => 'quiz_question',
//'help' => t('Add the question text and set a score for this question.'),
'has_body' => TRUE,
'has_title' => TRUE,
'body_label' => t('Question'),
);
foreach ($types as $type => $definition) {
$node_info = array(
'help' => t('Create a new @name.', array(
'@name' => $definition['name'],
)),
'name' => $definition['name'],
'description' => $definition['description'],
);
$info[$type] = $node_info + $defaults;
}
return $info;
}
/**
* Implementation of hook_access().
*/
function quiz_question_access($op, $node, $account) {
// Allow admin to do whatever.
if (user_access('administer quiz', $account)) {
return TRUE;
}
switch ($op) {
case 'view':
return user_access('view quiz question outside of a quiz');
case 'create':
return user_access('create quiz question', $account);
case 'update':
if (user_access('edit any quiz question', $account) || user_access('edit own quiz question', $account) && $account->uid == $node->uid) {
return TRUE;
}
case 'delete':
if (user_access('delete any quiz question', $account) || user_access('delete own quiz question', $account) && $account->uid == $node->uid) {
return TRUE;
}
}
}
/**
* Implementation of hook_form().
*/
function quiz_question_form(&$node, $form_state) {
$form = array();
$form['body'] = array(
'#type' => 'textarea',
'#title' => t('Question'),
'#description' => t('Enter the full text of the question that will be shown to the user. Include any special instructions on how to answer.'),
'#default_value' => $node->body,
'#required' => TRUE,
'#weight' => -15,
);
// process up the filter format
$form['format'] = filter_form($node->format);
// First we do the basic title and text fields.
// XXX: Should there be a way for question types to modify these (other than form alter hooks)?
// Allow user to set title?
if (user_access('allow user titles')) {
$form['helper']['#theme'] = 'quiz_question_creation_form';
$form['title'] = array(
'#type' => 'textfield',
'#title' => t('Title'),
'#default_value' => $node->title,
'#required' => FALSE,
'#description' => t('Add a title that will help distinguish this question from other questions. This will not be seen during the quiz.'),
);
}
else {
$form['title'] = array(
'#type' => 'value',
'#value' => $node->title,
);
}
// Collection ID used here to tie creation of a question to a specific quiz or qcollection
$collection_id = arg(3);
if (!empty($collection_id)) {
$collection = node_load((int) $collection_id);
$form['collection_nid'] = array(
'#type' => 'value',
'#value' => $collection->nid,
);
$form['collection_vid'] = array(
'#type' => 'value',
'#value' => $collection->vid,
);
// If coming from collection view, go back there on submit.
// TODO ask the node itself where to go after authoring a question
switch ($collection->type) {
case 'quiz':
$form['#redirect'] = 'node/' . $collection->nid . '/questions';
break;
case 'qcollection':
$form['#redirect'] = 'node/' . $collection->nid . '/items';
break;
}
}
$question = _quiz_question_get_instance($node);
$form = array_merge($form, $question
->getCreationForm($form_state));
return $form;
}
/**
* Implementation of hook_validate().
*/
function quiz_question_validate($node, &$form) {
// Check to make sure that there is a question.
if (empty($node->body)) {
form_set_error('body', t('Question text is empty.'));
}
_quiz_question_get_instance($node)
->validate($node, $form);
}
/**
* Get the form to show to the user.
*/
function quiz_question_question_form($context, $node) {
$form = _quiz_question_get_instance($node)
->getQuestionForm($node, $context);
if (_quiz_is_taking_context()) {
$quiz = menu_get_object();
if (!empty($quiz->backwards_navigation) && !empty($node->question_number)) {
$form['back'] = array(
'#type' => 'submit',
'#value' => t('Back'),
);
}
// Add navigation at the bottom:
// Submit button
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Next'),
);
/* TODO is skipping supporting?
if (empty($node->no_skip_button)) {
$form['op'] = array(
'#type' => 'submit',
'#value' => t('Skip'),
);
}
*/
}
else {
// reconstruct form so type is first (at top)
// TODO is there a better way? like unshift an assoc tuple?
$type = node_get_types('type', $node);
$newform['question_type'] = array(
'#type' => 'markup',
'#value' => '<div class="question_type_name">' . $type->name . '</div>',
);
$newform += $form;
$form = $newform;
if (!user_access('view quiz question solutions')) {
$form['tries'] = array(
'#type' => 'markup',
'#value' => '<br /> <em>Answers hidden</em>.',
);
}
}
return $form;
}
/**
* Form for teaser display
*/
function quiz_question_teaser_form($context, $node) {
$form['question'] = array(
'#type' => 'markup',
'#value' => check_markup($node->body, $node->format, FALSE),
);
// reconstruct form so type is first (at top)
// TODO is there a better way? like unshift an assoc tuple?
$type = node_get_types('type', $node);
$newform['question_type'] = array(
'#type' => 'markup',
'#value' => '<div class="question_type_name">' . $type->name . '</div>',
);
$newform += $form;
$form = $newform;
return $form;
}
/**
* Implementation of Quiz's hook_evaluate_question().
*
* @return
* Object with nid, vid, rid, score, is_correct flags set.
*/
function quiz_question_evaluate_question($question, $rid, $answer = NULL) {
//print_r($_POST['tries']);exit;
if (empty($answer)) {
$answer = $_POST['tries'];
//$answer = is_array($_POST['tries']) ? $_POST['tries'] : array($_POST['tries']);
}
unset($_POST['tries']);
$response = _quiz_question_response_get_instance($rid, $question, $answer);
// If a rid is set, we are taking a quiz.
if ($rid && isset($answer)) {
// We don't know whether or not the user has gone back a question. However,
// we do know that deleting a question for this result set should be safe in
// the case where the user has not gone back (since there will be no entries
// to delete). So we always run the delete.
$response
->delete();
$response
->save();
}
// Convert the response to a bare object.
return $response
->toBareObject();
}
/**
* Implementation of quiz hook_skip_question().
*/
function quiz_question_skip_question($question, $rid) {
unset($_POST['tries']);
// Unset any answer that might have been set.
// Delete any old answers for this question (for backwards nav).
_quiz_question_response_get_instance($rid, $question)
->delete();
// This is the standard response:
$response = new stdClass();
$response->nid = $question->nid;
$response->vid = $question->vid;
$response->rid = $rid;
$response->is_skipped = TRUE;
return $response;
}
/**
* Implementation of hook_list_questions().
*/
function quiz_question_list_questions($count = 0, $offset = 0) {
$sql = "SELECT n.nid, n.vid, r.body, r.format\n FROM {node} AS n\n INNER JOIN {node_revisions} AS r USING(vid)\n WHERE n.type IN (%s) ORDER BY n.type, n.changed";
$types = array();
foreach (array_keys(_quiz_question_get_implementations()) as $key) {
$types[] = "'" . $key . "'";
}
$type = implode(',', $types);
if ($count == 0) {
// Return all results
$result = db_query($sql, $type);
}
else {
// return only $count results
$result = db_query_range($sql, $type, $offset, $count);
}
$questions = array();
while ($question = db_fetch_object($result)) {
$question->question = check_markup($question->body, $question->format);
$questions[] = $question;
}
return $questions;
}
/**
* Implementation of hook_render_question().
*
* @param $node
* The question node.
*/
function quiz_question_render_question($node) {
return drupal_get_form('quiz_question_question_form', $node);
}
/**
* Imlementation of hook_get_report().
*
* @return
* Node containing all of the items from the question plus the user's answer.
*/
function quiz_question_get_report($nid, $vid, $rid) {
$node = node_load($nid, $vid);
$result = _quiz_question_response_get_instance($rid, $node)
->getReport();
$node->answers[$result['answer_id']] = $result;
// If this has been evaluated, we mark it as correct.
// FIXME: This needs to be improved substantially.
//if ($result && $result['is_evaluated'] && $result['score'] > 0) {
// $node->correct = TRUE;
//}
$node->correct = $result['is_correct'];
return $node;
}
/**
* Implementation of hook_quiz_question_score().
*/
function quiz_question_quiz_question_score($quiz, $question_nid, $question_vid, $rid) {
$node = node_load($question_nid, $question_vid);
$response = _quiz_question_response_get_instance($rid, $node);
$question = _quiz_question_get_instance($node);
$score = new stdClass();
$score->possible = $question
->getMaximumScore();
$score->attained = $response
->getScore();
$score->is_evaluated = $response
->isEvaluated();
return $score;
}
/**
* Get the admin settings form for a question type.
*/
function quiz_question_type_settings_form($context, $type) {
$node = new stdClass();
$node->type = $type;
return _quiz_question_get_instance($node)
->getAdminForm();
}
// NODE API
/**
* Implementation of hook_nodeapi().
*/
function quiz_question_nodeapi(&$node, $op) {
if ($op == 'delete revision') {
_quiz_question_get_instance($node)
->delete();
}
elseif ($op == 'presave') {
$q_types = _quiz_question_get_implementations();
foreach ($q_types as $q_type => $info) {
if ($node->type == $q_type) {
if (drupal_strlen($node->title == 0)) {
$node->title = drupal_substr(strip_tags($node->body), 0, variable_get('quiz_autotitle_length', 50));
}
}
}
}
}
/**
* Implementation of hook_insert()
*/
function quiz_question_insert($node) {
_quiz_question_get_instance($node)
->save(TRUE);
// If the form says this should be attached to a quiz, attach it.
if (isset($node->quiz_id) && $node->quiz_id > 0) {
$sql = 'INSERT INTO {quiz_node_relationship} (parent_nid, parent_vid, child_nid, child_vid, question_status)
VALUES (%d, %d, %d, %d, %d)';
db_query($sql, $node->quiz_id, $node->quiz_vid, $node->nid, $node->vid, QUESTION_ALWAYS);
}
}
/**
* Implementation of hook_view()
*/
function quiz_question_view($node, $teaser = FALSE, $page = FALSE) {
if ($teaser) {
$form_markup = drupal_get_form('quiz_question_teaser_form', $node);
}
else {
$form_markup = drupal_get_form('quiz_question_question_form', $node);
}
$node->content['body']['#value'] = $form_markup;
return $node;
}
/**
* Implementation of hook_update().
*/
function quiz_question_update($node) {
_quiz_question_get_instance($node)
->save();
}
/**
* Implementation of hook_delete().
*/
function quiz_question_delete(&$node) {
_quiz_question_get_instance($node)
->delete();
}
/**
* Implementation of hook_load().
*/
function quiz_question_load($node) {
return _quiz_question_get_instance($node)
->load();
}
// END NODE API
/**
* Get an instance of a quiz question.
* Get information about the class and use it to construct a new
* object of the appropriate type.
*/
function _quiz_question_get_instance($node) {
if (is_object($node)) {
$name = $node->type;
}
elseif (is_array($node)) {
$name = $node['type'];
}
else {
$name = $node;
}
$info = _quiz_question_get_implementations();
$constructor = $info[$name]['question provider'];
return new $constructor($node);
}
function _quiz_question_response_get_instance($rid, $question, $answer = NULL) {
$info = _quiz_question_get_implementations();
$constructor = $info[$question->type]['response provider'];
return new $constructor($rid, $question, $answer);
}
/**
* Get the information about various implementations of quiz questions.
*
* @param $reset
* If this is true, the cache will be reset.
* @return
* An array of information about quiz question implementations.
* @see quiz_question_quiz_question_info() for an example of a quiz question info hook.
*/
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) {
// We only want the ones with classes.
if (!empty($definition['question provider'])) {
$info[$type] = $definition;
}
}
}
return $info;
}
/**
* Load a quiz question node and validate it.
*
* @param $arg
* The Node ID
* @return
* A quiz question node object or FALSE if a load failed.
*/
function quiz_question_type_access_load($arg) {
$node = node_load($arg);
if (!$node) {
return FALSE;
}
$qtypes = module_invoke_all('quiz_question_info');
$qtypes_key = array_keys($qtypes);
if (!in_array($node->type, $qtypes_key)) {
return FALSE;
}
return $node;
}
Functions
Name | Description |
---|---|
quiz_question_access | Implementation of hook_access(). |
quiz_question_autoload_info | Implementation of hook_autoload_info(). |
quiz_question_delete | Implementation of hook_delete(). |
quiz_question_evaluate_question | Implementation of Quiz's hook_evaluate_question(). |
quiz_question_form | Implementation of hook_form(). |
quiz_question_get_report | Imlementation of hook_get_report(). |
quiz_question_help | Implementation of hook_help(). |
quiz_question_insert | Implementation of hook_insert() |
quiz_question_list_questions | Implementation of hook_list_questions(). |
quiz_question_load | Implementation of hook_load(). |
quiz_question_menu | Implementation of hook_menu(). |
quiz_question_nodeapi | Implementation of hook_nodeapi(). |
quiz_question_node_info | Implementation of hook_node_info(). |
quiz_question_perm | Implementation of hook_perm(). |
quiz_question_question_form | Get the form to show to the user. |
quiz_question_quiz_question_score | Implementation of hook_quiz_question_score(). |
quiz_question_render_question | Implementation of hook_render_question(). |
quiz_question_skip_question | Implementation of quiz hook_skip_question(). |
quiz_question_teaser_form | Form for teaser display |
quiz_question_theme | Implementation of hook_theme(). |
quiz_question_type_access_load | Load a quiz question node and validate it. |
quiz_question_type_settings_form | Get the admin settings form for a question type. |
quiz_question_update | Implementation of hook_update(). |
quiz_question_validate | Implementation of hook_validate(). |
quiz_question_view | Implementation of hook_view() |
_quiz_question_get_implementations | Get the information about various implementations of quiz questions. |
_quiz_question_get_instance | Get an instance of a quiz question. Get information about the class and use it to construct a new object of the appropriate type. |
_quiz_question_response_get_instance |