quiz.admin.inc in Quiz 7.5
Administrator interface for Quiz module.
File
quiz.admin.incView source
<?php
/**
* @file
* Administrator interface for Quiz module.
*/
// QUIZ ADMIN.
// Quiz Admin Settings.
/**
* This builds the main settings form for the quiz module.
*/
function quiz_admin_settings($form, &$form_state) {
$form = array();
$form['quiz_global_settings'] = array(
'#type' => 'fieldset',
'#title' => t('Global configuration'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
'#description' => t("Control aspects of the Quiz module's display"),
);
$form['quiz_global_settings']['quiz_auto_revisioning'] = array(
'#type' => 'checkbox',
'#title' => t('Auto revisioning'),
'#default_value' => variable_get('quiz_auto_revisioning', 1),
'#description' => t('It is strongly recommended that auto revisioning is always on. It makes sure that when a question or quiz is changed a new revision is created if the current revision has been answered. If this feature is switched off result reports might be broken because a users saved answer might be connected to a wrong version of the quiz and/or question she was answering. All sorts of errors might appear.'),
);
$form['quiz_global_settings']['quiz_durod'] = array(
'#type' => 'checkbox',
'#title' => t('Delete results when a user is deleted'),
'#default_value' => variable_get('quiz_durod', 0),
'#description' => t('When a user is deleted delete any and all results for that user.'),
);
$form['quiz_global_settings']['quiz_index_questions'] = array(
'#type' => 'checkbox',
'#title' => t('Index questions'),
'#default_value' => variable_get('quiz_index_questions', 1),
'#description' => t('If you turn this off, questions will not show up in search results. Note that you will need to enable the "View quiz question outside of a quiz" permission for anonymous users, as search needs this to index the question.'),
);
$form['quiz_global_settings']['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),
'#size' => 4,
'#maxlength' => 4,
'#description' => t('Supply a number of days to calculate the default close date for new quizzes.'),
);
$form['quiz_global_settings']['quiz_use_passfail'] = array(
'#type' => 'checkbox',
'#title' => t('Allow quiz creators to set a pass/fail option when creating a @quiz.', array(
'@quiz' => strtolower(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, uncheck this option.', array(
'@quiz' => QUIZ_NAME,
)),
);
$form['quiz_global_settings']['quiz_max_result_options'] = array(
'#type' => 'textfield',
'#title' => t('Maximum result options'),
'#description' => t('Set the maximum number of result options (categorizations for scoring a quiz). Set to 0 to disable result options.'),
'#default_value' => variable_get('quiz_max_result_options', 5),
'#size' => 2,
'#maxlength' => 2,
'#required' => FALSE,
);
$form['quiz_global_settings']['quiz_remove_partial_quiz_record'] = array(
'#title' => t('Remove incomplete quiz records (older than)'),
'#description' => t('Number of days to keep incomplete quiz attempts.'),
'#default_value' => variable_get('quiz_remove_partial_quiz_record', 0),
'#type' => module_exists('timeperiod') ? 'timeperiod_select' : 'textfield',
'#units' => array(
'86400' => array(
'max' => 30,
'step size' => 1,
),
'3600' => array(
'max' => 24,
'step size' => 1,
),
'60' => array(
'max' => 60,
'step size' => 1,
),
),
);
$form['quiz_global_settings']['quiz_remove_invalid_quiz_record'] = array(
'#title' => t('Remove invalid quiz records (older than)'),
'#description' => t('Number of days to keep invalid quiz attempts.'),
'#default_value' => variable_get('quiz_remove_invalid_quiz_record', 86400),
'#type' => module_exists('timeperiod') ? 'timeperiod_select' : 'textfield',
'#units' => array(
'86400' => array(
'max' => 30,
'step size' => 1,
),
'3600' => array(
'max' => 24,
'step size' => 1,
),
'60' => array(
'max' => 60,
'step size' => 1,
),
),
);
$form['quiz_global_settings']['quiz_autotitle_length'] = array(
'#type' => 'textfield',
'#title' => t('Length of automatically set question titles'),
'#size' => 3,
'#maxlength' => 3,
'#description' => t("Integer between 0 and 128. If the question creator doesn't set a question title the system will make a title automatically. Here you can decide how long the autotitle can be."),
'#default_value' => variable_get('quiz_autotitle_length', 50),
);
$form['quiz_global_settings']['quiz_pager_start'] = array(
'#type' => 'textfield',
'#title' => t('Pager start'),
'#size' => 3,
'#maxlength' => 3,
'#description' => t('If a quiz has this many questions, a pager will be displayed instead of a select box.'),
'#default_value' => variable_get('quiz_pager_start', 100),
);
$form['quiz_global_settings']['quiz_pager_siblings'] = array(
'#type' => 'textfield',
'#title' => t('Pager siblings'),
'#size' => 3,
'#maxlength' => 3,
'#description' => t('Number of siblings to show.'),
'#default_value' => variable_get('quiz_pager_siblings', 5),
);
$form['quiz_global_settings']['quiz_time_limit_buffer'] = array(
'#type' => 'textfield',
'#title' => t('Time limit buffer'),
'#size' => 3,
'#maxlength' => 3,
'#description' => t('How many seconds after the time limit runs out to allow answers.'),
'#default_value' => variable_get('quiz_time_limit_buffer', 5),
'#element_validate' => array(
'element_validate_integer',
),
);
// Review options.
$review_options = quiz_get_feedback_options();
$form['quiz_global_settings']['quiz_admin_review_options']['#title'] = t('Administrator review options');
$form['quiz_global_settings']['quiz_admin_review_options']['#type'] = 'fieldset';
$form['quiz_global_settings']['quiz_admin_review_options']['#description'] = t('Control what feedback types quiz administrators will see when viewing results for other users.');
foreach (quiz_get_feedback_times() as $key => $when) {
$form['quiz_global_settings']['quiz_admin_review_options']["quiz_admin_review_options_{$key}"] = array(
'#title' => $when['name'],
'#type' => 'checkboxes',
'#options' => $review_options,
'#default_value' => variable_get("quiz_admin_review_options_{$key}", array()),
);
}
$target = array(
'attributes' => array(
'target' => '_blank',
),
);
$links = array(
'!jquery_countdown' => l(t('JQuery Countdown'), 'http://drupal.org/project/jquery_countdown', $target),
);
$form['quiz_addons'] = array(
'#type' => 'fieldset',
'#title' => t('Addons configuration'),
'#description' => t('Quiz has built in integration with some other modules. Disabled checkboxes indicates that modules are not enabled.', $links),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
);
$form['quiz_addons']['quiz_has_timer'] = array(
'#type' => 'checkbox',
'#title' => t('Display timer'),
'#default_value' => variable_get('quiz_has_timer', 0),
'#description' => t("!jquery_countdown is an <strong>optional</strong> module for Quiz. It is used to display a timer when taking a quiz. Without this timer, the user will not know how much time they have left to complete the Quiz", $links),
'#disabled' => !function_exists('jquery_countdown_add'),
);
$form['quiz_look_feel'] = array(
'#type' => 'fieldset',
'#title' => t('Look and feel'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
'#description' => t("Control aspects of the Quiz module's display"),
);
$form['quiz_look_feel']['quiz_name'] = array(
'#type' => 'textfield',
'#title' => t('Display name'),
'#default_value' => QUIZ_NAME,
'#description' => t('Change the name of the quiz type. Do you call it <em>test</em> or <em>assessment</em> instead? Change the display name of the module to something else. By default, it is called <em>Quiz</em>.'),
'#required' => TRUE,
);
$form['#validate'][] = 'quiz_settings_form_validate';
$form['#submit'][] = 'quiz_settings_form_submit';
return system_settings_form($form);
}
/**
* Validation of the Form Settings form.
*
* Checks the values for the form administration form for quiz settings.
*/
function quiz_settings_form_validate($form, &$form_state) {
if (!_quiz_is_int($form_state['values']['quiz_default_close'])) {
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 (!_quiz_is_int($form_state['values']['quiz_autotitle_length'], 0, 128)) {
form_set_error('quiz_autotitle_length', t('The autotitle length value must be an integer between 0 and 128.'));
}
if (!_quiz_is_int($form_state['values']['quiz_max_result_options'], 0, 100)) {
form_set_error('quiz_max_result_options', t('The number of resultoptions must be an integer between 0 and 100.'));
}
if (!_quiz_is_plain($form_state['values']['quiz_name'])) {
form_set_error('quiz_name', t('The quiz name must be plain text.'));
}
/* if (!_quiz_is_plain($form_state['values']['quiz_action_type']))
form_set_error('quiz_action_type', t('The action type must be plain text.')); */
}
/**
* Submit the admin settings form.
*/
function quiz_settings_form_submit($form, &$form_state) {
if (QUIZ_NAME != $form_state['values']['quiz_name']) {
variable_set('quiz_name', $form_state['values']['quiz_name']);
define(QUIZ_NAME, $form_state['values']['quiz_name']);
menu_rebuild();
}
}
/**
* Renders the quiz node form for the admin pages.
*
* This form is used to configure default values for the quiz node form.
*/
function quiz_admin_node_form($form, &$form_state) {
// Create a dummy node to use as input for quiz_form.
$dummy_node = new stdClass();
// Def_uid is the uid of the default user holding the default values for the
// node form(no real user with this uid exists).
$settings = quiz_get_defaults();
foreach ($settings as $key => $value) {
if (!isset($dummy_node->{$key})) {
$dummy_node->{$key} = $value;
}
}
$form = quiz_form($dummy_node, $form_state);
$form['direction'] = array(
'#markup' => t('Here you can change the default @quiz settings for new users.', array(
'@quiz' => QUIZ_NAME,
)),
'#weight' => -10,
);
// Unset values we can't or won't let the user edit default values for.
unset($form['#quiz_check_revision_access'], $form['title'], $form['body_field'], $form['taking']['addons'], $form['quiz_availability']['quiz_open'], $form['quiz_availability']['quiz_close'], $form['resultoptions'], $form['number_of_random_questions'], $form['remember_global']);
$form['remember_settings']['#type'] = 'value';
$form['remember_settings']['#default_value'] = TRUE;
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Save'),
'#submit' => array(
'quiz_admin_node_form_submit',
),
);
return $form;
}
/**
* Validation function for the quiz_admin_node_form form.
*/
function quiz_admin_node_form_validate($form, &$form_state) {
// Create dummy node for quiz_validate.
$dummy_node = new stdClass();
foreach ($form_state['values'] as $key => $value) {
$dummy_node->{$key} = $value;
}
$dummy_node->resultoptions = array();
// We use quiz_validate to validate the default values.
quiz_validate($dummy_node);
}
/**
* Submit function for quiz_admin_node_form.
*
* The default values are saved as the user settings for the "default user". The
* default user is created when quiz is installed. He has a unique uid, but
* doesn't exist as a real user.
*
* Why?
* Default user settings can be loaded and saved using the same code and
* database tables as any other user settings, making the code a lot easier to
* maintain.
* Ref: http://en.wikipedia.org/wiki/Don%27t_repeat_yourself
*/
function quiz_admin_node_form_submit($form, &$form_state) {
// We add the uid for the "default user"
// Generate the node object:
$node = (object) $form_state['values'];
$node->uid = 0;
$node->nid = 0;
$node->vid = 0;
quiz_update_defaults($node);
$form_state['node'] = $node;
}
// QUIZ RESULTS ADMIN.
/**
* Quiz result report page for the quiz admin section.
*
* @param $quiz
* The quiz node.
* @param $result_id
* The result id.
*/
function quiz_admin_results($quiz, $quiz_result) {
// Preserve "Results" tab.
$item = menu_get_item("node/{$quiz->nid}/quiz");
menu_set_item(NULL, $item);
return entity_ui_get_form('quiz_result', $quiz_result);
}
// MANAGE QUESTIONS.
/**
* Creates a form for quiz questions.
*
* Handles the manage questions tab.
*
* @param $node
* The quiz node we are managing questions for.
* @return
* String containing the form.
*/
function quiz_questions_page($node) {
if ($node->randomization < 3) {
$mq_form = drupal_get_form('quiz_questions_form', $node);
$manage_questions = drupal_render($mq_form);
$question_bank = views_get_view('quiz_question_bank')
->preview();
// Insert into vert tabs.
$form['vert_tabs'] = array(
'#type' => 'vertical_tabs',
'#weight' => 0,
);
$form['vert_tabs']['question_admin'] = array(
'#type' => 'fieldset',
'#title' => t('Manage questions'),
'#value' => $manage_questions,
);
$form['vert_tabs']['global_questions'] = array(
'#type' => 'fieldset',
'#title' => t('Question bank'),
'#value' => $question_bank,
);
}
else {
$form = drupal_get_form('quiz_categorized_form', $node);
}
$variables = array(
'node' => $node,
'form' => $form,
);
return theme('quiz_questions_page', $variables);
}
/**
* Form for managing what questions should be added to a quiz with categorized
* random questions.
*
* @param array $form_state
* The form state array.
* @param object $quiz
* The quiz node.
*/
function quiz_categorized_form($form, $form_state, $quiz) {
$form = array();
_quiz_categorized_existing_terms_form($form, $form_state, $quiz);
_quiz_categorized_new_term_form($form, $form_state, $quiz);
$form['nid'] = array(
'#type' => 'value',
'#value' => $quiz->nid,
);
$form['vid'] = array(
'#type' => 'value',
'#value' => $quiz->vid,
);
$form['tid'] = array(
'#type' => 'value',
'#value' => NULL,
);
// Give the user the option to create a new revision of the quiz.
_quiz_add_revision_checkbox($form, $quiz);
// Timestamp is needed to avoid multiple users editing the same quiz at the
// same time.
$form['timestamp'] = array(
'#type' => 'hidden',
'#default_value' => REQUEST_TIME,
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Submit'),
);
$form['#tree'] = TRUE;
return $form;
}
function _quiz_categorized_existing_terms_form(&$form, $form_state, $quiz) {
$terms = _quiz_get_terms($quiz->vid);
if ($terms) {
if (empty($form_state['input']) && !_quiz_build_categorized_question_list($quiz)) {
drupal_set_message(t('There are not enough questions in the requested categories.'), 'error');
}
}
foreach ($terms as $term) {
$form[$term->tid]['name'] = array(
'#markup' => check_plain($term->name),
);
$form[$term->tid]['number'] = array(
'#type' => 'textfield',
'#size' => 3,
'#default_value' => $term->number,
);
$form[$term->tid]['max_score'] = array(
'#type' => 'textfield',
'#size' => 3,
'#default_value' => $term->max_score,
);
$form[$term->tid]['remove'] = array(
'#type' => 'checkbox',
'#default_value' => 0,
);
$form[$term->tid]['weight'] = array(
'#type' => 'textfield',
'#size' => 3,
'#default_value' => $term->weight,
'#attributes' => array(
'class' => array(
'term-weight',
),
),
);
}
}
/**
* Form for adding new terms to a quiz.
*
* @see quiz_categorized_form
*/
function _quiz_categorized_new_term_form(&$form, $form_state, $quiz) {
$form['new'] = array(
'#type' => 'fieldset',
'#title' => t('Add category'),
'#collapsible' => FALSE,
'#collapsed' => FALSE,
'#tree' => FALSE,
);
$form['new']['term'] = array(
'#type' => 'textfield',
'#title' => t('Category'),
'#description' => t('Type in the name of the term you would like to add questions from.'),
'#autocomplete_path' => "node/{$quiz->nid}/questions/term_ahah",
'#field_suffix' => '<a id="browse-for-term" href="javascript:void(0)">' . t('browse') . '</a>',
);
$form['new']['number'] = array(
'#type' => 'textfield',
'#title' => t('Number of questions'),
'#description' => t('How many questions would you like to draw from this term?'),
);
$form['new']['max_score'] = array(
'#type' => 'textfield',
'#title' => t('Max score for each question'),
'#description' => t('The number of points a user will be awarded for each question he gets correct.'),
'#default_value' => 1,
);
}
/**
* Validate the categorized form.
*/
function quiz_categorized_form_validate($form, &$form_state) {
if ($form_state['build_info']['args'][0]->changed > $form_state['values']['timestamp']) {
form_set_error('changed', t('This content has been modified by another user, changes cannot be saved.'));
}
if (!empty($form_state['values']['term'])) {
$tid = _quiz_get_id_from_string($form_state['values']['term']);
if ($tid === FALSE) {
$terms = _quiz_search_terms($form_state['values']['term']);
$num_terms = count($terms);
if ($num_terms == 1) {
$tid = key($terms);
}
elseif ($num_terms > 1) {
form_set_error('term', t('You need to be more specific, or use the autocomplete feature. The term name you entered matches several terms: %terms', array(
'%terms' => implode(', ', $terms),
)));
}
elseif ($num_terms == 0) {
form_set_error('term', t("The term name you entered doesn't match any registered question terms."));
}
}
if (in_array($tid, array_keys($form))) {
form_set_error('term', t('The category you are trying to add has already been added to this quiz.'));
}
else {
form_set_value($form['tid'], $tid, $form_state);
}
if (!_quiz_is_int($form_state['values']['number'])) {
form_set_error('number', t('The number of questions needs to be a positive integer'));
}
if (!_quiz_is_int($form_state['values']['max_score'], 0)) {
form_set_error('max_score', t('The max score needs to be a positive integer or 0'));
}
}
}
/**
* Submit the categorized form.
*/
function quiz_categorized_form_submit($form, $form_state) {
$quiz = node_load($form_state['values']['nid'], $form_state['values']['vid']);
$quiz->number_of_random_questions = 0;
// Update the refresh latest quizzes table so that we know what the users
// latest quizzes are.
if (variable_get('quiz_auto_revisioning', 1)) {
$is_new_revision = quiz_has_been_answered($quiz);
}
else {
$is_new_revision = (bool) $form_state['values']['new_revision'];
}
if (!empty($form_state['values']['tid'])) {
$quiz->number_of_random_questions += _quiz_categorized_add_term($form, $form_state);
}
$quiz->number_of_random_questions += _quiz_categorized_update_terms($form, $form_state);
if ($is_new_revision) {
$quiz->revision = 1;
}
// We save the node to update its timestamp and let other modules react to the
// update. We also do this in case a new revision is required...
node_save($quiz);
}
/**
* Update the categoriez belonging to a quiz with categorized random questions.
*
* Helper function for quiz_categorized_form_submit.
*/
function _quiz_categorized_update_terms(&$form, &$form_state) {
$ids = array(
'weight',
'max_score',
'number',
);
$changed = array();
$removed = array();
$num_questions = 0;
foreach ($form_state['values'] as $key => $existing) {
if (!is_numeric($key)) {
continue;
}
if (!$existing['remove']) {
$num_questions += $existing['number'];
}
foreach ($ids as $id) {
if ($existing[$id] != $form[$key][$id]['#default_value'] && !$existing['remove']) {
$existing['nid'] = $form_state['values']['nid'];
$existing['vid'] = $form_state['values']['vid'];
$existing['tid'] = $key;
if (empty($existing['weight'])) {
$existing['weight'] = 1;
}
$changed[] = $form[$key]['name']['#markup'];
drupal_write_record('quiz_terms', $existing, array(
'vid',
'tid',
));
break;
}
elseif ($existing['remove']) {
db_delete('quiz_terms')
->condition('tid', $key)
->condition('vid', $form_state['values']['vid'])
->execute();
$removed[] = $form[$key]['name']['#markup'];
break;
}
}
}
if (!empty($changed)) {
drupal_set_message(t('Updates were made for the following terms: %terms', array(
'%terms' => implode(', ', $changed),
)));
}
if (!empty($removed)) {
drupal_set_message(t('The following terms were removed: %terms', array(
'%terms' => implode(', ', $removed),
)));
}
return $num_questions;
}
/**
* Adds a term to a categorized quiz.
*
* This is a helper function for the submit function.
*/
function _quiz_categorized_add_term($form, $form_state) {
drupal_set_message(t('The term was added'));
// Needs to be set to avoid error-message from db:
$form_state['values']['weight'] = 0;
drupal_write_record('quiz_terms', $form_state['values']);
return $form_state['values']['number'];
}
/**
* Searches for an id in the end of a string.
*
* Id should be written like "(id:23)".
*
* @param string $string
* The string where we will search for an id.
* @return int
* The matched integer.
*/
function _quiz_get_id_from_string($string) {
$matches = array();
preg_match('/\\(id:(\\d+)\\)$/', $string, $matches);
return isset($matches[1]) ? (int) $matches[1] : FALSE;
}
/**
* Ahah function for finding terms...
*
* @param string $start
* The start of the string we are looking for.
*/
function quiz_categorized_term_ahah($start) {
$terms = _quiz_search_terms($start, $start == '*');
$to_json = array();
foreach ($terms as $key => $value) {
$to_json["{$value} (id:{$key})"] = $value;
}
drupal_json_output($to_json);
}
/**
* Helper function for finding terms...
*
* @param string $start
* The start of the string we are looking for.
*/
function _quiz_search_terms($start, $all = FALSE) {
$terms = array();
$sql_args = array_keys(_quiz_get_vocabularies());
if (empty($sql_args)) {
return $terms;
}
$query = db_select('taxonomy_term_data', 't')
->fields('t', array(
'name',
'tid',
))
->condition('t.vid', $sql_args, 'IN');
if (!$all) {
$query
->condition('t.name', '%' . $start . '%', 'LIKE');
}
$res = $query
->execute();
// @todo Don't user db_fetch_object.
while ($res_o = $res
->fetch()) {
$terms[$res_o->tid] = $res_o->name;
}
return $terms;
}
/**
* Handles "manage questions" tab.
*
* Displays form which allows questions to be assigned to the given quiz.
*
* This function is not used if the question assignment type "categorized random
* questions" is chosen.
*
* @param $form_state
* The form state variable
* @param $quiz
* The quiz node.
*
* @return
* HTML output to create page.
*/
function quiz_questions_form($form, $form_state, $quiz) {
$types = quiz_get_question_types();
_quiz_add_fields_for_creating_questions($form, $types, $quiz);
// Display questions in this quiz.
$form['question_list'] = array(
'#type' => 'fieldset',
'#title' => t('Questions in this @quiz', array(
'@quiz' => QUIZ_NAME,
)),
'#theme' => 'question_selection_table',
'#collapsible' => TRUE,
'#attributes' => array(
'id' => 'mq-fieldset',
),
'question_status' => array(
'#tree' => TRUE,
),
);
// Add randomization settings if this quiz allows randomized questions.
_quiz_add_fields_for_random_quiz($form, $quiz);
$include_random = $quiz->randomization == 2;
// @todo deal with $include_random.
$questions = quiz_get_questions($quiz->nid, $quiz->vid);
if (empty($questions)) {
$form['question_list']['no_questions'] = array(
'#markup' => '<div id = "no-questions">' . t('There are currently no questions in this @quiz. Assign existing questions by using the question browser below. You can also use the links above to create new questions.', array(
'@quiz' => QUIZ_NAME,
)) . '</div>',
);
}
// We add the questions to the form array.
_quiz_add_questions_to_form($form, $questions, $quiz, $types);
// Show the number of questions in the table header.
$always_count = isset($form['question_list']['titles']) ? count($form['question_list']['titles']) : 0;
$form['question_list']['#title'] .= ' (' . $always_count . ')';
// Give the user the option to create a new revision of the quiz.
_quiz_add_revision_checkbox($form, $quiz);
// Timestamp is needed to avoid multiple users editing the same quiz at the
// same time.
$form['timestamp'] = array(
'#type' => 'hidden',
'#default_value' => REQUEST_TIME,
);
$form['actions']['#type'] = 'actions';
$form['actions']['submit'] = array(
'#type' => 'submit',
'#value' => t('Submit'),
'#submit' => array(
'quiz_questions_form_submit',
),
);
return $form;
}
/**
* Fields for creating new questions are added to the quiz_questions_form.
*
* @param $form
* FAPI form(array).
* @param $types
* All the question types(array).
* @param $quiz
* The quiz node.
*/
function _quiz_add_fields_for_creating_questions(&$form, &$types, &$quiz) {
// Display links to create other questions.
$form['additional_questions'] = array(
'#type' => 'fieldset',
'#title' => t('Create new question'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
);
$url_query = drupal_get_destination();
$url_query['quiz_nid'] = $quiz->nid;
$url_query['quiz_vid'] = $quiz->vid;
$create_question = FALSE;
foreach ($types as $type => $info) {
$url_type = str_replace('_', '-', $type);
$options = array(
'attributes' => array(
'title' => t('Create @name', array(
'@name' => $info['name'],
)),
),
'query' => $url_query,
);
$access = node_access('create', $type);
if ($access) {
$create_question = TRUE;
}
$form['additional_questions'][$type] = array(
'#markup' => '<div class="add-questions">' . l($info['name'], "node/add/{$url_type}", $options) . '</div>',
'#access' => $access,
);
}
if (!$create_question) {
$form['additional_questions']['create'] = array(
'#type' => 'markup',
'#markup' => t('You have not enabled any question type module or no has permission been given to create any question.'),
);
}
}
/**
* Add fields for random quiz to the quiz_questions_form.
*
* @param $form
* FAPI form array.
* @param $quiz
* The quiz node(object).
*/
function _quiz_add_fields_for_random_quiz(&$form, $quiz) {
if ($quiz->randomization != 2) {
return;
}
$form['question_list']['random_settings'] = array(
'#type' => 'fieldset',
'#title' => t('Settings for random questions'),
'#collapsible' => TRUE,
);
$form['question_list']['random_settings']['num_random_questions'] = array(
'#type' => 'textfield',
'#size' => 3,
'#maxlength' => 3,
'#weight' => -5,
'#title' => t('Number of random questions'),
'#description' => t('The number of questions to be randomly selected each time someone takes this quiz'),
'#default_value' => isset($quiz->number_of_random_questions) ? $quiz->number_of_random_questions : 10,
);
$form['question_list']['random_settings']['max_score_for_random'] = array(
'#type' => 'textfield',
'#size' => 3,
'#maxlength' => 3,
'#weight' => -5,
'#title' => t('Max score for each random question'),
'#default_value' => isset($quiz->max_score_for_random) ? $quiz->max_score_for_random : 1,
);
}
/**
* Adds the questions in the $questions array to the form.
*
* @param $form
* FAPI form(array).
* @param $questions
* The questions to be added to the question list(array).
* @param $quiz
* The quiz node(object).
* @param $question_types
* array of all available question types.
*/
function _quiz_add_questions_to_form(&$form, &$questions, &$quiz, &$question_types) {
$form['question_list']['weights'] = array(
'#tree' => TRUE,
);
$form['question_list']['qnr_ids'] = array(
'#tree' => TRUE,
);
$form['question_list']['qnr_pids'] = array(
'#tree' => TRUE,
);
$form['question_list']['max_scores'] = array(
'#tree' => TRUE,
);
$form['question_list']['auto_update_max_scores'] = array(
'#tree' => TRUE,
);
$form['question_list']['stayers'] = array(
'#tree' => TRUE,
);
$form['question_list']['revision'] = array(
'#tree' => TRUE,
);
if ($quiz->randomization == 2) {
$form['question_list']['compulsories'] = array(
'#tree' => TRUE,
);
}
foreach ($questions as $question) {
// @todo replace entire form with usage of question instance
$question_node = node_load($question->nid, $question->vid);
$instance = _quiz_question_get_instance($question_node);
$fieldset = 'question_list';
$id = $question->nid . '-' . $question->vid;
$form[$fieldset]['weights'][$id] = array(
'#type' => 'textfield',
'#size' => 3,
'#default_value' => isset($question->weight) ? $question->weight : 0,
);
$form[$fieldset]['qnr_pids'][$id] = array(
'#type' => 'textfield',
'#size' => 3,
'#default_value' => $question->qnr_pid,
);
$form[$fieldset]['qnr_ids'][$id] = array(
'#type' => 'textfield',
'#size' => 3,
'#default_value' => $question->qnr_id,
);
// Quiz directions don't have scoring...
$form[$fieldset]['max_scores'][$id] = array(
'#type' => $instance
->isGraded() ? 'textfield' : 'hidden',
'#size' => 2,
'#disabled' => isset($question->auto_update_max_score) ? $question->auto_update_max_score : FALSE,
'#default_value' => isset($question->max_score) ? $question->max_score : 0,
'#states' => array(
'disabled' => array(
"#edit-auto-update-max-scores-{$id}" => array(
'checked' => TRUE,
),
),
),
);
$form[$fieldset]['auto_update_max_scores'][$id] = array(
'#type' => $instance
->isGraded() ? 'checkbox' : 'hidden',
'#default_value' => isset($question->auto_update_max_score) ? $question->auto_update_max_score : 0,
);
// Add checkboxes to remove questions in js disabled browsers...
$form[$fieldset]['stayers'][$id] = array(
'#type' => 'checkbox',
'#default_value' => 0,
'#attributes' => array(
'class' => array(
'q-staying',
),
),
);
// Add checkboxes to mark compulsory questions for randomized quizzes.
if ($quiz->randomization == 2) {
$form[$fieldset]['compulsories'][$id] = array(
'#type' => 'checkbox',
'#default_value' => isset($question->question_status) ? $question->question_status == QUIZ_QUESTION_ALWAYS ? 1 : 0 : 0,
'#attributes' => array(
'class' => array(
'q-compulsory',
),
),
);
}
if (user_access('view quiz question outside of a quiz')) {
$link_options = array(
'attributes' => array(
'class' => array(
'handle-changes',
),
),
);
$question_titles = l($question->title, 'node/' . $question->nid, $link_options);
}
else {
$question_titles = check_plain($question->title);
}
$form[$fieldset]['titles'][$id] = array(
'#markup' => $question_titles,
);
$form[$fieldset]['types'][$id] = array(
'#markup' => $question_types[$question->type]['name'],
'#question_type' => $question->type,
);
$form[$fieldset]['view_links'][$id] = array(
'#markup' => l(t('Edit'), 'node/' . $question->nid . '/edit', array(
'query' => drupal_get_destination(),
'attributes' => array(
'class' => array(
'handle-changes',
),
),
)),
'#access' => node_access('update', node_load($question->nid, $question->vid)),
);
// For js enabled browsers questions are removed by pressing a remove link.
$form[$fieldset]['remove_links'][$id] = array(
'#markup' => '<a href="#" class="rem-link">' . t('Remove') . '</a>',
);
// Add a checkbox to update to the latest revision of the question.
if ($question->vid == $question->latest_vid) {
$update_cell = array(
'#markup' => t('<em>Up to date</em>'),
);
}
else {
$update_cell = array(
'#type' => 'checkbox',
'#title' => l(t('Latest'), 'node/' . $question->nid . '/revisions/' . $question->latest_vid . '/view') . ' of ' . l(t('revisions'), 'node/' . $question->nid . '/revisions'),
);
}
$form[$fieldset]['revision'][$id] = $update_cell;
}
}
/**
* Adds checkbox for creating new revision. Checks it by default if answers
* exists.
*
* @param $form
* FAPI form(array).
*
* @param $quiz
* Quiz node(object).
*/
function _quiz_add_revision_checkbox(&$form, &$quiz) {
// Recommend and preselect to create the quiz as a new revision if it already
// has been answered.
if (quiz_has_been_answered($quiz)) {
$rev_default = TRUE;
$rev_description = t('This quiz has been answered. To maintain correctness of existing answers and reports, changes should be saved as a new revision.');
}
else {
$rev_default = in_array('revision', variable_get('node_options_quiz', array()));
$rev_description = t('Allow question status changes to create a new revision of the quiz?');
}
if (user_access('manual quiz revisioning') && !variable_get('quiz_auto_revisioning', 1)) {
$form['new_revision'] = array(
'#type' => 'checkbox',
'#default_value' => $rev_default,
'#title' => t('New revision'),
'#description' => $rev_description,
);
}
else {
$form['new_revision'] = array(
'#type' => 'value',
'#value' => $rev_default,
);
}
}
/**
* Validate that the supplied questions are real.
*/
function quiz_questions_form_validate($form, $form_state) {
if ($form_state['build_info']['args'][0]->changed > $form_state['values']['timestamp']) {
form_set_error('changed', t('This content has been modified by another user, changes cannot be saved.'));
}
$already_checked = array();
$weight_map = isset($form_state['values']['weights']) ? $form_state['values']['weights'] : array();
// Make sure the number of random questions is a positive number.
if (isset($form_state['values']['num_random_questions']) && !_quiz_is_int($form_state['values']['num_random_questions'], 0)) {
form_set_error('num_random_questions', 'The number of random questions needs to be a positive number');
}
// Make sure the max score for random questions is a positive number.
if (isset($form_state['values']['max_score_for_random']) && !_quiz_is_int($form_state['values']['max_score_for_random'], 0)) {
form_set_error('max_score_for_random', 'The max score for random questions needs to be a positive number');
}
if (empty($weight_map)) {
form_set_error('none', 'No questions were included.');
return;
}
$question_types = array_keys(quiz_get_question_types());
foreach ($weight_map as $id => $weight) {
list($nid, $vid) = explode('-', $id, 2);
// If a node isn't one of the questionstypes we remove it from the question
// list.
$has_questions = (bool) db_select('node', 'n')
->fields('n', array(
'nid',
))
->condition('type', $question_types, 'IN')
->condition('n.nid', $nid)
->execute()
->fetchField();
if (!$has_questions) {
form_set_error('none', 'One of the supplied questions was invalid. It has been removed from the quiz.');
unset($form_state['values']['weights'][$id]);
}
elseif (in_array($nid, $already_checked)) {
form_set_error('none', 'A duplicate question has been removed. You can only ask a question once per quiz.');
unset($form_state['values']['weights'][$id]);
}
else {
$already_checked[] = $nid;
}
}
// We make sure max score is a positive number.
$max_scores = $form_state['values']['max_scores'];
foreach ($max_scores as $id => $max_score) {
if (!_quiz_is_int($max_score, 0)) {
form_set_error("max_scores][{$id}", t('Max score needs to be a positive number'));
}
}
}
/**
* Update a quiz set of items with new weights and membership.
*
* @param $quiz
* The quiz node.
* @param $weight_map
* Weights for each question(determines the order in which the question will
* be taken by the quiz taker).
* @param $max_scores
* Array of max scores for each question.
* @param $is_new_revision
* Array of boolean values determining if the question is to be updated to the
* newest revision.
* @param $refreshes
* True if we are creating a new revision of the quiz.
* @param $stayers
* Questions added to the quiz.
* @param $compulsories
* Array of boolean values determining if the question is compulsory or not.
*
* @return array set of questions after updating
*/
function _quiz_update_items($quiz, $weight_map, $max_scores, $auto_update_max_scores, $is_new_revision, $refreshes, $stayers, $qnr_ids, $qnr_pids, $compulsories = NULL) {
$questions = array();
foreach ($weight_map as $id => $weight) {
if ($stayers[$id]) {
continue;
}
list($nid, $vid) = explode('-', $id, 2);
$nid = (int) $nid;
$vid = (int) $vid;
$question = new stdClass();
$question->child_nid = $nid;
if (!empty($refreshes[$id])) {
// User marked to update question to latest revision.
$question_node = node_load($nid);
$question->child_vid = $question_node->vid;
}
else {
// Use the provided nid/vid.
$question->child_vid = $vid;
}
if (isset($compulsories)) {
if ($compulsories[$id] == 1) {
$question->question_status = QUIZ_QUESTION_ALWAYS;
}
else {
$question->question_status = QUIZ_QUESTION_RANDOM;
$max_scores[$id] = $quiz->max_score_for_random;
}
}
else {
$question->question_status = QUIZ_QUESTION_ALWAYS;
}
$question->weight = $weight;
$question->max_score = $max_scores[$id];
$question->auto_update_max_score = $auto_update_max_scores[$id];
$question->qnr_pid = $qnr_pids[$id] > 0 ? $qnr_pids[$id] : NULL;
$question->qnr_id = $qnr_ids[$id] > 0 ? $qnr_ids[$id] : NULL;
// Add item as an object in the questions array.
$questions[] = $question;
}
// Save questions.
quiz_set_questions($quiz, $questions, $is_new_revision);
return $questions;
}
/**
* Submit function for quiz_questions.
*
* Updates from the "manage questions" tab.
*/
function quiz_questions_form_submit($form, &$form_state) {
// Load the quiz node.
$quiz = $form_state['build_info']['args'][0];
// Update the refresh latest quizzes table so that we know what the users
// latest quizzes are.
if (variable_get('quiz_auto_revisioning', 1)) {
$is_new_revision = quiz_has_been_answered($quiz);
}
else {
$is_new_revision = !empty($form_state['values']['new_revision']);
}
_quiz_question_browser_submit($form, $form_state);
$weight_map = $form_state['values']['weights'];
$qnr_pids_map = $form_state['values']['qnr_pids'];
$qnr_ids_map = $form_state['values']['qnr_ids'];
$max_scores = $form_state['values']['max_scores'];
$auto_update_max_scores = $form_state['values']['auto_update_max_scores'];
$refreshes = isset($form_state['values']['revision']) ? $form_state['values']['revision'] : NULL;
$stayers = $form_state['values']['stayers'];
$compulsories = isset($form_state['values']['compulsories']) ? $form_state['values']['compulsories'] : NULL;
$num_random = isset($form_state['values']['num_random_questions']) ? $form_state['values']['num_random_questions'] : 0;
$quiz->max_score_for_random = isset($form_state['values']['max_score_for_random']) ? $form_state['values']['max_score_for_random'] : 1;
// Store what questions belong to the quiz.
$questions = _quiz_update_items($quiz, $weight_map, $max_scores, $auto_update_max_scores, $is_new_revision, $refreshes, $stayers, $qnr_ids_map, $qnr_pids_map, $compulsories);
// If using random questions and no term ID is specified, make sure we have
// enough.
$assigned_random = 0;
foreach ($questions as $question) {
if ($question->question_status == QUIZ_QUESTION_RANDOM) {
++$assigned_random;
}
}
// Adjust number of random questions downward to match number of selected
// questions.
if ($num_random > $assigned_random) {
$num_random = $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,
), array(
'langcode' => 'warning',
)));
}
if ($quiz->type == 'quiz') {
// Update the quiz node properties.
$success = db_update('quiz_node_properties')
->fields(array(
'number_of_random_questions' => $num_random ? $num_random : 0,
'max_score_for_random' => $quiz->max_score_for_random,
))
->condition('vid', $quiz->vid)
->condition('nid', $quiz->nid)
->execute();
// Get sum of max_score.
$query = db_select('quiz_node_relationship', 'qnr');
$query
->addExpression('SUM(max_score)', 'sum');
$query
->condition('parent_vid', $quiz->vid);
$query
->condition('question_status', QUIZ_QUESTION_ALWAYS);
$score = $query
->execute()
->fetchAssoc();
$success2 = db_update('quiz_node_properties')
->expression('max_score', 'max_score_for_random * number_of_random_questions + :sum', array(
':sum' => (int) $score['sum'],
))
->condition('vid', $quiz->vid)
->execute();
}
if (isset($success) && isset($success2)) {
entity_get_controller('node')
->resetCache(array(
$quiz->nid,
));
drupal_set_message(t('Questions updated successfully.'));
}
else {
drupal_set_message(t('There was an error updating the @quiz.', array(
'@quiz' => QUIZ_NAME,
)), 'error');
}
}
/**
* Takes care of the browser part of the submitted form values.
*
* This function changes the form_state to reflect questions added via the
* browser. (Especially if js is disabled)
*
* @param $form
* FAPI form(array)
* @param $form_state
* FAPI form_state(array)
*/
function _quiz_question_browser_submit($form, &$form_state) {
// Find the biggest weight:
$next_weight = max($form_state['values']['weights']);
// If a question is chosen in the browser, add it to the question list if it
// isn't already there.
if (isset($form_state['values']['browser']['table']['titles'])) {
foreach ($form_state['values']['browser']['table']['titles'] as $id) {
if ($id !== 0) {
$matches = array();
preg_match('/([0-9]+)-([0-9]+)/', $id, $matches);
$nid = $matches[1];
$vid = $matches[2];
$form_state['values']['weights'][$id] = ++$next_weight;
$form_state['values']['max_scores'][$id] = quiz_question_get_max_score($nid, $vid);
$form_state['values']['stayers'][$id] = 1;
}
}
}
}
// HELPER FUNCTIONS.
/**
* Helper function for theme_question_selection_table.
*
* TODO: DELETE
*
* @see quiz_questions_form()
* @see theme_question_selection_table()
*
* @param $sub_form
* Form definition array for a filtered questions list
* @param $id
* Identifier used in $sub_form
*
* @return table row
* Array defining a table row
*/
function _quiz_get_question_row($sub_form, $id) {
$question_types = quiz_get_question_types();
$type = $sub_form['types'][$id]['#markup'];
$action = theme('item_list', array(
'items' => array(
drupal_render($sub_form['view_links'][$id]),
'<SPAN CLASS="q-remove" STYLE="display:none">' . drupal_render($sub_form['remove_links'][$id]) . '</SPAN>',
),
'attributes' => array(
'class' => array(
'links',
'inline',
),
),
));
$qnr_pid = $sub_form['qnr_pids'][$id]['#default_value'];
$data_array = array(
// The checkbox and the title.
($qnr_pid ? theme('indentation', array(
'size' => 1,
)) : NULL) . drupal_render($sub_form['titles'][$id]),
$type,
$action,
isset($sub_form['revision'][$id]) ? drupal_render($sub_form['revision'][$id]) : t("Up to date"),
drupal_render($sub_form['max_scores'][$id]),
drupal_render($sub_form['auto_update_max_scores'][$id]),
drupal_render($sub_form['stayers'][$id]),
);
if (isset($sub_form['compulsories'])) {
$data_array[] = drupal_render($sub_form['compulsories'][$id]);
}
$data_array[] = drupal_render($sub_form['weights'][$id]);
$data_array[] = drupal_render($sub_form['qnr_pids'][$id]);
$data_array[] = array(
'class' => array(
'tabledrag-hide',
),
'data' => drupal_render($sub_form['qnr_ids'][$id]),
);
$leaf_class = $sub_form['types'][$id]['#question_type'] != 'quiz_page' ? 'tabledrag-leaf' : '';
return array(
'class' => array(
'q-row',
'draggable',
$leaf_class,
),
'id' => 'q-' . $id,
'data' => $data_array,
);
}
/**
* Adds inline js to automatically set the question's node title.
*/
function quiz_set_auto_title() {
$max_length = variable_get('quiz_autotitle_length', 50);
drupal_add_js(array(
'quiz_max_length' => $max_length,
), array(
'type' => 'setting',
));
drupal_add_js(drupal_get_path('module', 'quiz') . '/js/quiz.auto-title.js');
}
/**
* Admin page for feedback settings.
*/
function quiz_feedback_page() {
$rows = array();
$header = array(
t('Time'),
t('Description'),
t('Conditions'),
);
foreach (quiz_get_feedback_times() as $key => $time) {
$conditions = l(t('Conditions'), 'admin/quiz/feedback/manage/quiz_feedback_' . $key);
$rows[] = array(
$time['name'],
$time['description'],
$conditions,
);
}
return theme('table', array(
'caption' => t('Quiz feedback conditions'),
'rows' => $rows,
'header' => $header,
));
}
Functions
Name![]() |
Description |
---|---|
quiz_admin_node_form | Renders the quiz node form for the admin pages. |
quiz_admin_node_form_submit | Submit function for quiz_admin_node_form. |
quiz_admin_node_form_validate | Validation function for the quiz_admin_node_form form. |
quiz_admin_results | Quiz result report page for the quiz admin section. |
quiz_admin_settings | This builds the main settings form for the quiz module. |
quiz_categorized_form | Form for managing what questions should be added to a quiz with categorized random questions. |
quiz_categorized_form_submit | Submit the categorized form. |
quiz_categorized_form_validate | Validate the categorized form. |
quiz_categorized_term_ahah | Ahah function for finding terms... |
quiz_feedback_page | Admin page for feedback settings. |
quiz_questions_form | Handles "manage questions" tab. |
quiz_questions_form_submit | Submit function for quiz_questions. |
quiz_questions_form_validate | Validate that the supplied questions are real. |
quiz_questions_page | Creates a form for quiz questions. |
quiz_settings_form_submit | Submit the admin settings form. |
quiz_settings_form_validate | Validation of the Form Settings form. |
quiz_set_auto_title | Adds inline js to automatically set the question's node title. |
_quiz_add_fields_for_creating_questions | Fields for creating new questions are added to the quiz_questions_form. |
_quiz_add_fields_for_random_quiz | Add fields for random quiz to the quiz_questions_form. |
_quiz_add_questions_to_form | Adds the questions in the $questions array to the form. |
_quiz_add_revision_checkbox | Adds checkbox for creating new revision. Checks it by default if answers exists. |
_quiz_categorized_add_term | Adds a term to a categorized quiz. |
_quiz_categorized_existing_terms_form | |
_quiz_categorized_new_term_form | Form for adding new terms to a quiz. |
_quiz_categorized_update_terms | Update the categoriez belonging to a quiz with categorized random questions. |
_quiz_get_id_from_string | Searches for an id in the end of a string. |
_quiz_get_question_row | Helper function for theme_question_selection_table. |
_quiz_question_browser_submit | Takes care of the browser part of the submitted form values. |
_quiz_search_terms | Helper function for finding terms... |
_quiz_update_items | Update a quiz set of items with new weights and membership. |