<?php


/**
 * @file
 * Quiz Module
 *
 * This module allows the creation of interactive quizzes for site visitors.
 */

// This module is structured as follows:
//
// The main module file:
// * Defines and general includes are at the top.
// * Hook implementations come immediately after.
// * Public functions come next.
// * Private functions are at the bottom.
//
// Where possible, user pages are located in quiz.pages.inc, and admin pages
// are in quiz.admin.inc. Most utility functions have been left here, even if they
// are only used by a function in one of the other files. quiz_datetime.inc holds
// some additional date/time functions.
//
// Themes are in quiz.pages.inc unless they clearly only apply to admin screens.
// Then they are in quiz.admin.inc.
//
// Views support is included in includes/views/quiz.views.inc
define('QUIZ_VIEWS_DIR', drupal_get_path('module', 'quiz') . '/includes/views');

module_load_include('inc', 'quiz', 'quiz_datetime');

/*
 * Define question statuses...
 */
define('QUESTION_RANDOM', 0);
define('QUESTION_ALWAYS', 1);
define('QUESTION_NEVER', 2);

/**
 * Quiz name.
 */
define('QUIZ_NAME', _quiz_get_quiz_name());

/**
 * Define feedback statuses.
 */
define('QUIZ_FEEDBACK_END',              0);
define('QUIZ_FEEDBACK_QUESTION',         1);
define('QUIZ_FEEDBACK_NEVER',            2);

/**
 * Define options for keeping results.
 */
define('QUIZ_KEEP_BEST', 0);
define('QUIZ_KEEP_LATEST', 1);
define('QUIZ_KEEP_ALL', 2);

/**
 * Implements hook_help().
 */
function quiz_help($path, $arg) {
  // This is moved on an experimental basis.
  include_once DRUPAL_ROOT . '/' . drupal_get_path('module', 'quiz') . '/quiz.help.inc';
  return _quiz_help($path, $arg);
}

/**
 * Implements hook_views_api().
 */
function quiz_views_api() {
  return array(
    'api' => 2,
    'path' => QUIZ_VIEWS_DIR,
  );
}

/**
 * Implements hook_permission().
 */
function quiz_permission() {
  return array(
    // Configure quiz:
    'administer quiz configuration' => array(
      'title' => t('Administer quiz configuration'),
      'description' => t('Control the various settings and behaviours of quiz'),
      'restrict access' => TRUE,
    ),
    // Managing quizzes:
    'access quiz' => array(
      'title' => t('Take quiz'),
      'description' => t('Can access (take) all quizzes.'),
    ),
    // viewing results:
    'view any quiz results' => array(
      'title' => t('View any quiz results'),
      'description' => t('Can view results for all quizzes and users.'),
    ),
    'view own quiz results' => array(
      'title' => t('View own quiz results'),
      'description' => t('Quiz takers can view their own results, also when quiz is not passed.'),
    ),
    'view results for own quiz' => array(
      'title' => t('View results for own quiz'),
      'description' => t('Quiz makers can view results for their own quizzes.'),
    ),
    // deleting results:
    'delete any quiz results' => array(
      'title' => t('Delete any quiz results'),
    ),
    'delete results for own quiz' => array(
      'title' => t('Delete results for own quiz'),
    ),
    // scoring:
    'score any quiz' => array(
      'title' => t('Score any quiz'),
    ),
    'score own quiz' => array(
      'title' => t('Score own quiz'),
    ),
    'score taken quiz answer' => array(
      'title' => t('score taken quiz answer'),
      'description' => t('Allows attendee to score questions needing manual evaluation.'),
    ),
    // Allow a quiz question to be viewed outside of a test.
    'view quiz question outside of a quiz' => array(
      'title' => t('View quiz question outside of a quiz'),
      'description' => t('Questions can only be accessed through taking a quiz (not as individual nodes) unless this permission is given.'),
    ),
    // Allow the user to see the correct answer, when viewed outside a quiz
    'view any quiz question correct response' => array(
      'title' => t('View any quiz question correct response'),
      'description' => t('Allow the user to see the correct answer, when viewed outside a quiz.'),
    ),
    // Allows users to pick a name for their questions. Otherwise this is auto
    // generated.
    'edit question titles' => array(
      'title' => t('Edit question titles'),
      'description' => t('Questions automatically get a title based on the question text. This allows titles to be set manually.'),
    ),
    // Allow users to assign an action to be performed when a user has completed
    // a quiz:
    'assign any action to quiz events' => array(
      'title' => t('Assign any action to quiz events'),
      'description' => t("Enables Drupal's actions system for Quiz triggers."),
    ),
    // Control revisioning, only assign this permission to users who understand
    // who permissions work. Note: If a quiz or question is changed and not
    // revisioned you will also change existing result reports.
    'manual quiz revisioning' => array(
      'title' => t('Manual quiz revisioning'),
      'description' => t('Quizzes are revisioned automatically each time they are changed. This allows you to do revisions manually.'),
    ),
  );
}

/**
 * Implements hook_admin_paths().
 */
function quiz_admin_paths() {
  if (variable_get('node_admin_theme')) {
    return array(
      'node/*/questions' => TRUE,
    );
  }
}

/**
 * Helper function to determine if a user has access to the different results
 * pages.
 *
 * @param $quiz
 *   The quiz node.
 * @param $rid
 *   The result id of a result we are trying to access.
 * @return boolean
 *   TRUE if user has permission.
 */
function quiz_access_results($quiz, $rid = NULL) {
  global $user;

  if ($quiz->type !== 'quiz') {
    return FALSE;
  }
  // If rid is set we must make sure the result belongs to the quiz we are
  // viewing results for.
  if (isset($rid)) {
    $res = db_query('SELECT qnr.nid, qnr.uid FROM {quiz_node_results} qnr WHERE result_id = :result_id', array(':result_id' => $rid))->fetch();
    if ($res && $res->nid != $quiz->nid) {
      return FALSE;
    }
  }
  if (user_access('view any quiz results')) {
    return TRUE;
  }
  if (user_access('view results for own quiz') && $user->uid == $quiz->uid) {
    return TRUE;
  }
  if (user_access('score taken quiz answer')) {
    //check if the taken user is seeing his result
    if (isset($rid) && $res && $res->uid == $user->uid) {
      return TRUE;
    }
  }
}

/**
 * Helper function to determine if a user has access to view his quiz results
 *
 * @param object $quiz
 *  The Quiz node
 */
function quiz_access_my_results($quiz) {
  global $user;
  if ($quiz->type !== 'quiz') {
    return FALSE;
  }
  if (user_access('view own quiz results') && !quiz_access_results($quiz)) {
    $answered = db_query('SELECT 1 FROM {quiz_node_results} WHERE nid = :nid AND uid = :uid AND is_evaluated = :is_evaluated', array(':nid' => $quiz->nid, ':uid' => $user->uid, ':is_evaluated' => 1))->fetchField();
    if ($answered) {
      return TRUE;
    }
  }
}

/**
 * Helper function to determine if a user has access to view a specific quiz result.
 *
 * @param int $rid
 *  Result id
 * @return boolean
 *  True if access, false otherwise
 */
function quiz_access_my_result($rid) {
  global $user;
  if (!user_access('view own quiz results')) {
    return FALSE;
  }
  $time_end = db_query('SELECT time_end FROM {quiz_node_results} WHERE result_id = :result_id AND uid = :uid', array(':result_id' => $rid, ':uid' => $user->uid))->fetchField();
  return $time_end > 0;
}

/**
 * Helper function to determine if a user has access to score a quiz.
 *
 * @param $quiz_creator
 *   uid of the quiz creator.
 */
function quiz_access_to_score($quiz_creator = NULL) {
  global $user;
  if ($quiz_creator == NULL && ($quiz = quiz_get_quiz_from_menu())) {
    $quiz_creator = $quiz->uid;
  }
  if (user_access('score any quiz')) {
    return TRUE;
  }
  if (user_access('score own quiz') && $user->uid == $quiz_creator) {
    return TRUE;
  }
  if (user_access('score taken quiz answer')) {
    return TRUE;
  }
}

/**
 * Helper function to check if the user has any of a given list of permissions.
 *
 * @param args
 *   Any number of permissions.
 * @return
 *   TRUE if the user has access to any of the arguments given.
 */
function quiz_access_multi_or() {
  $perms = func_get_args();
  foreach ($perms as $perm) {
    if (user_access($perm)) {
      return TRUE;
    }
  }
}

/**
 * Implements hook_node_info().
 */
function quiz_node_info() {
  return array(
    'quiz' => array(
      'name' => t('@quiz', array("@quiz" => QUIZ_NAME)),
      'base' => 'quiz',
      'description' => 'Create interactive quizzes for site visitors',
    )
  );
}
/*
function quiz_element_info() {
  $types['advancedtable'] = array(
    '#process' => array('form_process_fieldset', 'ajax_process_form'),
    '#theme_wrappers' => array('quiz_table'),
  );
  $types['advancedtable_header'] = array(
    '#process' => array('form_process_fieldset', 'ajax_process_form'),
    '#theme_wrappers' => array('quiz_table_header'),
  );
  $types['advancedtable_body'] = array(
    '#process' => array('form_process_fieldset', 'ajax_process_form'),
    '#theme_wrappers' => array('quiz_table_body'),
  );
  return $types;
}
*/

/*
 * Implementation of hook_cron().
 */
/**
 * @todo Please document this function.
 * @see http://drupal.org/node/1354
 */
function quiz_cron() {
  // Remove old quiz results that haven't been finished.
  $rm_time = variable_get('quiz_remove_partial_quiz_record', '0');

  if ($rm_time) { // $time = 0 for never.
    db_delete('quiz_node_results')
    ->condition('time_end', 0)
    ->where('(:request_time - time_start) > :remove_time', array(
      ':request_time' => REQUEST_TIME,
      ':remove_time' => $rm_time
    ))
    ->execute();
  }
}

/**
 * Implements hook_menu().
 */
function quiz_menu() {

  // Take quiz.
  $items['node/%node/take'] = array(
    'title' => 'Take',
    'page callback' => 'quiz_take',
    'page arguments' => array(1),
    'access callback' => 'quiz_take_access',
    'access arguments' => array(1),
    'type' => MENU_LOCAL_TASK,
  );

  // Admin pages.
  $items['admin/quiz'] = array(
    'title' => '@quiz',
    'title arguments' => array('@quiz' => QUIZ_NAME),
    'description' => 'View results, score answers, run reports and edit configurations.',
    'page callback' => 'system_admin_menu_block_page',
    'access arguments' => array('administer quiz configuration', 'score any quiz', 'score own quiz', 'view any quiz results', 'view results for own quiz'),
    'access callback' => 'quiz_access_multi_or',
    'type' => MENU_NORMAL_ITEM,
    'file' => 'system.admin.inc',
    'file path' => drupal_get_path('module', 'system'),
  );

  $items['admin/quiz/settings'] = array(
    'title' => '@quiz settings',
    'title arguments' => array('@quiz' => QUIZ_NAME),
    'description' => 'Change settings for the all Quiz project modules.',
    'page callback' => 'system_admin_menu_block_page',
    'access arguments' => array('administer quiz configuration'),
    'type' => MENU_NORMAL_ITEM,
    'file' => 'system.admin.inc',
    'file path' => drupal_get_path('module', 'system'),
  );

  $items['admin/quiz/settings/config'] = array(
    'title' => '@quiz configuration',
    'title arguments' => array('@quiz' => QUIZ_NAME),
    'description' => 'Configure the Quiz module.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('quiz_admin_settings'),
    'access arguments' => array('administer quiz configuration'),
    'type' => MENU_NORMAL_ITEM, // optional
    'file' => 'quiz.admin.inc',
  );

  $items['admin/quiz/settings/quiz_form'] = array(
    'title' => '@quiz form configuration',
    'title arguments' => array('@quiz' => QUIZ_NAME),
    'description' => 'Configure default values for the quiz creation form.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('quiz_admin_node_form'),
    'access arguments' => array('administer quiz configuration'),
    'type' => MENU_NORMAL_ITEM, // optional
    'file' => 'quiz.admin.inc',
  );

  $items['admin/quiz/reports'] = array(
    'title' => 'Quiz reports and scoring',
    'title arguments' => array('@quiz' => QUIZ_NAME),
    'description' => 'View reports and score answers.',
    'page callback' => 'system_admin_menu_block_page',
    'access arguments' => array('view any quiz results', 'view results for own quiz'),
    'access callback' => 'quiz_access_multi_or',
    'type' => MENU_NORMAL_ITEM,
    'file' => 'system.admin.inc',
    'file path' => drupal_get_path('module', 'system'),
  );

  $items['admin/quiz/reports/results'] = array(
    'title' => '@quiz results',
    'title arguments' => array('@quiz' => QUIZ_NAME),
    'description' => 'View results.',
    'page callback' => 'quiz_admin_quizzes',
    'access arguments' => array('view any quiz results', 'view results for own quiz'),
    'access callback' => 'quiz_access_multi_or',
    'type' => MENU_NORMAL_ITEM,
    'file' => 'quiz.admin.inc',
  );

  $items['node/%node/results'] = array(
    'title' => 'Results',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('quiz_results_manage_results_form', 1),
    'access arguments' => array(1),
    'access callback' => 'quiz_access_results',
    'type' => MENU_LOCAL_TASK,
    'file' => 'quiz.admin.inc',
    'weight' => 3,
  );

  $items['node/%node/myresults'] = array(
    'title' => 'My results',
    'page callback' => 'quiz_my_results',
    'page arguments' => array(1),
    'access callback' => 'quiz_access_my_results',
    'access arguments' => array(1),
    'type' => MENU_LOCAL_TASK,
    'file' => 'quiz.pages.inc',
    'weight' => 3,
  );

  $items['node/%node/myresults/%quiz_rid'] = array(
    'title' => 'User results',
    'page callback' => 'quiz_user_results',
    'page arguments' => array(3),
    'access arguments' => array(3),
    'access callback' => 'quiz_access_my_result',
    'type' => MENU_CALLBACK,
    'file' => 'quiz.pages.inc',
  );

  $items['node/%node/results/%quiz_rid'] = array(
    'title' => 'Results',
    'page callback' => 'quiz_admin_results',
    'page arguments' => array(1, 3),
    'type' => MENU_CALLBACK, // , MENU_SUGGESTED_ITEM, MENU_LOCAL_TASK, MENU_DEFAULT_LOCAL_TASK
    'file' => 'quiz.admin.inc',
    'access arguments' => array(1, 3),
    'access callback' => 'quiz_access_results',
  );

  // Add questions to quiz.
  $items['node/%node/questions'] = array(
    'title' => 'Manage questions',
    'page callback' => 'quiz_questions',
    'page arguments' => array(1),
    'access callback' => 'quiz_type_confirm',
    'access arguments' => array(1, 'update'),
    'type' => MENU_LOCAL_TASK,
    'file' => 'quiz.admin.inc',
    'weight' => 2,
  );

  // User pages.
  $items['user/%/myresults'] = array(
    'title' => 'My results',
    'page callback' => 'quiz_get_user_results',
    'page arguments' => array(1),
    'access callback' => '_quiz_user_results_access',
    'access arguments' => array(1),
    'type' => MENU_LOCAL_TASK,
    'file' => 'quiz.pages.inc',
  );

  $items['user/quiz/%/userresults'] = array(
    'title' => 'User results',
    'page callback' => 'quiz_user_results',
    'page arguments' => array(2),
    'access arguments' => array(2),
    'access callback' => 'quiz_access_my_result',
    'type' => MENU_CALLBACK,
    'file' => 'quiz.pages.inc',
  );

  $items['node/%node/questions/browser_ahah'] = array(//TODO: Add node access instead of user access...
    'page callback' => 'quiz_browser_ahah',
    'type' => MENU_CALLBACK,
    'access arguments' => array('create quiz content'),
    'file' => 'quiz.admin.inc',
  );

  $items['node/%node/questions/term_ahah'] = array(//TODO: Add node access instead of user access...
    'page callback' => 'quiz_categorized_term_ahah',
    'type' => MENU_CALLBACK,
    'access arguments' => array('create quiz content'),
    'file' => 'quiz.admin.inc',
  );

  $items['node/%node/results/browser_ahah'] = array(
    'page callback' => 'quiz_results_mr_browser_ahah',
    'type' => MENU_CALLBACK,
    'access callback' => 'quiz_access_results',
    'access arguments' => array(1),
    'file' => 'quiz.admin.inc',
  );

  return $items;
}

/**
 * Implements hook_theme().
 */
function quiz_theme($existing, $type, $theme, $path) {
  return array(
    'quiz_view_stats' => array(
      'variables' => array('node' => NULL),
      'file' => 'quiz.pages.inc',
    ),
    'quiz_categorized_form' => array(
      'render element' => 'form',
      'file' => 'quiz.admin.inc'
    ),
    'quiz_get_user_results' => array(
      'variables' => array('results' => NULL),
      'file' => 'quiz.pages.inc',
    ),
    'quiz_take_summary' => array(
      'variables' => array('quiz' => NULL, 'questions' => NULL, 'score' => 0, 'summary' => '', 'rid' => NULL),
      'file' => 'quiz.pages.inc',
    ),
    'quiz_admin_summary' => array(
      'variables' => array('quiz' => NULL, 'questions' => NULL, 'score' => NULL, 'summary' => NULL, 'rid' => NULL),
      'file' => 'quiz.admin.inc',
    ),
    'quiz_user_summary' => array(
      'variables' => array('quiz' => NULL, 'questions' => NULL, 'score' => NULL, 'summary' => NULL, 'rid' => NULL),
      'file' => 'quiz.pages.inc',
    ),
    'quiz_progress' => array(
      'variables' => array('question_number' => NULL, 'num_questions' => NULL, 'allow_jumping' => NULL, 'time_limit' => NULL),
      'file' => 'quiz.pages.inc',
    ),
    'quiz_no_feedback' => array(
      'file' => 'quiz.pages.inc',
      'variables' => array(),
    ),
    'quiz_admin_quizzes' => array(
      'file' => 'quiz.admin.inc',
      'variables' => array('results' => NULL),
    ),
    'quiz_single_question_node' => array(
      'file' => 'quiz.pages.inc',
      'variables' => array('question_node' => NULL),
    ),
    'question_selection_table' => array(
      'file' => 'quiz.admin.inc',
      'render element' => 'form',
    ),
    'quiz_score_correct' => array(
      'file' => 'quiz.pages.inc',
      'variables' => array(),
    ),
    'quiz_score_incorrect' => array(
      'file' => 'quiz.pages.inc',
      'variables' => array(),
    ),
    'quiz_question_browser' => array(
      'render element' => 'form',
      'path' => $path . '/theme',
      'template' => 'quiz-question-browser',
    ),
    'quiz_results_browser_body' => array(
      'render element' => 'form',
      'path' => $path . '/theme',
      'template' => 'quiz-results-browser-body',
    ),
    'quiz_results_browser_header' => array(
      'render element' => 'form',
      'path' => $path . '/theme',
      'template' => 'quiz-results-browser-header',
    ),
    'quiz_questions_browser_body' => array(
      'render element' => 'form',
      'path' => $path . '/theme',
      'template' => 'quiz-questions-browser-body',
    ),
    'quiz_questions_browser_header' => array(
      'render element' => 'form',
      'path' => $path . '/theme',
      'template' => 'quiz-questions-browser-header',
    ),
    'quiz_report_form' => array(
      'render element' => 'form',
      'path' => $path . '/theme',
      'template' => 'quiz-report-form',
    ),
    'quiz_node_form' => array(
      'render element' => 'form',
      'file' => 'quiz.admin.inc',
    ),
    'quiz_browser' => array(
      'render element' => 'form',
      'file' => 'quiz.admin.inc',
    ),
    'quiz_jumper' => array(
      'variables' => array('current' => 0, 'num_questions' => 0),
      'file' => 'quiz.admin.inc',
    ),
    'quiz_my_results_for_quiz' => array(
      'variables' => array('rows' => array()),
      'file' => 'quiz.admin.inc'
    )
  );
}

/**
 * Implements hook_form_alter().
 *
 * Override settings in some existing forms. For example, we remove the
 * preview button on a quiz.
 */
function quiz_form_alter(&$form, $form_state, $form_id) {
  // Set taking options the first vertical tab item
  if ('quiz_node_form' == $form_id && isset($form['menu']['#weight'])) {
    $form['menu']['#weight'] = 1;
  }
  // Remove revision fieldset if user don't have access to revise quiz manually.
  if (isset($form['#quiz_check_revision_access'])) {
    if (!user_access('manual quiz revisioning') || variable_get('quiz_auto_revisioning', 1)) {
      $form['revision_information']['revision']['#type'] = 'value';
      $form['revision_information']['revision']['#value'] = $form['revision_information']['revision']['#default_value'];
      $form['revision_information']['log']['#type'] = 'value';
      $form['revision_information']['log']['#value'] = $form['revision_information']['log']['#default_value'];
      $form['revision_information']['#access'] = FALSE;
    }
    unset($form['actions']['preview'], $form['actions']['preview_changes']);
    $form['actions']['submit']['#access'] = TRUE;
    // Quiz questions might want to add a cancel button.
    if (isset($form['#cancel_button'])) {
      $form['actions']['cancel'] = array(
        '#markup' => l(t('Cancel'), $form_state['redirect']),
        '#weight' => 6,
      );
    }
  }
}

/**
 * Implements hook_form_FORM_ID_alter() for the quiz node form.
 */
function quiz_form_quiz_node_form_alter(&$form, $form_state) {
  $form['actions']['submit']['#submit'][] = 'quiz_form_quiz_node_form_submit';
}

/**
 * Quiz node form submit callback.
 *
 * @see quiz_form_quiz_node_form_alter()
 */
function quiz_form_quiz_node_form_submit($form, &$form_state) {
  $node = $form_state['node'];

  if (empty($form_state['values']['nid'])) {
    drupal_set_message(t('You just created a new quiz. Now you have to add questions to it. This page is for adding and managing questions. Here you can create new questions or add some of your already created questions. If you want to change the quiz settings, you can use the "edit" tab.'));
    $form_state['redirect'] = "node/$node->nid/questions";
  }
  // If the quiz don't have any questions jump to the manage questions tab.
  $res = db_query('SELECT child_vid FROM {quiz_node_relationship} WHERE parent_vid = :vid', array(':vid' => $node->vid))->fetchField();
  if (!is_numeric($res)) {
    $form_state['redirect'] = 'node/' . $node->nid . '/questions';
  }
}

/**
 * Implements hook_insert().
 */
function quiz_insert($node) {
  // Apply defaults.
  $defaults = _quiz_load_user_settings(variable_get('quiz_def_uid', 1)) + _quiz_get_node_defaults();
  foreach ($defaults as $key => $value) {
    if (!isset($node->$key)) {
      $node->$key = $value;
    }
  }

  _quiz_save_user_settings($node);

  // Copy all the questions belonging to the quiz if this is a new translation.
  if ($node->is_new && isset($node->translation_source)) {
    quiz_copy_questions($node);
  }

  _quiz_common_presave_actions($node);

  // If the quiz is saved as not randomized we have to make sure that questions belonging to the quiz are saved as not random
  _quiz_check_num_random($node);
  _quiz_check_num_always($node);

  // TODO Please convert this statement to the D7 database API syntax.
  /* db_query($sql, $node->vid, $node->nid, $node->aid, $node->number_of_random_questions, $node->randomization, $node->backwards_navigation, $node->repeat_until_correct, $node->quiz_open, $node->quiz_close, $node->takes, $node->show_attempt_stats, $node->keep_results, $node->time_limit, $node->pass_rate, $node->summary_pass, $node->summary_default, $node->quiz_always, $node->feedback_time, $node->display_feedback, $tid, isset($node->has_userpoints) ? $node->has_userpoints : 0, $node->allow_skipping, $node->allow_resume, $node->allow_jumping) */
  db_insert('quiz_node_properties')
    ->fields(array(
      'vid'                         => $node->vid,
      'nid'                         => $node->nid,
      'aid'                         => !empty($node->aid) ? $node->aid : 0,
      'number_of_random_questions'  => $node->number_of_random_questions,
      'randomization'               => $node->randomization,
      'backwards_navigation'        => $node->backwards_navigation,
      'repeat_until_correct'        => $node->repeat_until_correct,
      'quiz_open'                   => $node->quiz_open,
      'quiz_close'                  => $node->quiz_close,
      'takes'                       => $node->takes,
      'show_attempt_stats'          => $node->show_attempt_stats,
      'keep_results'                => $node->keep_results,
      'time_limit'                  => $node->time_limit,
      'pass_rate'                   => $node->pass_rate,
      'summary_pass'                => is_array($node->summary_pass) ? $node->summary_pass['value'] : $node->summary_pass,
      'summary_pass_format'         => is_array($node->summary_pass) ? $node->summary_pass['format'] : $node->summary_pass_format,
      'summary_default'             => is_array($node->summary_default) ? $node->summary_default['value'] : $node->summary_default,
      'summary_default_format'      => is_array($node->summary_default) ? $node->summary_default['format'] : $node->summary_default_format,
      'quiz_always'                 => $node->quiz_always,
      'feedback_time'               => $node->feedback_time,
      'display_feedback'            => $node->display_feedback,
      'tid'                         => (isset($node->tid) ? $node->tid : 0),
      'has_userpoints'              => isset($node->has_userpoints) ? $node->has_userpoints : 0,
      'allow_skipping'              => $node->allow_skipping,
      'allow_resume'                => $node->allow_resume,
      'allow_jumping'               => $node->allow_jumping,
      'show_passed'                 => $node->show_passed,
      'mark_doubtful'		    => $node->mark_doubtful
    ))
    ->execute();

  _quiz_insert_resultoptions($node);
}

/**
 * Implements hook_update().
 */
function quiz_update($node) {
  _quiz_save_user_settings($node);
  // Quiz node vid (revision) was updated.
  if (isset($node->revision) && $node->revision) {
    // Insert a new row in the quiz_node_properties table.
    $old_auto = isset($node->auto_created);
    $node->auto_created = TRUE;
    quiz_insert($node);
    if (!$old_auto) {
      unset($node->auto_created);
    }

    // Create new quiz-question relation entries in the quiz_node_relationship
    // table.
    quiz_update_quiz_question_relationship($node->old_vid, $node->vid, $node->nid);
  }

  // Quiz node vid (revision) was not updated.
  else {
    // Update an existing row in the quiz_node_properties table.
    _quiz_common_presave_actions($node);
    $resource = db_update('quiz_node_properties')
      ->fields(array(
        'vid'                         => $node->vid,
        'aid'                         => $node->aid,
        'randomization'               => $node->randomization,
        'backwards_navigation'        => $node->backwards_navigation,
        'repeat_until_correct'        => $node->repeat_until_correct,
        'quiz_open'                   => $node->quiz_open,
        'quiz_close'                  => $node->quiz_close,
        'takes'                       => $node->takes,
        'show_attempt_stats'          => $node->show_attempt_stats,
        'keep_results'                => $node->keep_results,
        'time_limit'                  => $node->time_limit,
        'pass_rate'                   => $node->pass_rate,
        'summary_pass'                => is_array($node->summary_pass) ? $node->summary_pass['value'] : $node->summary_pass,
        'summary_pass_format'         => is_array($node->summary_pass) ? $node->summary_pass['format'] : $node->summary_pass_format,
        'summary_default'             => is_array($node->summary_default) ? $node->summary_default['value'] : $node->summary_default,
        'summary_default_format'      => is_array($node->summary_default) ? $node->summary_default['format'] : $node->summary_default_format,
        'quiz_always'                 => $node->quiz_always,
        'feedback_time'               => $node->feedback_time,
        'display_feedback'            => $node->display_feedback,
        'number_of_random_questions'  => $node->number_of_random_questions,
        'has_userpoints'              => isset($node->has_userpoints) ? $node->has_userpoints : 0,
        'allow_skipping'              => $node->allow_skipping,
        'allow_resume'                => $node->allow_resume,
        'allow_jumping'               => $node->allow_jumping,
        'show_passed'                 => $node->show_passed,
        'mark_doubtful'		      => $node->mark_doubtful
       ))
      ->condition('vid', $node->vid)
      ->condition('nid', $node->nid)
      ->execute();
    _quiz_update_resultoptions($node);
  }
  _quiz_check_num_random($node);
  _quiz_check_num_always($node);
  quiz_update_max_score_properties(array($node->vid));
  drupal_set_message(t('Some of the updated settings may not apply to quiz being taken already. To see all changes in action you need to start again.'), 'warning');
}

/**
 * Implements hook_field_extra_fields().
 */
function quiz_field_extra_fields() {
  $extra['node']['quiz'] = array(
    'display' => array(
      'take' => array(
        'label' => t('Take quiz button'),
        'description' => t('The take button.'),
        'weight' => 10,
      ),
      'stats' => array(
	'label' => t('Quiz summary'),
	'description' => t('Quiz summary'),
	'weight' => 9,
      ),
    ),
    'form' => array(
      'taking' => array(
        'label' => t('Taking options'),
        'description' => t('Fieldset for customizing how a quiz is taken'),
        'weight' => 0,
      ),
      'quiz_availability' => array(
        'label' => t('Availability options'),
        'description' => t('Fieldset for customizing when a quiz is available'),
        'weight' => 0,
      ),
      'summaryoptions' => array(
        'label' => t('Summary options'),
        'description' => t('Fieldset for customizing summaries in the quiz reports'),
        'weight' => 0,
      ),
      'resultoptions' => array(
        'label' => t('Result options'),
        'description' => t('Fieldset for customizing result comments in quiz reports'),
        'weight' => 0,
      ),
      'remember_settings' => array(
        'label' => t('Remember settings'),
        'description' => t('Checkbox for remembering quiz settings'),
        'weight' => 0,
      ),
    ),
  );
  return $extra;
}

/**
 * Common actions that need to be done before a quiz is inserted or updated
 *
 * @param $node
 *   Quiz node
 */
function _quiz_common_presave_actions(&$node) {
  quiz_translate_form_date($node, 'quiz_open');
  quiz_translate_form_date($node, 'quiz_close');

  if (empty($node->pass_rate)) {
    $node->pass_rate = 0;
  }
  if ($node->randomization < 2) {
    $node->number_of_random_questions = 0;
  }
}

/**
 * Implements hook_delete().
 */
function quiz_delete($node) {
  $res = db_query('SELECT result_id FROM {quiz_node_results}
          WHERE nid = :nid', array(':nid' => $node->nid));
  $rids = array();
  while ($rid = $res->fetchField()) {
    $rids[] = $rid;
  }
  quiz_delete_results($rids);

  // Remove quiz node records from table quiz_node_properties
  db_delete('quiz_node_properties')
    ->condition('nid', $node->nid)
    ->execute();
  // Remove quiz node records from table quiz_node_relationship
  db_delete('quiz_node_relationship')
    ->condition('parent_nid', $node->nid)
    ->execute();
  // Remove quiz node records from table quiz_node_results
  db_delete('quiz_node_results')
    ->condition('nid', $node->nid)
    ->execute();
  // Remove quiz node records from table quiz_node_result_options
  db_delete('quiz_node_result_options')
    ->condition('nid', $node->nid)
    ->execute();

}

/**
 * Returns default values for all quiz settings.
 *
 * @return
 *   Array of default values.
 */
function _quiz_get_node_defaults() {
  return array(
    'aid' => NULL,
    'number_of_random_questions' => 0,
    'max_score_for_random' => 1,
    'pass_rate' => 75,
    'summary_pass' => '',
    'summary_pass_format' => filter_fallback_format(),
    'summary_default' => '',
    'summary_default_format' => filter_fallback_format(),
    'randomization' => 0,
    'backwards_navigation' => 1,
    'repeat_until_correct' => 0,
    'feedback_time' => 0,
    'display_feedback' => 1,
    'quiz_open' => 0,
    'quiz_close' => 0,
    'takes' => 0,
    'show_attempt_stats' => 1,
    'keep_results' => 2,
    'time_limit' => 0,
    'quiz_always' => 1,
    'tid' => 0,
    'has_userpoints' => 0,
    'allow_skipping' => 1,
    'allow_resume' => 1,
    'allow_jumping' => 0,
    'show_passed' => 1,
    'quiz_open' => _quiz_form_prepare_date(),
    'quiz_close' => _quiz_form_prepare_date(NULL, variable_get('quiz_default_close', 30)),
    'mark_doubtful' => 0,
  );
}

/**
 * Result_id, menu argument loader.
 */
function quiz_rid_load($rid) {
  return (!empty($rid) && is_numeric($rid)) ? $rid : 0;
}

/**
 * Workaround for a _menu_translate() bug.
 */
function quiz_rid_to_arg() {
  return 0;
}

/**
 * Implements hook_load().
 */
function quiz_load($nodes) {
  foreach ($nodes as $nid => &$node) {
    // Fetching defaults.
    $default_additions = _quiz_get_node_defaults();

    // Fetching node settings.
    $query = db_select('quiz_node_properties', 'qnp');
    foreach (array_keys($default_additions) as $field) {
      $query->addField('qnp', $field);
    }
    $query->condition('vid', $node->vid);
    $query->condition('nid', $node->nid);
    $fetched_additions = (array) $query->execute()->fetch();

    $additions = ($fetched_additions) ? (object) ($fetched_additions += $default_additions) : NULL;
    // Fetching result options.
    $query = db_select('quiz_node_result_options', 'qnro');
    $query->fields('qnro');
    $query->condition('nid', $node->nid);
    $query->condition('vid', $node->vid);
    $options = $query->execute();
    foreach ($options as $option) {
      $additions->resultoptions[$option->option_id] = (array) $option;
    }

    foreach ($additions as $property => &$value) {
      $node->$property = $value;
    }
  }
}

/**
 * Implements hook_view().
 */
//function quiz_view($node, $teaser = FALSE, $page = FALSE) {
function quiz_view($node, $view_mode) {
  drupal_alter('quiz_view', $node, $teaser, $page);
  node_invoke($node, 'prepare');

  // Number of questions is needed on the statistics page.
  $node->number_of_questions = $node->number_of_random_questions + _quiz_get_num_always_questions($node->vid);

  $node->content['stats'] = array(
    '#markup' => theme('quiz_view_stats', array('node' => $node)),
    '#weight' => -1,
  );

  $available = quiz_availability($node);
  if ($available === TRUE) {
    // Check the permission before displaying start button.
    if (user_access('access quiz')) {
      // Add a link to the take tab as a button if this isn't a teaser view.
      if (!$teaser) {
        $quiz_form = drupal_get_form('quiz_start_quiz_button_form', $node);
        $node->content['take'] = array(
          '#markup' => drupal_render($quiz_form),
          '#weight' => 2,
        );
      }
      // Add a link to the take tab if this is a teaser view.
      else {
        $node->content['take'] = array(
          '#markup' =>  l(t('Start quiz'), 'node/' . $node->nid . '/take'),
          '#weight' => 2,
        );
      }
    }
  }
  else {
    $node->content['take'] = array(
      '#markup' => '<div class="quiz-not-available">' . $available . '</div>',
      '#weight' => 2,
    );
  }

  return $node;
}

/**
 * Helper function for hook_view().
 *
 * Returns a button to use as a link to start taking the quiz.
 *
 * @param $form_state
 *   Form state array.
 * @param $node
 *   The quiz node.
 * @return
 *   Form with a button linking to the take tab.
 */
function quiz_start_quiz_button_form($form, &$form_state, $node) {
  $form = array();
  $form['#action'] = url("node/$node->nid/take");
  $form['button'] = array(
    '#type' => 'button',
    '#value' => t('Start quiz'),
  );
  return $form;
}

/**
 * Primary quiz-taking view on 'Take' tab.
 */
function quiz_take($node) {
  //drupal_alter('quiz_take', $node);
  if (isset($node->rendered_content)) {
    return $node->rendered_content;
  }
  $to_be_rendered = quiz_take_quiz($node);
  return drupal_render($to_be_rendered);
}

/**
 * Does the current user have access to take the quiz?
 *
 * @param $node
 *  The quiz node
 */
function quiz_take_access($node) {
  if ($node->type != 'quiz') {
    return FALSE;
  }
  return node_access('view', $node) && user_access('access quiz') && quiz_availability($node) === TRUE;
}

/**
 * Implements hook_form().
 *
 * This is an admin form used to build a new quiz. It is called as part of the
 * node edit form.
 */
function quiz_form(&$node, &$form_state) {

  $form = array();

  // We tell quiz_form_alter to check for the manual revisioning permission.
  $form['#quiz_check_revision_access'] = TRUE;

  $form['title'] = array(
    '#type' => 'textfield',
    '#title' => t('Title'),
    '#default_value' => isset($node->title) ? $node->title : '',
    '#description' => t('The name of the @quiz.', array('@quiz' => QUIZ_NAME)),
    '#required' => TRUE,
  );

  $form['taking'] = array(
    '#type' => 'fieldset',
    '#title' => t('Taking options'),
    '#collapsed' => isset($settings_loaded) ? $settings_loaded : FALSE,
    '#collapsible' => TRUE,
    '#attributes' => array('id' => 'taking-fieldset'),
    '#group' => 'additional_settings',
    '#weight' => -2,
  );
  $form['taking']['allow_resume'] = array(
    '#type' => 'checkbox',
    '#title' => t('Allow Resume'),
    '#default_value' => $node->allow_resume,
    '#description' => t('Whether to allow users to leave the @quiz incomplete and then resume it from where they left off.', array('@quiz' => QUIZ_NAME)),
  );
  $form['taking']['allow_skipping'] = array(
    '#type' => 'checkbox',
    '#title' => t('Allow Skipping questions'),
    '#default_value' => $node->allow_skipping,
    '#description' => t('Whether to allow users to skip questions in the @quiz', array('@quiz' => QUIZ_NAME)),
  );
  $form['taking']['allow_jumping'] = array(
    '#type' => 'checkbox',
    '#title' => t('Allow jumping'),
    '#default_value' => $node->allow_jumping,
    '#description' => t('Whether to allow users to jump between questions using a menu in the @quiz', array('@quiz' => QUIZ_NAME)),
  );
  $form['taking']['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['taking']['repeat_until_correct'] = array(
    '#type' => 'checkbox',
    '#title' => t('Repeat until correct'),
    '#default_value' => $node->repeat_until_correct,
    '#description' => t('Require the user to re-try the question until they answer it correctly.'),
  );
  $form['taking']['mark_doubtful'] = array(
    '#type' => 'checkbox',
    '#title' => t('Mark Doubtful'),
    '#default_value' => $node->mark_doubtful,
    '#description' => t('Allow user to mention if they are not sure about the answer'),
  );
  $form['taking']['show_passed'] = array(
    '#type' => 'checkbox',
    '#title' => t('Show passed status'),
    '#default_value' => $node->show_passed,
    '#description' => t('Show the status, if the user has previously passed'),
  );

  $form['taking']['randomization'] = array(
    '#type' => 'radios',
    '#title' => t('Randomize questions'),
    '#options' => array(
      t('No randomization'),
      t('Random order'),
      t('Random questions'),
      t('Categorized random questions'),
    ),
    '#description' => t('The difference between "random order" and "random questions" is that with "random questions" questions are drawn randomly from a pool of questions. With "random order" the quiz will always consist of the same questions. With "Categorized random questions" you can choose several terms questions should be drawn from, and you can also choose how many questions that should be drawn from each, and max score for each term.'),
    '#default_value' => $node->randomization,
  );
  $form['taking']['feedback'] = array(
    '#type' => 'fieldset',
    '#title' => t('Feedback'),
    '#collapsible' => FALSE,
    '#collapsed' => FALSE,
  );
  $form['taking']['feedback']['feedback_time'] = array(
    '#title' => t('Feedback Time'),
    '#type' => 'radios',
    '#default_value' => $node->feedback_time,
    '#options' => _quiz_get_feedback_options(),
    '#description' => t('Indicates at what point feedback for each question will be given to the user'),
  );
  $form['taking']['feedback']['display_feedback'] = array(
    '#title' => t('Display solution'),
    '#type' => 'checkbox',
    '#default_value' => $node->display_feedback,
    '#description' => t('Display the users answers and the correct answers for all questions along with the score for each question.')
  );

  // Added the action as a dropdown for selection with specific quizzes. This
  // allows you to choose a defined action from the actions module for use when
  // a user completes the quiz.
  if (user_access('assign any action to quiz events')) {
    $form['taking']['aid'] = array(
      '#title' => t('Assign Action'),
      '#description' => t('Select an action to be preformed after a user has completed this @quiz.', array('@quiz' => QUIZ_NAME)),
      '#type' => 'select',
      // @todo An idea here would be to add a system conf variable into the
      //   quiz_action_options() function that could filter the type of actions
      //   you could display on your quizzes.  For Example: you create a custom
      //   module that defines some actions that you only want a user to choose
      //   when creating a quiz and selecting an action from the dropdown.  You
      //   setup your actions with type 'quiz' and then add in that variable
      //   into the function and it will automatically filter and show only
      //   thosespecific actions.  @note:  In doing this you lose your default
      //   "Choose an Action" option.  Review actions and the
      //   quiz_action_options() function for further explaination.
      '#options' => quiz_action_options(variable_get('quiz_action_type', 'all')),
      '#default_value' => drupal_hash_base64($node->aid),
    );
  }
  $options = array(t('Unlimited'));
  for ($i = 1; $i < 10; $i++) {
    $options[$i] = $i;
  }
  $form['taking']['multiple_takes'] = array(
    '#type' => 'fieldset',
    '#title' => t('Multiple takes'),
    '#collapsible' => FALSE,
    '#collapsed' => FALSE,
    '#attributes' => array('id' => 'multiple-takes-fieldset'),
  );
  $form['taking']['multiple_takes']['takes'] = array(
    '#type' => 'select',
    '#title' => t('Allowed number of attempts'),
    '#default_value' => $node->takes,
    '#options' => $options,
    '#description' => t('The number of times a user is allowed to take the @quiz. <strong>Anonymous users are only allowed to take quizzes that allow an unlimited number of attempts.</strong>', array('@quiz' => QUIZ_NAME)),
  );
  $form['taking']['multiple_takes']['show_attempt_stats'] = array(
    '#type' => 'checkbox',
    '#title' => t('Display allowed number of attempts'),
    '#default_value' => $node->show_attempt_stats,
    '#description' => t('Display the allowed number of attempts on the starting page for this quiz.'),
  );

  if (user_access('delete any quiz results') || user_access('delete results for own quiz')) {
    $form['taking']['multiple_takes']['keep_results'] = array(
      '#type' => 'radios',
      '#title' => t('These results should be stored for each user'),
      '#options' => array(
         t('The best'),
         t('The newest'),
         t('All'),
      ),
      '#default_value' => $node->keep_results,
    );
  }
  else {
    $form['taking']['multiple_takes']['keep_results'] = array(
      '#type' => 'value',
      '#value' => $node->keep_results,
    );
  }

  if (function_exists('jquery_countdown_add') && variable_get('quiz_has_timer', 0)) {
    $form['taking']['addons'] = array(
      '#type' => 'fieldset',
      '#title' => t('Quiz Addons Properties'),
      '#collapsible' => TRUE,
      '#collapsed' => FALSE,
    );
    $form['taking']['addons']['time_limit'] = array(
      '#type' => 'textfield',
      '#title' => t(' Time Limit'),
      '#default_value' => isset($node->time_limit) ? $node->time_limit : 0,
      '#description' => t('Set the maximum allowed time in seconds for this @quiz. Use 0 for no limit.', array('@quiz' => QUIZ_NAME)),
    );
  }
  else {
    $form['taking']['addons']['time_limit'] = array(
      '#type' => 'value',
      '#value' => 0,
    );
  }

  if (function_exists('userpoints_userpointsapi') && variable_get('quiz_has_userpoints', 1)) {
    $form['has_userpoints'] = array(
      '#type' => 'checkbox',
      '#default_value' => (isset($node->has_userpoints) ? $node->has_userpoints : 1),
      '#title' => t('Enable UserPoints Module Integration'),
      '#description' => t('If checked, marks scored in this @quiz will be credited to userpoints. For each correct answer 1 point will be added to user\'s point.', array('@quiz' => QUIZ_NAME)),
    );
  }

  // Set up the availability options.
  $form['quiz_availability'] = array(
    '#type' => 'fieldset',
    '#title' => t('Availability options'),
    '#collapsed' => TRUE,
    '#collapsible' => TRUE,
    '#attributes' => array('id' => 'availability-fieldset'),
    '#group' => 'additional_settings',
  );
  $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)),
    '#after_build' => array('_quiz_limit_year_options'),
  );
  $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)),
    '#after_build' => array('_quiz_limit_year_options'),
  );

  // Quiz summary options.
  $form['summaryoptions'] = array(
    '#type' => 'fieldset',
    '#title' => t('Pass/fail options'),
    '#collapsible' => TRUE,
    '#collapsed' => TRUE,
    '#attributes' => array('id' => 'summaryoptions-fieldset'),
    '#group' => 'additional_settings',
  );
  // If pass/fail option is checked, present the form elements.
  if (variable_get('quiz_use_passfail', 1)) {
    $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.', array('@quiz' => QUIZ_NAME)),
      '#required' => FALSE,
    );
    $form['summaryoptions']['summary_pass'] = array(
      '#type' => 'text_format',
      '#base_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)),
      '#format' => isset($node->summary_pass_format) && !empty($node->summary_pass_format) ? $node->summary_pass_format : NULL,
    );
  }
  // If the pass/fail option is unchecked, use the default and hide it.
  else {
    $form['summaryoptions']['pass_rate'] = array(
      '#type' => 'hidden',
      '#value' => $node->pass_rate,
      '#required' => FALSE,
    );
  }
  // We use a helper to enable the wysiwyg module to add an editor to the
  // textarea.
  $form['summaryoptions']['helper']['summary_default'] = array(
    '#type' => 'text_format',
    '#base_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."),
    '#format' => isset($node->summary_default_format) && !empty($node->summary_default_format) ? $node->summary_default_format : NULL,
  );

  // Number of random questions, max score and tid for random questions are set on
  // the manage questions tab. We repeat them here so that they're not removed
  // if the quiz is being updated.
  $num_rand = (isset($node->number_of_random_questions)) ? $node->number_of_random_questions : 0;
  $form['number_of_random_questions'] = array(
    '#type' => 'value',
    '#value' => $num_rand,
  );
  $max_score_for_random = (isset($node->max_score_for_random)) ? $node->max_score_for_random : 0;
  $form['max_score_for_random'] = array(
    '#type' => 'value',
    '#value' => $max_score_for_random,
  );
  $tid = (isset($node->tid)) ? $node->tid : 0;
  $form['tid'] = array(
    '#type' => 'value',
    '#value' => $tid,
  );

  $options = !empty($node->resultoptions) ? $node->resultoptions : array();
  $num_options = max(count($options), variable_get('quiz_max_result_options', 5));

  if ($num_options > 0) {
    $form['resultoptions'] = array(
      '#type' => 'fieldset',
      '#title' => t('Result Comments'),
      '#collapsible' => TRUE,
      '#collapsed' => TRUE,
      '#tree' => TRUE,
      '#attributes' => array('id' => 'resultoptions-fieldset'),
      '#group' => 'additional_settings',
    );

    for ($i = 0; $i < $num_options; $i++) {
      $option = (count($options) > 0) ? array_shift($options) : NULL; // grab each option in the array
      $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'),
        '#default_value' => isset($option['option_name']) ? $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).'),
        '#default_value' => isset($option['option_start']) ? $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).'),
        '#default_value' => isset($option['option_end']) ? $option['option_end'] : '',
        '#size' => 5,
      );
      $form['resultoptions'][$i]['option_summary'] = array(
        '#type' => 'text_format',
        '#base_type' => 'textarea',
        '#title' => t('Display text for the result'),
        '#default_value' => isset($option['option_summary']) ? $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.'),
        '#format' => isset($option['option_summary_format']) ? $option['option_summary_format'] : NULL,
      );
      if (isset($option['option_id'])) {
        $form['resultoptions'][$i]['option_id'] = array(
          '#type' => 'hidden',
          '#value' => isset($option['option_id']) ? $option['option_id'] : '',
        );
      }
    }
  }
  $form['remember_settings'] = array(
    '#type' => 'checkbox',
    '#title' => t('Remember my settings'),
    '#description' => t('If this box is checked most of the quiz specific settings you have made will be remembered and will be your default settings next time you create a quiz.'),
    '#weight' => 49,
  );
  if (quiz_has_been_answered($node)) {
    $node->revision = 1;
    $node->log = t('The current revision has been answered. We create a new revision so that the reports from the existing answers stays correct.');
  }
  return $form;
}

/**
 * Implements hook_validate().
 */
function quiz_validate($node) {
  if (isset($node->nid) && !$node->nid && empty($_POST)) {
    return;
  }

  // Don't check dates if the quiz is always available.
  if (!$node->quiz_always) {
    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 (!empty($node->pass_rate)) {
    if (!_quiz_is_int($node->pass_rate, 0, 100)) {
      form_set_error('pass_rate', t('The pass rate value must be a number between 0 and 100.'));
    }
  }

  if (isset($node->time_limit)) {
    if (!_quiz_is_int($node->time_limit, 0)) {
      form_set_error('time_limit', t('Time limit must be a non negative interger'));
    }
  }

  if (isset($node->resultoptions) && count($node->resultoptions) > 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']))) {

          // Check for a number between 0-100.
          foreach (array('option_start' => 'start', 'option_end' => 'end') as $bound => $bound_text) {
            if (!_quiz_is_int($option[$bound], 0, 100)) {
              form_set_error($bound, t('The range %start value must be a number between 0 and 100.', array('%start' => $bound_text)));
            }
          }

          // Check that range end >= start.
          if ($option['option_start'] > $option['option_end']) {
            form_set_error('option_start', t('The start must be less than the end of the range.'));
          }

          // Check that range doesn't collide with any other 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);
          }
        }
      }
      elseif (!_quiz_is_empty_html($option['option_summary']['value'])) {
        form_set_error('option_summary', t('Option has a summary, but no name.'));
      }
    }
  }
  if ($node->allow_jumping && !$node->allow_skipping) {
    form_set_error('allow_skipping', t('If jumping is allowed skipping also has to be allowed.'));
  }
}

/**
 * Implementation of hook_node_presave().
 */
function quiz_node_presave($node) {
  if ($node->type == 'quiz') {
    // Convert the action id to the actual id from the MD5 hash.
    // Why the actions module does this I do not know? Maybe to prevent
    // invalid values put into the options value="" field.
    if (!empty($node->aid) && $aid = actions_function_lookup($node->aid)) {
      $node->aid = $aid;
    }

    if (variable_get('quiz_auto_revisioning', 1)) {
      $node->revision = (quiz_has_been_answered($node)) ? 1 : 0;
    }
  }
  if (isset($node->is_quiz_question) && variable_get('quiz_auto_revisioning', 1)) {
    $node->revision = (quiz_question_has_been_answered($node)) ? 1 : 0;
  }
}

/**
 * Implementation of hook_node_prepare().
 */
function quiz_node_prepare($node) {
  if ($node->type == 'quiz' && !isset($node->nid)) {
    // If this is a new node we apply the user defaults for the quiz settings.
    $settings = _quiz_load_user_settings();
    if (!$settings) {
      if (arg(0) == 'node') {
        drupal_set_message(t('You are making your first @quiz. On this page you set the attributes, most of which you may tell the system to remember as defaults for the future. On the next screen you can add questions.',
        array('@quiz' => QUIZ_NAME) ));
      }
      $settings = _quiz_load_user_settings(variable_get('quiz_def_uid', 1));
    }
    $settings += _quiz_get_node_defaults();
    foreach ($settings as $key => $value) {
      if (!isset($node->$key)) {
        $node->$key = $value;
      }
    }
  }

  if (isset($node->is_quiz_question)) {
    if (variable_get('quiz_auto_revisioning', 1)) {
      $node->revision = (quiz_question_has_been_answered($node)) ? 1 : 0;
    }
  }
}

/**
 * Implements hook_user_cancel().
 */
function quiz_user_cancel($edit, $account, $method) {
  db_delete('quiz_user_settings')
    ->condition('uid', $account->uid)
    ->execute();
  if (variable_get('quiz_durod', 0)) {
    _quiz_delete_users_results($account->uid);
  }
}

/**
 * Deletes all results associated with a given user.
 *
 * @param int $uid
 *  The users id
 */
function _quiz_delete_users_results($uid) {
  $res = db_query("SELECT result_id FROM {quiz_node_results} WHERE uid = :uid", array(':uid' => $uid));
  $rids = array();
  while ($rid = $res->fetchField()) {
    $rids[] = $rid;
  }
  quiz_delete_results($rids);
}

/**
 * @defgroup quiz_public Public API functions.
 * @{
 */

/**
 * Load a quiz node, cache it and validate that it is indeed of type quiz.
 *
 * @param $arg
 *   The Node ID.
 * @return
 *   A quiz node object or FALSE if a load failed.
 */
function quiz_type_access_load($arg) {
  static $quiz_nodes = array();
  if (isset($quiz_nodes[$arg])) {
    return $quiz_nodes[$arg];
  }
  $to_return = (($node = node_load($arg)) && $node->type == 'quiz') ? $node : FALSE;
  if ($to_return) {
    $quiz_nodes[$arg] = $to_return;
  }
  return $to_return;
}

/**
 * Validate that a node is of type quiz, and that the user has access to it.
 *
 * @param $node
 * @param $op
 * @return unknown_type
 */
function quiz_type_confirm($node, $op = NULL) {
  if ($node->type != 'quiz') {
    return FALSE;
  }
  if (isset($op)) {
    $node_ops = array('view', 'update', 'delete', 'create');
    if (in_array($op, $node_ops)) {
      return node_access($op, $node);
    }
    return user_access($op);
  }

  return user_access('access quiz');
}

/**
 * Returns the titles for all quizzes the user has access to.
 *
 * @return quizzes
 *   Array with nids as keys and titles as values.
 */
function quiz_get_all_titles() {
  return db_select('node', 'n')
    ->fields('n', array('nid', 'title'))
    ->condition('n.type', 'quiz')
    ->addTag('node_access')
    ->execute()
    ->fetchAllKeyed();
}

/**
 * Returns the titles for all quizzes the user has access to.
 *
 * @return quizzes
 *   Array with nids as keys and (array with vid as key and title as value) as values.
 *   Like this: array($nid => array($vid => $title))
 */
function quiz_get_all_version_titles() {
  $query = db_select('node', 'n');
  $query->join('node_revision', 'nr', 'nr.nid = n.nid');
  $query->fields('nr', array('nid', 'vid', 'title'))
    ->condition('n.type', 'quiz')
    ->execute();

  $to_return = array();
  while ($res_o = $query->fetch()) {
    $to_return[$res_o->nid][$res_o->vid] = $res_o->title;
  }
  return $to_return;
}

/**
 * Return highest score data for given quizzes.
 *
 * @param $nids
 *   nids for the quizzes we want to collect scores from.
 * @param $uid
 *   uid for the user we want to collect score for.
 * @param $include_num_questions
 *   Do we want to collect information about the number of questions in a quiz?
 *   This adds a performance hit.
 * @return
 *   Array of score data.
 *   For several takes on the same quiz, only returns highest score.
 */
function quiz_get_score_data($nids, $uid, $include_num_questions = FALSE) {
  // Validate that the nids are integers.
  foreach ($nids as $key => $nid) {
    if (!_quiz_is_int($nid)) {
      unset($nids[$key]);
    }
  }
  if (empty($nids)) {
    return array();
  }

  // Fetch score data for the validated nids.
  $to_return = array();
  $vids = array();
  $sql = 'SELECT n.title, n.nid, n.vid, p.number_of_random_questions as num_random_questions, r.score AS percent_score, p.max_score, p.pass_rate AS percent_pass
          FROM {node} n
          JOIN {quiz_node_properties} p
          ON n.vid = p.vid
          LEFT OUTER JOIN {quiz_node_results} r
          ON r.nid = n.nid AND r.uid = :uid
          LEFT OUTER JOIN (
            SELECT nid, max(score) as highest_score
            FROM {quiz_node_results}
            GROUP BY nid
          ) rm
          ON n.nid = rm.nid AND r.score = rm.highest_score
          WHERE n.nid in (' . implode(', ', $nids) . ')
          ';
  $res = db_query($sql, array(':uid' => $uid));
  foreach ($res as $res_o) {
    if (!$include_num_questions) {
      unset($res_o->num_random_questions);
    }
    if (!isset($to_return[$res_o->vid]) || $res_o->percent_score > $to_return[$res_o->vid]->percent_score) {
      $to_return[$res_o->vid] = $res_o; // Fetch highest score
    }
    // $vids will be used to fetch number of questions.
    $vids[] = $res_o->vid;
  }
  if (empty($vids)) {
    return array();
  }

  // Fetch total number of questions.
  if ($include_num_questions) {
    $res = db_query('SELECT COUNT(*) AS num_always_questions, parent_vid
            FROM {quiz_node_relationship}
            WHERE parent_vid IN (' . implode(', ', $vids) . ')
            AND question_status = ' . QUESTION_ALWAYS . '
            GROUP BY parent_vid');
    foreach ($res as $res_o) {
      $to_return[$res_o->parent_vid]->num_questions = $to_return[$res_o->parent_vid]->num_random_questions + $res_o->num_always_questions;
    }
  }

  return $to_return;
}

/**
 * Finds out the number of questions for the quiz.
 *
 * Good example of usage could be to calculate the % of score.
 *
 * @param $nid
 *   Quiz ID.
 * @return
 *   Returns the number of quiz questions.
 */
function quiz_get_number_of_questions($vid) {
  $always_count = _quiz_get_num_always_questions($vid);
  $rand_count = db_query('SELECT number_of_random_questions FROM {quiz_node_properties} WHERE vid = :vid', array(':vid' => $vid))->fetchField();
  return $always_count + (int) $rand_count;
}

/**
 * Finds out the pass rate for the quiz.
 *
 * @param $nid
 *   The quiz ID.
 * @return
 *   Returns the passing percentage of the quiz.
 */
function quiz_get_pass_rate($nid, $vid) {
  return db_query('SELECT pass_rate FROM {quiz_node_properties} WHERE nid = :nid AND vid = :vid', array(':nid' => $nid, ':vid' => $vid))->fetchField();
}

/**
 * Copies quiz-question relation entries in the quiz_node_relationship table
 * from an old version of a quiz to a new.
 *
 * @param $old_quiz_vid
 *   The quiz vid prior to a new revision.
 * @param $new_quiz_vid
 *   The quiz vid of the latest revision.
 * @param $quiz_nid
 *   The quiz node id.
 */
function quiz_update_quiz_question_relationship($old_quiz_vid, $new_quiz_vid, $quiz_nid) {
  // query for questions in previous version
  $result = db_select('quiz_node_relationship', 'qnr')
    ->fields('qnr', array('parent_nid','child_nid', 'child_vid', 'question_status', 'weight', 'max_score', 'auto_update_max_score'))
    ->condition('parent_nid', $quiz_nid)
    ->condition('parent_vid', $old_quiz_vid)
    ->condition('question_status', QUESTION_NEVER, '!=')
    ->execute();
  // only proceed if query returned data
  if ($result->rowCount()) {
    $insert_query = db_insert('quiz_node_relationship')
      ->fields(array('parent_nid', 'parent_vid', 'child_nid', 'child_vid', 'question_status', 'weight', 'max_score', 'auto_update_max_score'));
    while ($quiz_question = $result->fetchAssoc()) {
      $insert_query->values(array(
        'parent_nid' => $quiz_nid,
        'parent_vid' => $new_quiz_vid,
        'child_nid' => $quiz_question['child_nid'],
        'child_vid' => $quiz_question['child_vid'],
        'question_status' => $quiz_question['question_status'],
        'weight' => $quiz_question['weight'],
        'max_score' => $quiz_question['max_score'],
        'auto_update_max_score' => $quiz_question['auto_update_max_score'],
      ));
    }
    $insert_query->execute();
  }

  /* Update terms if any */
  $result = db_select('quiz_terms', 'qt')
    ->fields('qt', array('nid', 'tid', 'weight', 'max_score', 'number'))
    ->condition('vid', $old_quiz_vid)
    ->execute();
  // only proceed if query returned data
  if ($result->rowCount()) {
    $insert_query = db_insert('quiz_terms')
      ->fields(array('nid', 'vid', 'tid', 'weight', 'max_score', 'number'));
    while ($quiz_term = $result->fetchAssoc()) {
      $insert_query->values(array(
        'nid' => $quiz_nid,
        'vid' => $new_quiz_vid,
        'tid' => $quiz_term['tid'],
        'weight' => $quiz_term['weight'],
        'max_score' => $quiz_term['max_score'],
        'number' => $quiz_term['number'],
      ));
    }
    $insert_query->execute();
  }

  /*$sql = "INSERT INTO {quiz_terms} (nid, vid, tid, weight, max_score, number)
          SELECT qt.nid, %d, qt.tid, qt.weight, qt.max_score, qt.number
          FROM {quiz_terms} qt
          WHERE qt.vid = %d";*/
}


/**
 * Handles quiz taking.
 *
 * This gets executed when the main quiz node is first loaded.
 *
 * @param $quiz
 *   The quiz node.
 *
 * @return
 *   Content array.
 */
function quiz_take_quiz($quiz) {
  global $user;
  $allow_skipping = $quiz->allow_skipping;

  if (!isset($quiz)) {
    drupal_not_found();
    return;
  }

  // If anonymous user and no unique hash, refresh with a unique string to
  // prevent caching.
  if (!$user->uid && arg(4) != NULL) {
    drupal_goto('node/'. $quiz->nid .'/take/'. md5(mt_rand() . time()));
  }

  // Make sure we use the same revision of the quiz throughout the quiz taking
  // session.
  if (isset($_SESSION['quiz_' . $quiz->nid]['quiz_vid']) && $quiz->vid != $_SESSION['quiz_' . $quiz->nid]['quiz_vid'] && variable_get('quiz_auto_revisioning')) {
    $quiz = node_load($quiz->nid, $_SESSION['quiz_' . $quiz->nid]['quiz_vid']);
  }

  // If the session has no data for this quiz.
  if (!isset($_SESSION['quiz_' . $quiz->nid]['quiz_questions'])) {
    // We delete questions in progress from old revisions.
    _quiz_delete_old_in_progress($quiz, $user->uid);

    // See if the current user has progress for this revision of the quiz stored
    // in the database
    $rid = $user->uid > 0 ? _quiz_active_result_id($user->uid, $quiz->nid, $quiz->vid) : 0;

    // Are we resuming an in-progress quiz?
    if ($quiz->allow_resume && $rid > 0) {
      _quiz_resume_existing_quiz($quiz, $user->uid, $rid);

    }

    // First time running through quiz.
    elseif (quiz_start_check($quiz, $rid)) {
      _quiz_take_quiz_init($quiz);

    }

    else {
      return array('body' => array('#markup' => t('This quiz is closed')));
    }
  }

  $q_passed_validation = FALSE;

  if (quiz_availability($quiz) !== TRUE) {
    drupal_set_message(t('This quiz is not available anymore.'), 'error');
    return array('body' => array('#markup' => t('This quiz is closed')));
  }

  if (isset($_SESSION['quiz_' . $quiz->nid]['question_duration'])) {
    $_SESSION['quiz_' . $quiz->nid]['question_duration'] -= REQUEST_TIME - $_SESSION['quiz_' . $quiz->nid]['question_start_time'];
  }

  if (!isset($_POST['op'])) {
    // @todo Starting new quiz... Do we need to show instructions here?
  }
  elseif (isset($_POST['question_nid']) &&  ($_POST['question_nid'] != $_SESSION['quiz_' . $quiz->nid]['quiz_questions'][0]['nid'])) {
    // The user has pressed the navigation buttons multiple times...
  }
  elseif (isset($_SESSION['quiz_' . $quiz->nid]['question_duration']) && $_SESSION['quiz_' . $quiz->nid]['question_duration'] < -2) {
    // Timed quiz where the time has gone out 2 seconds ago. Do not store the users results...
  }
  // Workaround to show question and feedback in separage pages, when the
  // feedback time it set to show after each questions
  elseif ($_POST['op'] == t('Next question')) {
    drupal_goto("node/{$quiz->nid}/take");
  }
  // We maintain two lists: previous questions and upcomming questions.
  // When we go backward, we pop one from the previous and prepend it to the
  // upcomming.
  // @todo This can be maintained more efficiently with a single array of all
  //   questions and then a pointer to the current question. That makes
  //   rewinding much easier.
  // Check for answer submission.
  elseif ($_POST['op'] == t('Finish') || $_POST['op'] == t('Next') || $_POST['op'] == t('Back') && $quiz->backwards_navigation) {
    // Previous quiz questions: Questions that have been asked already. We save
    // a record of all of them so that a user can navigate backward all the way
    // to the beginning of the quiz.
    $_SESSION['quiz_' . $quiz->nid]['quiz_questions'][0]['rid'] = $_SESSION['quiz_' . $quiz->nid]['result_id'];
    $_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($former_question_array['nid'], $former_question_array['vid']);

    // Call hook_evaluate_question().
    $types = _quiz_get_question_types();
    $module = $types[$former_question->type]['module'];
    $result = module_invoke($module, 'evaluate_question', $former_question, $_SESSION['quiz_' . $quiz->nid]['result_id']);
    $q_passed_validation = $result->is_valid;
    $check_jump = TRUE;
    if ($q_passed_validation === TRUE) {
      quiz_store_question_result($quiz, $result, array('set_msg' => TRUE, 'question_data' => $former_question_array));
    }
    elseif ($quiz->allow_jumping && _quiz_is_int($_POST['jump_to_question'])) {
      $_POST['op'] = t('Leave blank');
      $allow_skipping = TRUE;
      $jumping = TRUE;
    }

    // Stash feedback in the session, since the $_POST gets cleared.
    if ($quiz->feedback_time == QUIZ_FEEDBACK_QUESTION && $_POST['op'] != t('Back') && $q_passed_validation === TRUE) {
      // Invoke hook_get_report().
      $report = module_invoke($module, 'get_report', $former_question_array['nid'], $former_question_array['vid'], $_SESSION['quiz_' . $quiz->nid]['result_id']);
      $path = drupal_get_path('module', 'quiz');
      require_once DRUPAL_ROOT . '/' . $path . '/quiz.pages.inc';
      if ($report) {
        $report_form = drupal_get_form('quiz_report_form', array($report), TRUE, TRUE, TRUE);
        $report_form['op'] = array(
          '#type' => 'submit',
          '#value' => t('Next question'),
        );
        return $report_form;
      }
    }

    if ($quiz->repeat_until_correct && $_POST['op'] != t('Back') && $q_passed_validation === TRUE) {
      // If the question was answered incorrectly, repeat it
      if ($result && !$result->is_correct && $result->is_evaluated) {
        $last_q = array_pop($_SESSION['quiz_' . $quiz->nid]['previous_quiz_questions']);
        array_unshift($_SESSION['quiz_' . $quiz->nid]['quiz_questions'], $last_q);
        drupal_set_message(t('The answer was incorrect. Please try again.'), 'error');
        unset($_SESSION['quiz_' . $quiz->nid]['feedback']);
      }
    }
    elseif ($_POST['op'] == t('Back') && $quiz->backwards_navigation) {
      $quiz_id = 'quiz_' . $quiz->nid;
      // We jump back two times. From the next question to the current, and then
      // from the current to the previous.
      for ($i = 0; $i < 2; $i++) {
        $last_q = array_pop($_SESSION[$quiz_id]['previous_quiz_questions']);
        array_unshift($_SESSION[$quiz_id]['quiz_questions'], $last_q);
      }
    }
    // If anonymous user, refresh url with unique hash to prevent caching.
    if (!$user->uid   && $q_passed_validation === TRUE) {
      drupal_goto('node/' . $quiz->nid . '/take', array('query' => array('quizkey' => md5(mt_rand() . REQUEST_TIME))));
    }
  }
  // Check for a skip.
  if (isset($_POST['op']) && ($_POST['op'] == t('Leave blank') || $_POST['op'] == t('Leave blank and finish')) && $allow_skipping) {

    if (!isset($_SESSION['quiz_'. $quiz->nid]['result_id'])) {
      $_SESSION['quiz_'. $quiz->nid]['result_id'] = quiz_create_rid($quiz);
    }
    $q_passed_validation = TRUE;
    // Advance the question.
    if (!isset($jumping) || (isset($jumping) && !$jumping)) {
      $_SESSION['quiz_' . $quiz->nid]['previous_quiz_questions'][] = $_SESSION['quiz_' . $quiz->nid]['quiz_questions'][0];

      // Load the last asked question.
      $former_question_array = array_shift($_SESSION['quiz_' . $quiz->nid]['quiz_questions']);
      $former_question = node_load($former_question_array['nid'], $former_question_array['vid']);
    }
    // Call hook_skip_question().
    $module = quiz_question_module_for_type($former_question->type);
    if (!$module) {
      return array('body' => array('#markup' => ' '));
    }
    $result = module_invoke($module, 'skip_question', $former_question, $_SESSION['quiz_' . $quiz->nid]['result_id']);


    // Store that the question was skipped:
    quiz_store_question_result($quiz, $result, array('set_msg' => TRUE, 'question_data' => $former_question_array));
  }

  if (isset($check_jump) && $check_jump) {
    if ($quiz->allow_jumping && _quiz_is_int($_POST['jump_to_question'])) {
      quiz_jump_to($_POST['jump_to_question'], $quiz, $_SESSION['quiz_' . $quiz->nid]['result_id']);
    }
  }
  $show_validation_message = FALSE;

  // If this quiz is in progress, load the next questions and return it via the theme.
  if (!empty($_SESSION['quiz_' . $quiz->nid]['quiz_questions']) || is_string($q_passed_validation)) {

    // If we got no error when validating the question
    if (!is_string($q_passed_validation) || $_POST['op'] == t('Back') && $quiz->backwards_navigation) {
      $question_node = node_load(
        $_SESSION['quiz_' . $quiz->nid]['quiz_questions'][0]['nid'],
        $_SESSION['quiz_' . $quiz->nid]['quiz_questions'][0]['vid']
      );
      if (isset($_SESSION['quiz_' . $quiz->nid]['quiz_questions'][0]['rid'])) {
        $question_node->rid = $_SESSION['quiz_' . $quiz->nid]['quiz_questions'][0]['rid'];
      }
      // We got an error message when trying to validate the previous answer
    }
    else {
      $question_node = $former_question;
      $show_validation_message = TRUE;
      array_unshift($_SESSION['quiz_'. $quiz->nid]['quiz_questions'], $former_question_array);
      if (is_array($_SESSION['quiz_'. $quiz->nid]['previous_quiz_questions'])) {
        array_pop($_SESSION['quiz_'. $quiz->nid]['previous_quiz_questions']);
      }
      // Avoid caching for anonymous users
      if (!$user->uid) {
        drupal_set_message($q_passed_validation, 'error');
        drupal_goto('node/' . $quiz->nid . '/take', array('query' => array('quizkey' => md5(mt_rand() . REQUEST_TIME))));
      }
    }

    // Added the progress info to the view.
    $number_of_questions = quiz_get_number_of_questions($quiz->vid);

    $question_number = $number_of_questions - count($_SESSION['quiz_' . $quiz->nid]['quiz_questions']);
    $question_node->question_number = $question_number;
    $content['progress']['#markup'] = theme('quiz_progress', array(
      'question_number' => $question_number,
      'num_questions' => $number_of_questions,
      'allow_jumping' => $quiz->allow_jumping,
      'time_limit' => $quiz->time_limit));
    $content['progress']['#weight'] = -50;
    if (count($_SESSION['quiz_' . $quiz->nid]['quiz_questions']) + count($_SESSION['quiz_' . $quiz->nid]['previous_quiz_questions']) > $number_of_questions) {
      drupal_set_message(t('At least one question have been deleted from the quiz after you started taking it. You will have to start over.'), 'warning', FALSE);
      unset($_SESSION['quiz_' . $quiz->nid]);
      drupal_goto('node/' . $quiz->nid . '/take');
    }

    if (isset($_SESSION['quiz_' . $quiz->nid]['question_duration'])) {
      $time = $_SESSION['quiz_' . $quiz->nid]['question_duration'];
      if ($time < 1 && $time > -2) {
        // The page was probably submitted by the js, we allow the data to be stored
        $time = 1;
      }
      db_update('quiz_node_results')
        ->fields(array('time_left' => $time))
        ->condition('result_id', $_SESSION['quiz_' . $quiz->nid]['result_id'])
        ->execute();

      if ($time <= 1) {
        // Quiz has been timed out, run a loop to mark the remaining questions
        // as skipped.
        quiz_jump_to(count($_SESSION['quiz_' . $quiz->nid]['quiz_questions']) + count($_SESSION['quiz_' . $quiz->nid]['previous_quiz_questions']) + 1, $quiz, $_SESSION['quiz_' . $quiz->nid]['result_id']);
        $quiz_end = TRUE;
        unset($content['progress']);
        $show_validation_message = FALSE;
        drupal_set_message(t('You have run out of time.'), 'error');
      }
      else {
        // There is still time left, so let's go ahead and insert the countdown
        // javascript.
        if (function_exists('jquery_countdown_add') && variable_get('quiz_has_timer', 1)) {
          jquery_countdown_add('.countdown', array('until' => $time, 'onExpiry' => 'finished', 'compact' => TRUE, 'layout' => t('Time left') . ': {hnn}{sep}{mnn}{sep}{snn}'));
          // These are the two button op values that are accepted for answering
          // questions.
          $button_op1 = drupal_json_encode(t('Finish'));
          $button_op2 = drupal_json_encode(t('Next'));
          $js = "
            function finished() {
              // Find all buttons with a name of 'op'.
              var buttons = jQuery('input[type=submit][name=op], button[type=submit][name=op]');
              // Filter out the ones that don't have the right op value.
              buttons = buttons.filter(function() {
                return this.value == $button_op1 || this.value == $button_op2;
              });
              if (buttons.length == 1) {
                // Since only one button was found, this must be it.
                buttons.click();
              }
              else {
                // Zero, or more than one buttons were found; fall back on a page refresh.
                window.location = window.location.href;
              }
            }
          ";
          drupal_add_js($js, array('type' => 'inline', 'scope' => JS_DEFAULT));
        }
      }
      $_SESSION['quiz_' . $quiz->nid]['question_start_time'] = REQUEST_TIME;
    }
    if ($show_validation_message) {
      drupal_set_message($q_passed_validation, 'error');
    }

    // If we're not yet at the end.
    if (empty($quiz_end)) {
      $content['body']['question']['#markup'] = quiz_take_question_view($question_node, $quiz);
      $content['body']['question']['#weight'] = 0;
      // If we had feedback from the last question.
      if (isset($_SESSION['quiz_' . $quiz->nid]['feedback']) && $quiz->feedback_time == QUIZ_FEEDBACK_QUESTION) {
        $content['body']['feedback']['#markup'] = rawurldecode($_SESSION['quiz_' . $quiz->nid]['feedback']);
        $content['body']['feedback']['#weight'] = -100;
      }
      drupal_set_title($quiz->title);
      unset($_SESSION['quiz_' . $quiz->nid]['feedback']);
    }
  }
  // Else we're at the end of quiz.
  else {
    drupal_set_title($quiz->title);
    $quiz_end = TRUE;
  }

  // If we're at the end of the quiz.
  if (!empty($quiz_end)) {
    // IMPORTANT: Because of a bug _quiz_get_answers always have to be called before quiz_end_scoring... :/
    $questions = _quiz_get_answers($quiz, $_SESSION['quiz_' . $quiz->nid]['result_id']);
    $score = quiz_end_scoring($quiz, $_SESSION['quiz_' . $quiz->nid]['result_id']);
    if ($quiz->feedback_time == QUIZ_FEEDBACK_NEVER) {
      $content['body']['#markup'] = theme('quiz_no_feedback');
    }
    else {
      // Get the results and summary text for this quiz.
      $summary = _quiz_get_summary_text($quiz, $score);
      $data = array(
        'quiz' => $quiz,
        'questions' => $questions,
        'score' => $score,
        'summary' => $summary,
        'rid' => $_SESSION['quiz_' . $quiz->nid]['result_id'],
      );
      // Get the themed summary page.
      $content['body']['#markup'] = theme('quiz_take_summary', $data);
    }
    if ($score['is_evaluated']) {
      _quiz_maintain_results($quiz, $_SESSION['quiz_' . $quiz->nid]['result_id']);
    }

    // Remove session variables, save $rid
    $session_data = $_SESSION['quiz_' . $quiz->nid];
    unset($_SESSION['quiz_' . $quiz->nid]);
    // NOTE: End actions might redirect the user somewhere. Code below this line might not get executed...
    quiz_end_actions($quiz, $score, $session_data);
  }

  return $content;
}

/**
 * Set the current session to jump to a specific question number
 *
 * @param int $question_num
 *  The question number we want to jump to
 */
function quiz_jump_to($question_num, $quiz, $rid) {
  $num_next = count($_SESSION['quiz_' . $quiz->nid]['quiz_questions']);
  $num_previous = count($_SESSION['quiz_' . $quiz->nid]['previous_quiz_questions']);
  if ($question_num > $num_next + $num_previous + 1) {
    // Trying to jump too far. We allow skipping all questions because of jQuery countdown...
    return;
  }
  if ($question_num <= $num_previous) {
    for ($i = 0; $i < $num_previous - $question_num + 1; $i++) {
      array_unshift($_SESSION['quiz_' . $quiz->nid]['quiz_questions'], array_pop($_SESSION['quiz_' . $quiz->nid]['previous_quiz_questions']));
    }
  }
  elseif ($question_num > $num_previous + 1) {
    for ($i = 0; $i < $question_num - $num_previous - 1; $i++) {
      $_SESSION['quiz_' . $quiz->nid]['previous_quiz_questions'][] = array_shift($_SESSION['quiz_' . $quiz->nid]['quiz_questions']);
    }
    _quiz_cleanup_after_jumping($quiz, $rid);
  }
}

function _quiz_take_quiz_init($quiz) {
  // Create question list.
  $questions = quiz_build_question_list($quiz);

  if ($questions === FALSE) {
    drupal_set_message(t('Not enough random questions were found. Please add more questions before trying to take this @quiz.',
      array('@quiz' => QUIZ_NAME)), 'error');
    return array('body' => array('#value' => ' '));
  }

  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(t('assign questions'), 'node/' . arg(1) . '/questions'))), 'error');
    return array('body' => array('#value' => t('Please assign questions...')));
  }

  // Initialize session variables.
  $_SESSION['quiz_' . $quiz->nid]['result_id'] = quiz_create_rid($quiz);
  $_SESSION['quiz_' . $quiz->nid]['quiz_questions'] = $questions;
  $_SESSION['quiz_' . $quiz->nid]['previous_quiz_questions'] = array();
  $_SESSION['quiz_' . $quiz->nid]['question_number'] = 0;
  $_SESSION['quiz_' . $quiz->nid]['question_start_time'] = REQUEST_TIME;
  $_SESSION['quiz_' . $quiz->nid]['quiz_vid'] = $quiz->vid;

  if ($quiz->time_limit > 0) {
    $_SESSION['quiz_' . $quiz->nid]['question_duration'] = $quiz->time_limit;
  }
}

/**
 * Create the view for a question the user is about to take.
 *
 * @param $question_node
 *   The question node that should be rendered.
 * @param $quiz_node
 *   The quiz node.
 * @return
 *   A string containing the body of the node.
 */
function quiz_take_question_view($question_node, $quiz_node) {
  $question_node->allow_skipping = $quiz_node->allow_skipping;
  node_build_content($question_node, 'teaser');
  module_invoke_all('node_build_alter', $question_node, FALSE);
  //$question_node->body = drupal_render($question_node->content);
  return theme('quiz_single_question_node', array('question_node' => $question_node));
}

/**
 * Store a quiz question result.
 *
 * @param $quiz
 *  The quiz node
 * @param $result
 *  Object with data about the result for a question.
 * @param $options
 *  Array with options that affect the behavior of this function.
 *  ['set_msg'] - Sets a message if the last question was skipped.
 */
function quiz_store_question_result($quiz, $result, $options) {
  //drupal_set_message('<pre>'. print_r($result, 1) .'</pre>');
  if (isset($result->is_skipped) && $result->is_skipped == TRUE) {
    if ($options['set_msg']) {
      drupal_set_message(t('Last question skipped.'), 'status');
    }
    $result->is_correct = FALSE;
    $result->score = 0;
  }
  else {
    // Make sure this is set.
    $result->is_skipped = FALSE;
  }
  if (!isset($result->score)) {
    $result->score = $result->is_correct ? 1 : 0;
  }

  // Points are stored pre-scaled in the quiz_node_results_answers table. We get the scale.
  if ($quiz->randomization < 2) {
    $scale = db_query("SELECT (max_score / (
                  SELECT max_score
                  FROM {quiz_question_properties}
                  WHERE nid = :nid AND vid = :vid
                )) as scale
                FROM {quiz_node_relationship}
                WHERE parent_nid = :parent_nid
                AND parent_vid = :parent_vid
                AND child_nid = :child_nid
                AND child_vid = :child_vid
               ", array(':nid' => $result->nid, ':vid' => $result->vid, ':parent_nid' => $quiz->nid, ':parent_vid' => $quiz->vid, ':child_nid' => $result->nid, ':child_vid' => $result->vid))->fetchField();
  }
  elseif ($quiz->randomization == 2) {
    $scale = db_query("SELECT (max_score_for_random / (
                  SELECT max_score
                  FROM {quiz_question_properties}
                  WHERE nid = :nid AND vid = :vid
                )) as scale
                FROM {quiz_node_properties}
                WHERE vid = :vid
               ", array(':nid' => $result->nid, ':vid' => $result->vid, ':vid' => $quiz->vid))->fetchField();
  }
  elseif ($quiz->randomization == 3) {
    if (isset($options['question_data']['tid'])) {
      $result->tid = $options['question_data']['tid'];
    }
    $scale = db_query("SELECT (max_score / (
                  SELECT max_score
                  FROM {quiz_question_properties}
                  WHERE nid = :nid AND vid = :vid
                )) as scale
                FROM {quiz_terms}
                WHERE vid = :vid
                AND tid = :tid
               ", array(':nid' => $result->nid, ':vid' => $result->vid, ':vid' => $quiz->vid, ':tid' => $result->tid))->fetchField();
  }
  $points = round($result->score * $scale);
  // Insert result data, or update existing data.
  $rid_count = db_query("SELECT COUNT('result_id') AS count
              FROM {quiz_node_results_answers}
              WHERE question_nid = :question_nid
              AND question_vid = :question_vid
              AND result_id = :result_id", array(':question_nid' => $result->nid, ':question_vid' => $result->vid, ':result_id' => $result->rid))->fetchField();
  if ($rid_count > 0) {
    $update = db_update('quiz_node_results_answers')
      ->fields(array(
        'is_correct' => (int)$result->is_correct,
        'points_awarded' => $points,
        'answer_timestamp' => REQUEST_TIME,
        'is_skipped' => (int) $result->is_skipped,
        'is_doubtful' => (int) $result->is_doubtful,
        'tid' => ($quiz->randomization == 3 && $result->tid) ? $result->tid : 0,
      ));

    $args = array($result->is_correct, $points, REQUEST_TIME, $result->is_skipped);
    $update->condition('question_nid', $result->nid);
    $update->condition('question_vid', $result->vid);
    $update->condition('result_id', $result->rid);
    $update->execute();
  }
  else {
     $insert = db_insert('quiz_node_results_answers')
      ->fields(array('question_nid', 'question_vid', 'result_id', 'is_correct', 'points_awarded', 'answer_timestamp', 'is_skipped', 'is_doubtful', 'number', 'tid'))
      ->values(array(
        'question_nid' => $result->nid,
        'question_vid' => $result->vid,
        'result_id' => $result->rid,
        'is_correct' => (int) $result->is_correct,
        'points_awarded' => $points,
        'answer_timestamp' => REQUEST_TIME,
        'is_skipped' => (int) $result->is_skipped,
        'is_doubtful' => (int) $result->is_doubtful,
        'number' => $options['question_data']['number'],
        'tid' => ($quiz->randomization == 3 && $result->tid) ? $result->tid : 0,
      ))
      ->execute();
  }
}

/**
 * Score a completed quiz.
 */
function quiz_end_scoring($quiz, $rid) {
  global $user;
  $score = quiz_calculate_score($quiz, $rid);
  if (!isset($score['percentage_score'])) {
    $score['percentage_score'] = 0;
  }
  db_update('quiz_node_results')
    ->fields(array(
      'is_evaluated' => $score['is_evaluated'],
      'time_end' => REQUEST_TIME,
      'score' => $score['percentage_score'],
    ))
    ->condition('result_id', $rid)
    ->execute();
  if ($user->uid) {
    $score['passing'] = quiz_is_passed($user->uid, $quiz->nid, $quiz->vid);
  }
  else {
    $score['passing'] = $score['percentage_score'] >= $quiz->pass_rate;
  }
  return $score;
}

/**
 * Actions to take at the end of a quiz
 *
 * @param $quiz
 *  The quiz node
 * @param $rid
 *  Result id
 * @param $score
 *  Score as a number
 */
function quiz_end_actions($quiz, $score, $session_data) {
  // Call hook_quiz_finished().
  module_invoke_all('quiz_finished', $quiz, $score, $session_data);

  // Lets piggy back here to perform the quiz defined action since were done
  // with this quiz.
  // We will verify that there is an associated action with this quiz and then
  // perform that action.
  if (!empty($quiz->aid)) {
    // Some actions are reliant on objects and I am unsure which ones, for now I
    // have simply passed the actions_do() function an empty array. By passing
    // this function a single id then it will retrieve the callback, get the
    // parameters and perform that function (action) for you.
    actions_do($quiz->aid, $quiz, $score, $session_data);
  }

  return $score;
}

/**
 * Implements hook_quiz_finished().
 *
 * Performs actions like sending quiz results over email at the end of quiz.
 */
function quiz_quiz_finished($quiz, $score, $session_data) {
  $rid = $session_data['result_id'];
  // Load data about the quiz taker
  $sql = 'SELECT u.uid, u.mail FROM {users} u JOIN {quiz_node_results} qnr ON u.uid = qnr.uid WHERE result_id = %d';
  $taker = db_query('SELECT u.uid, u.mail FROM {users} u JOIN {quiz_node_results} qnr ON u.uid = qnr.uid WHERE result_id = :result_id', array(':result_id' => $rid))->fetch();

  if (variable_get('quiz_results_to_quiz_author', 0)) {
    $author_mail = db_query('SELECT mail FROM {users} WHERE uid = :uid', array(':uid' => $quiz->uid))->fetchField();
    drupal_mail('quiz', 'notice', $author_mail, NULL, array($quiz, $score, $rid, 'author'));
  }

  if (variable_get('quiz_email_results', 0) && variable_get('quiz_use_passfail', 1) && $taker->uid != 0 && $score['is_evaluated']) {
    drupal_mail('quiz', 'notice', $taker->mail, NULL, array($quiz, $score, $rid, 'taker'));
    drupal_set_message(t('Your results have been sent to your e-mail address.'));
  }

  // Calls userpoints functions to credit user point based on number of correct
  // answers.
  if ($quiz->has_userpoints && $taker->uid != 0 && $score['is_evaluated']) {

    //Looking up the tid of the selected Userpoint vocabulary
    $selected_tid = db_query("SELECT tid FROM {taxonomy_index}
                WHERE nid = :nid AND tid IN (
                  SELECT tid
                  FROM {taxonomy_term_data} t_t_d JOIN {taxonomy_vocabulary} t_v ON t_v.vid = t_t_d.vid
                  WHERE t_t_d.vid = :vid
                )", array(':nid' => $quiz->nid, ':vid' => $quiz->vid, ':vid' => userpoints_get_vid()))->fetchField();
    $variables = array(
      '@title' => $quiz->title,
      '@quiz' => variable_get('quiz_name', QUIZ_NAME),
      '@time' => date('l jS \of F Y h:i:s A'),
    );
    $params = array(
      'points' => $score['numeric_score'],
      'description' => t('Attened @title @quiz on @time', $variables),
      'tid' => $selected_tid,
      'uid' => $taker->uid,
    );
    userpoints_userpointsapi($params);
  }
}

/**
 * Implements hook_quiz_scored().
 *
 * Performs actions like sending quiz results over email at the end of quiz.
 */
function quiz_quiz_scored($quiz, $score, $rid) {
  $taker = db_query('SELECT u.uid, u.mail FROM {users} u JOIN {quiz_node_results} qnr ON u.uid = qnr.uid WHERE result_id = :rid', array(':rid' => $rid))->fetch();
  if (variable_get('quiz_email_results', 0) && $taker->uid != 0 && $score['is_evaluated']) {
    drupal_mail('quiz', 'notice', $taker->mail, NULL, array($quiz, $score, $rid, 'taker'));
    drupal_set_message(t('The results has been sent to the users e-mail address.'));
  }

  // Calls userpoints functions to credit user point based on number of correct
  // answers.
  if ($quiz->has_userpoints && $taker->uid != 0 && $score['is_evaluated']) {

    //Looking up the tid of the selected Userpoint vocabulary
    $selected_tid = db_query("SELECT ti.tid
                FROM {taxonomy_index} ti
                JOIN {taxonomy_term_data} td
                  ON td.tid = ti.tid AND td.vid = :vid
                WHERE ti.nid = :nid", array(':nid' => $quiz->nid, ':vid' => userpoints_get_vid()))->fetchField();
    $variables = array(
      '@title' => $quiz->title,
      '@quiz' => variable_get('quiz_name', QUIZ_NAME),
      '@time' => date('l jS \of F Y h:i:s A'),
    );
    $params = array(
      'points' => $score['numeric_score'],
      'description' => t('Attened @title @quiz on @time', $variables),
      'tid' => $selected_tid,
    );
    userpoints_userpointsapi($params);
  }
}

/**
 * Implements hook_mail().
 */
function quiz_mail($key, &$message, $params) {
  global $user;

  switch ($key) {
    case 'notice':
      list($quiz, $score, $rid, $target) = $params;
      $quiz_body = field_get_items('node', $quiz, 'body');
      $substitutions = array(
        '!title' => $quiz->title,
        '!sitename' => variable_get('site_name', 'Quiz'),
        '!taker' => $user->name,
        '!author' => $quiz->name,
        '!title' => check_plain($quiz->title),
        '!date' => date("F j, Y, g:i a"),
        '!desc' => $quiz_body ? $quiz_body[0]['value'] : '',
        '!correct' => isset($score['numeric_score']) ? $score['numeric_score'] : 0,
        '!total' => $score['possible_score'],
        '!percentage' => $score['percentage_score'],
        '!url' => url('user/quiz/' . $rid . '/userresults', array('absolute' => TRUE)),
        '!minutes' => db_query("SELECT CEIL((time_end - time_start)/60) FROM {quiz_node_results} WHERE result_id = :result_id AND time_end", array(':result_id' => $rid))->fetchField()
      );
      $type = $target != 'author' ? '_taker' : '';
      $test = variable_get('quiz_email_results_body' . $type, quiz_email_results_format('body', $target));
      $message['subject'] = t(variable_get('quiz_email_results_subject' . $type, quiz_email_results_format('subject', $target)), $substitutions, array('langcode' => $user->language));
      $message['body'][] = t($test, $substitutions, array('langcode' => $user->language));
  }
}

/**
 * This functions returns the default email subject and body format which will
 * be used at the end of quiz.
 */
function quiz_email_results_format($type, $target) {
  global $user;
  if ($type == 'subject') {
    if ($target == 'author') {
      return t('!title Results Notice from !sitename');
    }
    if ($target == 'taker') {
      return t('!title Results Notice from !sitename');
    }
  }
  if ($type == 'body') {
    if ($target == 'author') {
      return t('Dear !author') . "\n\n" .
        t('!taker attended the quiz !title on !date') . "\n" .
        t('Test Description : !desc') . "\n" .
        t('!taker got !correct out of !total points in !minutes minutes. Score given in percentage is !percentage') . "\n" .
        t('You can access the result here !url') . "\n";
    }
    if ($target == 'taker') {
      return t('Dear !taker') . "\n\n" .
        t('You attended the quiz !title on !date') . "\n" .
        t('Test Description : !desc') . "\n" .
        t('You got !correct out of !total points in !minutes minutes. Score given in percentage is !percentage') . "\n" .
        t('You can access the result here !url') . "\n";
    }
  }
}

/**
 * Update a score for a quiz.
 *
 * This updates the quiz node results table.
 *
 * It is used in cases where a quiz score is changed after the quiz has been
 * taken. For example, if a long answer question is scored later by a human,
 * then the quiz should be updated when that answer is scored.
 *
 * Important: The value stored in the table is the *percentage* score.
 *
 * @param $quiz
 *   The quiz node for the quiz that is being scored.
 * @param $rid
 *   The result ID to update.
 * @return
 *   The score as an integer representing percentage. E.g. 55 is 55%.
 */
function quiz_update_total_score($quiz, $rid) {
  $score = quiz_calculate_score($quiz, $rid);
  db_update('quiz_node_results')
    ->fields(array(
      'score' => $score['percentage_score'],
    ))
    ->condition('result_id', $rid)
    ->execute();
  if ($score['is_evaluated']) {
    // Call hook_quiz_scored().
    module_invoke_all('quiz_scored', $quiz, $score, $rid);
    _quiz_maintain_results($quiz, $rid);
    db_update('quiz_node_results')
      ->fields(array('is_evaluated' =>  1))
      ->condition('result_id', $rid)
      ->execute();
  }
  return $score['percentage_score'];
}

/**
 * Updates the max_score property on the specified quizzes
 *
 * @param $quizzes_to_update
 *  Array with the vid's of the quizzes to update
 */
function quiz_update_max_score_properties($quizzes_to_update) {
  if (empty($quizzes_to_update)) {
    return;
  }

  db_update('quiz_node_properties')
    ->expression('max_score', 'max_score_for_random * number_of_random_questions + (
      SELECT COALESCE(SUM(max_score), 0)
      FROM {quiz_node_relationship} qnr
      WHERE qnr.question_status = ' . QUESTION_ALWAYS . '
      AND parent_vid = {quiz_node_properties}.vid)')
    ->condition('vid', $quizzes_to_update, 'IN')
    ->execute();

  db_update('quiz_node_properties')
    ->expression('max_score',
      '(SELECT COALESCE(SUM(qt.max_score * qt.number), 0)
      FROM {quiz_terms} qt
      WHERE qt.nid = {quiz_node_properties}.nid AND qt.vid = {quiz_node_properties}.vid)')
    ->condition('randomization', 3)
    ->condition('vid', $quizzes_to_update, 'IN')
    ->execute();

  db_update('node_revision')
    ->fields(array('timestamp' => REQUEST_TIME))
    ->condition('vid', $quizzes_to_update, 'IN')
    ->execute();

  db_update('node')
    ->fields(array('changed' => REQUEST_TIME))
    ->condition('vid', $quizzes_to_update, 'IN')
    ->execute();

  $results_to_update = db_query('SELECT vid FROM {quiz_node_properties} WHERE vid IN (:vid) AND max_score <> :max_score', array(':vid' => $quizzes_to_update, ':max_score' => 0))->fetchCol();
  if (!empty($results_to_update)) {
    db_update('quiz_node_results')
      ->expression('score',
	  'ROUND(
	  100 * (
	    SELECT COALESCE (SUM(a.points_awarded), 0)
	    FROM {quiz_node_results_answers} a
	    WHERE a.result_id = {quiz_node_results}.result_id
	  ) / (
	    SELECT max_score
	    FROM {quiz_node_properties} qnp
	    WHERE qnp.vid = {quiz_node_results}.vid
	  )
	)')
      ->condition('vid', $results_to_update, 'IN')
      ->execute();
  }
}

/**
 * Check a user/quiz combo to see if the user passed the given quiz.
 *
 * This will return TRUE if the user has passed the quiz at least once, and
 * FALSE otherwise. Note that a FALSE may simply indicate that the user has not
 * taken the quiz.
 *
 * @param $uid
 *   The user ID.
 * @param $nid
 *   The node ID.
 * @param $vid
 *   The version ID.
 */
function quiz_is_passed($uid, $nid, $vid) {
  $passed = db_query('SELECT COUNT(result_id) AS passed_count FROM {quiz_node_results} qnrs
    INNER JOIN {quiz_node_properties} USING (vid, nid)
    WHERE qnrs.vid = :vid
      AND qnrs.nid = :nid
      AND qnrs.uid = :uid
      AND score >= pass_rate', array(':vid' => $vid, ':nid' => $nid, ':uid' => $uid))->fetchField();
  // Force into boolean context.
  return ($passed !== FALSE && $passed > 0);
}

/**
 * Actions to take place at the start of a quiz.
 *
 * This is called when the quiz node is viewed for the first time. It ensures
 * that the quiz can be taken at this time.
 *
 * @param $quiz
 *   The quiz node.
 * @param $rid
 *   The result ID
 *
 * @return
 *   Return quiz_node_results result_id, or FALSE if there is an error.
 */
function quiz_start_check($quiz, $rid) {
  global $user;

  $user_is_admin = user_access('edit any quiz content') || (user_access('edit own quiz content') && $quiz->uid == $user->uid);

  // Make sure this is available.
  if ($quiz->quiz_always != 1) {
    // Compare current GMT time to the open and close dates (which should still
    // be in GMT time).
    $now = gmmktime();

    if ($now >= $quiz->quiz_close || $now < $quiz->quiz_open) {
      if ($user_is_admin) {
        drupal_set_message(t('You are marked as an administrator or owner for this quiz. While you can take this quiz, the open/close times prohibit other users from taking this quiz.'), 'status');
      }
      else {
        drupal_set_message(t('This @quiz is not currently available.', array('@quiz' => QUIZ_NAME)), 'status');
        // Can't take quiz.
        return FALSE;
      }
    }
  }

  // Check to see if this user is allowed to take the quiz again:
  if ($quiz->takes > 0) {
    $query = "SELECT COUNT(*) AS takes FROM {quiz_node_results} WHERE uid = %d AND nid = %d";
    $taken = db_query("SELECT COUNT(*) AS takes FROM {quiz_node_results} WHERE uid = :uid AND nid = :nid", array(':uid' => $user->uid, ':nid' => $quiz->nid))->fetchField();
    $allowed_times = format_plural($quiz->takes, '1 time', '@count times');
    $taken_times   = format_plural($taken, '1 time', '@count times');

    // The user has already taken this quiz.
    if ($taken) {
      if ($user_is_admin) {

        drupal_set_message(t('You have taken this quiz already. You are marked as an owner or administrator for this quiz, so you can take this quiz as many times as you would like.'), 'status');
      }
      // If the user has already taken this quiz too many times, stop the user.
      elseif ($taken >= $quiz->takes) {
        drupal_set_message(t('You have already taken this quiz @really. You may not take it again.', array('@really' => $taken_times)), 'error');
        return FALSE;
      }
      // If the user has taken the quiz more than once, see if we should report
      // this.
      elseif ($quiz->show_attempt_stats) {
        drupal_set_message(t("You can only take this quiz @allowed. You have taken it @really.", array('@allowed' => $allowed_times, '@really' => $taken_times)), 'status');
      }
    }
  }

  // Check to see if the user is registered, and user alredy passed this quiz.
  if ($quiz->show_passed && $user->uid && quiz_is_passed($user->uid, $quiz->nid, $quiz->vid)) {
    drupal_set_message(t('You have already passed this @quiz.', array('@quiz' => QUIZ_NAME)), 'status');
  }

  // Call hook_quiz_begin().
  module_invoke_all('quiz_begin', $quiz, $rid);

  return TRUE;
}

/**
 * Find out if a quiz is available for taking or not
 *
 * @param $quiz
 *  The quiz node
 * @return
 *  TRUE if available
 *  Error message(String) if not available
 */
function quiz_availability($quiz) {
  global $user;

  if ($user->uid == 0 && $quiz->takes > 0) {
    return t('This quiz only allows %num_attempts attempts. Anonymous users can only access quizzes that allows an unlimited number of attempts.',
      array('%num_attempts' => $quiz->takes));
  }

  $user_is_admin = user_access('edit any quiz content') || (user_access('edit own quiz content') && $quiz->uid == $user->uid);
  if ($user_is_admin || $quiz->quiz_always == 1) {
    return TRUE;
  }

  // Compare current GMT time to the open and close dates (which should still be
  // in GMT time).
  $now = gmmktime();

  if ($now >= $quiz->quiz_close || $now < $quiz->quiz_open) {
    return t('This quiz is closed');
  }
  return TRUE;
}

/**
 * Creates a unique id to be used when storing results for a quiz taker.
 *
 * @param $quiz
 *   The quiz node.
 * @return $rid
 *   The result id.
 */
function quiz_create_rid($quiz) {
  $rid = db_insert('quiz_node_results')
    ->fields(array(
      'nid' => $quiz->nid,
      'vid' => $quiz->vid,
      'uid' => $GLOBALS['user']->uid,
      'time_start' => REQUEST_TIME,
    ))
    ->execute();
  if (!is_numeric($rid)) {
    form_set_error(t('There was a problem starting the @quiz. Please try again later.', array('@quiz' => QUIZ_NAME), array('langcode' => 'error')));
    return FALSE;
  }
  return $rid;
}

/**
 * Calculates the score user received on quiz.
 *
 * @param $quiz
 *   The quiz node.
 * @param $rid
 *   Quiz result ID.
 *
 * @return array
 *   Contains three elements: question_count, num_correct and percentage_score.
 */
function quiz_calculate_score($quiz, $rid) {
  // 1. Fetch all questions and their max scores
  $questions = db_query('SELECT a.question_nid, a.question_vid, n.type, r.max_score
    FROM {quiz_node_results_answers} a
    LEFT JOIN {node} n ON (a.question_nid = n.nid)
    LEFT OUTER JOIN {quiz_node_relationship} r ON (r.child_vid = a.question_vid) AND r.parent_vid = :vid
    WHERE result_id = :rid', array(':vid' => $quiz->vid, ':rid' => $rid));
  // 2. Callback into the modules and let them do the scoring. @todo after 4.0: Why isn't the scores already saved? They should be
  // Fetched from the db, not calculated....
  $scores = array();
  $count = 0;
  foreach ($questions as $question) {
    // Questions picked from term id's won't be found in the quiz_node_relationship table
    if ($question->max_score === NULL && isset($quiz->tid) && $quiz->tid > 0) {
      $question->max_score = $quiz->max_score_for_random;
    }

    // Invoke hook_quiz_question_score().
    // We don't use module_invoke() because (1) we don't necessarily want to wed
    // quiz type to module, and (2) this is more efficient (no NULL checks).
    $mod = quiz_question_module_for_type($question->type);
    if (!$mod) {
      continue;
    }
    $function = $mod . '_quiz_question_score';

    if (function_exists($function)) {
      $score = $function($quiz, $question->question_nid, $question->question_vid, $rid);
      // Allow for max score to be considered.
      $scores[] = $score;
    }
    else {
      drupal_set_message(t('A quiz question could not be scored: No scoring info is available'), 'error');
      $dummy_score = new stdClass();
      $dummy_score->possible = 0;
      $dummy_score->attained = 0;
      $scores[] = $dummy_score;
    }
    ++$count;
  }
  // 3. Sum the results.
  $possible_score = 0;
  $total_score = 0;
  $is_evaluated = TRUE;
  foreach ($scores as $score) {
    $possible_score += $score->possible;
    $total_score += $score->attained;
    if (isset($score->is_evaluated)) {
      // Flag the entire quiz if one question has not been evaluated.
      $is_evaluated &= $score->is_evaluated;
    }
  }

  // 4. Return the score.
  return array(
    'question_count' => $count,
    'possible_score' => $possible_score,
    'numeric_score' => $total_score,
    'percentage_score' => ($possible_score == 0) ? 0 : round(($total_score * 100) / $possible_score),
    'is_evaluated' => $is_evaluated,
  );
}

/**
 * @param $type
 *
 * @return string
 *   Name of module matching the question type, as given by quiz_question_info()
 *   hook.
 */
function quiz_question_module_for_type($type) {
  $types = _quiz_get_question_types();
  if (!isset($types[$type])) {
    drupal_set_message(t('The module for the questiontype %type is not enabled', array('%type' => $type)), 'warning');
    return FALSE;
  }
  return $types[$type]['module'];
}

/**
 * Retrieves a list of questions (to be taken) for a given quiz.
 *
 * If the quiz has random questions this function only returns a random
 * selection of those questions. This function should be used to decide
 * what questions a quiz taker should answer.
 *
 * @param $quiz
 *   Quiz node.
 * @return
 *   Array of question node IDs.
 */
function quiz_build_question_list($quiz) {
  $questions = array();

  if ($quiz->randomization == 3) {
    return _quiz_build_categorized_question_list($quiz);
  }

  // Get required questions first.
  $query = db_query('SELECT child_nid as nid, child_vid as vid, max_score as relative_max_score
    FROM {quiz_node_relationship} qnr
    JOIN {node} n ON qnr.child_nid = n.nid
    WHERE qnr.parent_vid = :parent_vid
    AND qnr.question_status = :question_status
    AND n.status = 1
    ORDER BY weight', array(':parent_vid' => $quiz->vid, ':question_status' => QUESTION_ALWAYS));
  while ($question_node = $query->fetchAssoc()) {
    $questions[] = $question_node;
  }

  // Get random questions for the remainder.
  if ($quiz->number_of_random_questions > 0) {
    $random_questions = _quiz_get_random_questions($quiz);
    $questions = array_merge($questions, $random_questions);
    if ($quiz->number_of_random_questions > count($random_questions)) {
      // Unable to find enough requested random questions.
      return FALSE;
    }
  }

  // Shuffle questions if required.
  if ($quiz->randomization > 0) {
    shuffle($questions);
  }
  $count = 0;
  foreach ($questions as &$question) {
    $count++;
    $question['number'] = $count;
  }
  return $questions;
}

/**
 * Builds the questionlist for quizzes with categorized random questions
 *
 * @see quiz_build_question_list()
 */
function _quiz_build_categorized_question_list($quiz) {
  $terms = _quiz_get_terms($quiz->vid);
  $questions = array();
  $nids = array();
  $question_types = array_keys(_quiz_get_question_types());
  if (empty($question_types)) {
    return array();
  }
  $total_count = 0;
  foreach ($terms as $term) {

    $query = db_select('node','n');
    $query->join('taxonomy_index','tn','n.nid = tn.nid');
    $query->fields('n', array('nid','vid'));
    $query->fields('tn', array('tid'));
    $query->condition('n.status',1,'=');
    $query->condition('n.type',$question_types,'IN');
    $query->condition('tn.tid',$term->tid,'=');
    if (!empty($nids)) {
      $query->condition('n.nid',$nids,'NOT IN');
    }
    $query->range(0,$term->number);
    $query->orderBy('RAND()');

    $result = $query->execute();
    $count = 0;
    while ($question = $result->fetchAssoc()) {
      $count++;
      $question['tid'] = $term->tid;
      $question['number'] = $count + $total_count;
      $questions[] = $question;
      $nids[] = $question['nid'];
    }
    $total_count += $count;
    if ($count < $term->number) {
      // Not enough questions
      return FALSE;
    }
  }
  return $questions;
}

/**
 * Get data for all terms belonging to a Quiz with categorized random questions
 *
 * @param int $vid
 *  version id for the quiz
 * @return array
 *  Array with all terms that belongs to the quiz as objects
 */
function _quiz_get_terms($vid) {
  $sql = 'SELECT td.name, qt.*
    FROM {quiz_terms} qt
    JOIN {taxonomy_term_data} td ON qt.tid = td.tid
    WHERE qt.vid = :vid
    ORDER BY qt.weight';
  return db_query($sql, array(':vid' => $vid))->fetchAll();
}

/**
 * Map node properties to a question object.
 *
 * @param $node
 *  The question node.
 * @param $include_question
 *  Should the question(the question nodes body) be included?
 *
 * @return
 *  Question object.
 */
function quiz_node_map($node, $include_question = TRUE) {
  $new_question = new stdClass();
  if ($include_question) {
    $new_question->question = check_markup($node->body, $node->body['und'][0]['format']);
  }
  $new_question->title = $node->title;
  $new_question->nid = $node->nid;
  $new_question->vid = $node->vid;
  $new_question->type = $node->type;
  $new_question->latest_vid = $node->latest_vid;
  $new_question->question_status = isset($node->question_status) ? $node->question_status : QUESTION_NEVER;
  if (isset($node->max_score)) {
    $new_question->max_score = $node->max_score;
  }
  if (isset($node->auto_update_max_score)) {
    $new_question->auto_update_max_score = $node->auto_update_max_score;
  }
  $new_question->weight = $node->weight;
  return $new_question;
}

/**
 * Sets the questions that are assigned to a quiz.
 *
 * @param $quiz
 *   The quiz(node) to modify.
 * @param $questions
 *   An array of questions.
 * @param $set_new_revision
 *   If TRUE, a new revision will be generated. Note that saving
 *   quiz questions unmodified will still generate a new revision of the quiz if
 *   this is set to TRUE. Why? For a few reasons:
 *   - All of the questions are updated to their latest VID. That is supposed to
 *     be a feature.
 *   - All weights are updated.
 *   - All status flags are updated.
 *
 * @return
 *   Boolean TRUE if update was successful, FALSE otherwise.
 */
function quiz_set_questions(&$quiz, $questions, $set_new_revision = FALSE) {
  $old_vid = $quiz->vid;
  if ($set_new_revision) {
    // Create a new Quiz VID, even if nothing changed.
    $quiz->revision = 1;
  }
  node_save($quiz);

  // When node_save() calls all of the node API hooks, old quiz info is
  // automatically inserted into quiz_node_relationship. We could get clever and
  // try to do strategic updates/inserts/deletes, but that method has already
  // proven error prone as the module has gained complexity (See 5.x-2.0-RC2).
  // So we go with the brute force method:
  db_delete('quiz_node_relationship')
    ->condition('parent_nid', $quiz->nid)
    ->condition('parent_vid', $quiz->vid)
    ->execute();

  if (empty($questions)) {
    return TRUE; // This is not an error condition.
  }

  $qnr_insert = db_insert('quiz_node_relationship')
    ->fields(array('parent_nid', 'parent_vid', 'child_nid', 'child_vid', 'question_status', 'weight', 'max_score', 'auto_update_max_score'));
  foreach ($questions as $question) {
    if ($question->state != QUESTION_NEVER) {
      $qnr_insert->values(array(
        'parent_nid' => $quiz->nid,
        'parent_vid' => $quiz->vid,
        'child_nid' => $question->nid,
        'child_vid' => $question->refresh ? db_query('SELECT vid FROM {node} WHERE nid = :nid', array(':nid' => $question->nid))->fetchField() : $question->vid,
        'question_status' => $question->state,
        'weight' => $question->weight,
        'max_score' => (int) $question->max_score,
        'auto_update_max_score' => (int) $question->auto_update_max_score,
      ));
    }
  }
  if (count($questions)) {
    $qnr_insert->execute();
  }
  quiz_update_max_score_properties(array($quiz->vid));
  return TRUE;
}

/**
 * @} End of "defgroup quiz_public".
 */

/**
 * Resume an in-progress quiz.
 *
 * This sets the user's session back to the state it was in when the quiz was
 * aborted.
 *
 * This function should only be called if the quiz needs resuming. Outside logic
 * needs to check that, though.
 *
 * @param $quiz
 *   The current quiz(node).
 * @param $uid
 *   The ID of the current user.
 * @param $rid
 *   The result ID found for the current quiz.
 */
function _quiz_resume_existing_quiz($quiz, $uid, $rid) {
  // Create question list.
  $questions = quiz_build_question_list($quiz);
  $already_answered = array();
  // Now we need to make sure to set previous questions to be correct.
  // This includes corrections for cases where questions were shuffled.
  $answered_questions = db_query('SELECT question_nid AS nid, question_vid AS vid, number
          FROM {quiz_node_results_answers}
          WHERE result_id = :rid
          ORDER BY number, answer_timestamp', array(':rid' => $rid));
  //while ($answered = db_fetch_object($answered_questions)) {
  foreach ($answered_questions as $answered) {
    foreach ($questions as $question) {
      if ($question['vid'] == $answered->vid) {
        $already_answered[] = $answered->vid;
        // we found it, no need to continue the foreach loop
        continue 2;
      }
    }
    // This question was answered, but wasn't in $questions, so we need
    // to replace an unanswered question in $questions with it.
    if (!in_array($answered->vid, $already_answered)) {
      foreach ($questions as $key => $question) {
        if (!in_array($question['vid'], $already_answered) && isset($question['random']) && $question['random'] === TRUE) {
          $questions[$key]['vid'] = $answered->vid;
          $questions[$key]['nid'] = $answered->nid;
          $questions[$key]['rid'] = $rid;
          $already_answered[] = $answered->vid;
          continue 2;
        }
      }
    }
  }

  // Adding data to the session.
  reset($questions);
  $_SESSION['quiz_' . $quiz->nid]['previous_quiz_questions'] = array();
  $_SESSION['quiz_' . $quiz->nid]['quiz_questions'] = array();
  $next_number = count($already_answered) + 1;
  foreach ($questions as $question) {
    if (in_array($question['vid'], $already_answered)) {
      $question['rid'] = $rid;
      $_SESSION['quiz_' . $quiz->nid]['previous_quiz_questions'][] = $question;
    }
    else {
      $question['number'] = $next_number;
      $next_number++;
      $_SESSION['quiz_' . $quiz->nid]['quiz_questions'][] = $question;
    }
  }

  if (empty($_SESSION['quiz_'. $quiz->nid]['quiz_questions']) &&
      count($_SESSION['quiz_'. $quiz->nid]['previous_quiz_questions']) == count($already_answered)
    ) {
    $_SESSION['quiz_'. $quiz->nid]['quiz_questions'][] = array_pop($_SESSION['quiz_'. $quiz->nid]['previous_quiz_questions']);
  }

  $_SESSION['quiz_'. $quiz->nid]['result_id'] = $rid;
  $_SESSION['quiz_'. $quiz->nid]['question_number'] = count($_SESSION['quiz_'. $quiz->nid]['previous_quiz_questions']);

  // Timed quizzes are likely to have expired by this point. But we let
  // the quiz_take_quiz handler handle that.
  $_SESSION['quiz_' . $quiz->nid]['question_start_time'] = REQUEST_TIME;
  $_SESSION['quiz_' . $quiz->nid]['question_duration'] = $quiz->time_limit;

  drupal_set_message(t('Resuming a previous quiz in-progress.'), 'status');
}

/**
 * Delete quiz responses for quizzes that haven't been finished.
 *
 * @param $quiz
 *   A quiz node where old in progress results shall be deleted.
 * @param $uid
 *   The userid of the user the old in progress results belong to.
 */
function _quiz_delete_old_in_progress($quiz, $uid) {
  $res = db_query('SELECT qnr.result_id FROM {quiz_node_results} qnr
          WHERE qnr.uid = :uid
          AND qnr.nid = :nid
          AND qnr.time_end = :time_end
          AND qnr.vid < :vid', array(':uid' => $uid, ':nid' => $quiz->nid, ':time_end' => 1, ':vid' => $quiz->vid));
  $rids = array();
  while ($rid = $res->fetchField()) {
    $rids[] = $rid;
  }
  quiz_delete_results($rids);
}

/**
 * Returns the result ID for any current result set for the given quiz.
 *
 * @param $uid
 *   User ID
 * @param $nid
 *   Quiz node ID
 * @param $vid
 *   Quiz node version ID
 * @param $now
 *   Timestamp used to check whether the quiz is still open. Default: current
 *   time.
 *
 * @return
 *   If a quiz is still open and the user has not finished the quiz,
 *   return the result set ID so that the user can continue. If no quiz is in
 *   progress, this will return 0.
 */
function _quiz_active_result_id($uid, $nid, $vid, $now = NULL) {
  if (!isset($now)) {
    $now = REQUEST_TIME;
  }

  // Get any quiz that is open, for this user, and has not already
  // been completed.
  $rid = db_query('SELECT result_id FROM {quiz_node_results} qnr
          INNER JOIN {quiz_node_properties} qnp ON qnr.vid = qnp.vid
          WHERE (qnp.quiz_always = :quiz_always OR (:between BETWEEN qnp.quiz_open AND qnp.quiz_close))
          AND qnr.vid = :vid
          AND qnr.uid = :uid
          AND qnr.time_end = :time_end', array(':quiz_always' => 1, ':between' => $now, ':vid' => $vid, ':uid' => $uid, ':time_end' => 0))->fetchField();
  return (int) $rid;
}

/**
 * Insert call specific to result options.
 *
 * This is called by quiz_insert().
 *
 * @param $node
 *   The quiz node.
 */
function _quiz_insert_resultoptions($node) {
  if (!isset($node->resultoptions)) {
    return;
  }

  $query = db_insert('quiz_node_result_options')
    ->fields(array('nid', 'vid', 'option_name', 'option_summary', 'option_summary_format', 'option_start', 'option_end'));

  foreach ($node->resultoptions as $id => $option) {
    if (!empty($option['option_name'])) {
      // When this function called direct from node form submit the $option['option_summary']['value'] and $option['option_summary']['format'] are we need
      // But when updating a quiz node eg. on manage questions page, this values come from loaded node, not from a submitted form.
      if (is_array($option['option_summary'])) {
        $option['option_summary_format'] = $option['option_summary']['format'];
        $option['option_summary'] = $option['option_summary']['value'];
      }
      $query->values(array(
        'nid'                   => $node->nid,
        'vid'                   => $node->vid,
        'option_name'           => $option['option_name'],
        'option_summary'        => $option['option_summary'],
        'option_summary_format' => $option['option_summary_format'],
        'option_start'          => $option['option_start'],
        'option_end'            => $option['option_end']
      ));
    }
  }

  $query->execute();
}

/**
 * Insert one result option.
 * @TODO This could be a dead function from now, need to remove this.
 * @param $option
 *  The option array to insert.
 */
function _quiz_insert_result_option($option) {
  db_insert('quiz_node_result_options')
    ->fields('nid', 'vid', 'option_name', 'option_summary', 'option_start', 'option_end')
    ->values(array(
      $option['nid'],
      $option['vid'],
      $option['option_name'],
      $option['option_summary'],
      $option['option_start'],
      $option['option_end'],
    ))
    ->execute();
}

/**
 * Modify result of option-specific updates.
 *
 * @param $node
 *   The quiz node.
 */
function _quiz_update_resultoptions($node) {
  // Brute force method. Easier to get correct, and probably faster as well.
  db_delete('quiz_node_result_options')
    ->condition('vid', $node->vid)
    ->execute();
  _quiz_insert_resultoptions($node);
}

/**
 * Get the summary message for a completed quiz.
 *
 * Summary is determined by whether we are using the pass / fail options, how
 * the user did, and where the method is called from.
 *
 * @todo Need better feedback for when a user is viewing their quiz results
 *   from the results list (and possibily when revisiting a quiz they can't take
 *   again).
 *
 * @param $quiz
 *   The quiz node object.
 * @param $score
 *   The score information as returned by quiz_calculate_score().
 * @return
 *   Filtered summary text or null if we are not displaying any summary.
 */
function _quiz_get_summary_text($quiz, $score) {  $summary = array();
  $admin = (arg(0) == 'admin');
  if (!$admin) {
    if (!empty($score['result_option'])) {
      // Unscored quiz, return the proper result option.
      $summary['result'] = check_markup($score['result_option'], $quiz->body['und'][0]['format']);
    }
    else {
      $result_option = _quiz_pick_result_option($quiz->nid, $quiz->vid, $score['percentage_score']);
      $summary['result'] = is_object($result_option) ? check_markup($result_option->option_summary, $result_option->option_summary_format) : '';
    }
  }

  // If we are using pass/fail, and they passed.
  if ($quiz->pass_rate > 0 && $score['percentage_score'] >= $quiz->pass_rate) {
    // If we are coming from the admin view page.
    if ($admin) {
      $summary['passfail'] = t('The user passed this quiz.');
    }
    elseif (variable_get('quiz_use_passfail', 1) == 0) {
      // If there is only a single summary text, use this.
      if (trim($quiz->summary_default) != '') {
        $summary['passfail'] = check_markup($quiz->summary_default, $quiz->body['und'][0]['format']);
      }
    }
    elseif (trim($quiz->summary_pass) != '') {
      // If there is a pass summary text, use this.
      $summary['passfail'] = check_markup($quiz->summary_pass, $quiz->summary_pass_format);
    }
  }
  // If the user did not pass or we are not using pass/fail.
  else {
    // If we are coming from the admin view page, only show a summary if we are
    // using pass/fail.
    if ($admin) {
      if ($quiz->pass_rate > 0) {
        $summary['passfail'] = t('The user failed this quiz.');
      }
      else {
        $summary['passfail'] = t('the user completed this quiz.');
      }
    }
    elseif (trim($quiz->summary_default) != '') {
      $summary['passfail'] = check_markup($quiz->summary_default, $quiz->summary_default_format);
    }
  }
  return $summary;
}

/**
 * Get summary text for a particular score from a set of result options.
 *
 * @param $qnid
 *   The quiz node id.
 * @param $qvid
 *   The quiz node revision id.
 * @param $score
 *   The user's final score.
 *
 * @return
 *   Summary text for the user's score.
 */
function _quiz_pick_result_option($qnid, $qvid, $score) {
  return db_query('SELECT option_summary, option_summary_format FROM {quiz_node_result_options}
      WHERE nid = :nid AND vid = :vid AND :option BETWEEN option_start AND option_end',
      array(':nid' => $qnid, ':vid' => $qvid, ':option' => $score)
    )->fetch();
}

/**
 * Get an array list of random questions for a quiz.
 *
 * @param $quiz
 *   The quiz node.
 *
 * @return
 *   Array of nid/vid combos for quiz questions.
 */
function _quiz_get_random_questions($quiz) {
  if (!is_object($quiz)) {
    drupal_set_message(t('The question pool cannot be generated.'), 'error');
    watchdog('quiz', '_quiz_get_random_questions was called incorrectly.', array(), WATCHDOG_ERROR);
    return FALSE;
  }
  $num_random = $quiz->number_of_random_questions;
  $tid = $quiz->tid;
  $questions = array();
  if ($num_random > 0) {
    if ($tid > 0) {
      $questions = _quiz_get_random_taxonomy_question_ids($tid, $num_random);
    }
    else {
      // Select random question from assigned pool.
      $result = db_query_range(
        "SELECT child_nid as nid, child_vid as vid
        FROM {quiz_node_relationship} qnr
        JOIN {node} n on qnr.child_nid = n.nid
        WHERE qnr.parent_vid = :parent_vid
        AND qnr.parent_nid = :parent_nid
        AND qnr.question_status = :question_status
        AND n.status = 1
        ORDER BY RAND()", 0, $quiz->number_of_random_questions, array(
          ':parent_vid' => $quiz->vid,
          ':parent_nid' => $quiz->nid,
          ':question_status' => QUESTION_RANDOM
        )
      );
      while ($question_node = $result->fetchAssoc()) {
        $question_node['random'] = TRUE;
        $question_node['relative_max_score'] = $quiz->max_score_for_random;
        $questions[] = $question_node;
      }
    }
  }
  return $questions;
}


/**
 * Given a term ID, get all of the question nid/vids that have that ID.
 *
 * @param $tid
 *   Integer term ID.
 *
 * @return
 *   Array of nid/vid combos, like array(array('nid'=>1, 'vid'=>2)).
 */
function _quiz_get_random_taxonomy_question_ids($tid, $num_random) {
  if ($tid == 0) {
    return array();
  }

  // Select random questions by taxonomy.
  $term = taxonomy_term_load($tid);
  $tree = taxonomy_get_tree($term->vid, $term->tid);

  // Flatten the taxonomy tree, and just keep term id's.
  $term_ids[] = $term->tid;
  if (is_array($tree)) {
    foreach ($tree as $term) {
      $term_ids[] = $term->tid;
    }
  }
  $term_ids = implode(',', $term_ids);

  // Get all published questions with one of the allowed term ids.
  // TODO Please convert this statement to the D7 database API syntax.
  $result = db_query_range("SELECT n.nid, n.vid
    FROM {node} n
    INNER JOIN {taxonomy_index} tn USING (nid)
    WHERE n.status = 1 AND tn.tid IN ($term_ids)
    AND n.type IN ('" . implode("','", array_keys(_quiz_get_question_types()))
    . "') ORDER BY RAND()");

  $questions = array();
  while ($question_node = db_fetch_array($result)) {
    $question_node['random'] = TRUE;
    $questions[] = $question_node;
  }

  return $questions;
}

/**
 * Retrieve list of question types.
 *
 * @return
 *  Array of question types.
 */
function _quiz_get_question_types() {
  static $to_return = array();

  // We vastly improves performance by statically caching the question types.
  if (!empty($to_return)) {
    return $to_return;
  }

  // Get question types from the modules that defines them..
  $quiz_questions = module_invoke_all('quiz_question_info');
  if (empty($quiz_questions)) {
    drupal_set_message(t('You need to install and enable at least one question type(multichoice for instance) to use quiz.'), 'warning', FALSE);
    return array();
  }

  return $quiz_questions;
}

/**
 * Retrieve list of vocabularies for all quiz question types.
 *
 * @return
 *   An array containing a vocabulary list.
 */
function _quiz_get_vocabularies() {
  $vocabularies = array();
  $types = array_keys(_quiz_get_question_types());
  foreach ($types as $type) {
    foreach (taxonomy_get_vocabularies($type) as $vid => $vocabulary) {
      $vocabularies[$vid] = $vocabulary;
    }
  }
  return $vocabularies;
}

/**
 * Prints a taxonomy selection form for each vocabulary.
 *
 * @param $value
 *   Default selected value(s).
 * @return
 *   HTML output to print to screen.
 */
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;
}

/**
 * Retrieve list of published questions assigned to quiz.
 *
 * This function should be used for question browsers and similiar... It should not be used to decide what questions
 * a user should answer when taking a quiz. quiz_build_question_list is written for that purpose.
 *
 * @param $quiz_nid
 *   Quiz node id.
 * @param $quiz_vid
 *   Quiz node version id.
 * @param $include_all_types
 *   Should the results be filtered on available question types?
 *   @todo: review this.
 * @param $nid_keys
 *   Should nid be used as keys in the array we return?
 * @param $include_question
 *   Should the question(the node body) be included for the questions in the
 *   returned array?
 *
 * @return
 *   An array of questions.
 */
function quiz_get_questions($quiz_nid = NULL, $quiz_vid = NULL, $include_all_types = TRUE, $nid_keys = FALSE, $include_question = TRUE, $include_random = TRUE) {
  $questions = array();
  $query = db_select('node', 'n');
  $query->fields('n', array('nid', 'type'));
  $query->fields('nr', array('vid', 'title'));
  $query->fields('qnr', array('question_status', 'weight', 'max_score', 'auto_update_max_score'));
  $query->addField('n', 'vid', 'latest_vid');
  $query->join('node_revision', 'nr', 'n.nid = nr.nid');
  $query->leftJoin('quiz_node_relationship', 'qnr', 'nr.vid = qnr.child_vid');

  if ($include_all_types === TRUE) {
    $query->condition('n.type', array_keys(_quiz_get_question_types()), 'IN');
  }

  if (!is_null($quiz_vid)) {
    $query->condition('parent_vid', $quiz_vid);
    $query->condition('parent_nid', $quiz_nid);
  }

  if ($include_random) {
    $query->condition('question_status', array(QUESTION_RANDOM, QUESTION_ALWAYS), 'IN');
  }
  else {
    $query->condition('question_status', QUESTION_ALWAYS);
  }

  $query
    ->condition('n.status', 1)
    ->orderBy('weight');


  $results = $query->execute();

  foreach($results as $result) {

    $node = $result;
    // Create questions array.
    if ($nid_keys === FALSE) {
      //while ($node = db_fetch_object($result)) {
        $questions[] = quiz_node_map($node, $include_question);
      //}
    }
    else {
      //while ($node = db_fetch_object($result)) {
        $n = quiz_node_map($node, $include_question);
        $questions[$n->nid] = $n;
      //}
    }
  }

  return $questions;
}

/**
 * Get a list of all available quizzes.
 *
 * @param $uid
 *   An optional user ID. If supplied, only quizzes created by that user will be
 *   returned.
 *
 * @return
 *   A list of quizzes.
 */
function _quiz_get_quizzes($uid = 0) {
  $results = array();
  $args = array();
  $query = db_select('node', 'n')
    ->fields('n', array('nid', 'vid', 'title', 'uid', 'created'))
    ->fields('u', array('name'));
  $query->leftJoin('users', 'u', 'u.uid = n.uid');
  $query->condition('n.type', 'quiz');
  if ($uid != 0) {
    $query->condition('n.uid', $uid);
  }
  $query->orderBy('n.nid');
  $quizzes = $query->execute();
  foreach ($quizzes as $quiz) {
    $results[$quiz->nid] = (array) $quiz;
  }
  return $results;
}

/**
 * Get answer data for a specific result.
 *
 * @param $rid
 *   Result id.
 *
 * @return
 *   Array of answers.
 */
function _quiz_get_answers($quiz, $rid) {
  $questions = array();
  $ids = db_query("SELECT question_nid, question_vid, type, rs.max_score, qt.max_score as term_max_score
                   FROM {quiz_node_results_answers} ra
                   LEFT JOIN {node} n ON (ra.question_nid = n.nid)
                   LEFT JOIN {quiz_node_results} r ON (ra.result_id = r.result_id)
                   LEFT OUTER JOIN {quiz_node_relationship} rs ON (ra.question_vid = rs.child_vid) AND rs.parent_vid = r.vid
                   LEFT OUTER JOIN {quiz_terms} qt ON (qt.vid = :vid AND qt.tid = ra.tid)
                   WHERE ra.result_id = :rid
                   ORDER BY ra.number, ra.answer_timestamp", array(':vid' => $quiz->vid, ':rid' => $rid));
  while ($line = $ids->fetch()) {
    // Questions picked from term id's won't be found in the quiz_node_relationship table
    if ($line->max_score === NULL) {
      if ($quiz->randomization == 2 && isset($quiz->tid) && $quiz->tid > 0) {
        $line->max_score = $quiz->max_score_for_random;
      }
      elseif ($quiz->randomization == 3) {
        $line->max_score = $line->term_max_score;
      }
    }
    $module = quiz_question_module_for_type($line->type);
    if (!$module) {
      continue;
    }
    // Invoke hook_get_report().
    $report = module_invoke($module, 'get_report', $line->question_nid, $line->question_vid, $rid);
    if (!$report) {
      continue;
    }
    $questions[$line->question_nid] = $report;
    // Add max score info to the question.
    if (!isset($questions[$line->question_nid]->score_weight)) {
      if ($questions[$line->question_nid]->max_score == 0) {
        $score_weight = 0;
      }
      else {
        $score_weight = $line->max_score / $questions[$line->question_nid]->max_score;
      }
      $questions[$line->question_nid]->qnr_max_score = $line->max_score;
      $questions[$line->question_nid]->score_weight = $score_weight;
    }
  }
  return $questions;
}

/**
 * Clean up result data when the user jumps forward in a quiz
 *
 * When jumping in a quiz we might be skipping lots of questions. We need
 * to update the result data according to this...
 *
 * @param object $quiz
 *  The quiz node we want to clean up results for
 * @param int $rid
 *  Result id for the result we want to clean up
 */
function _quiz_cleanup_after_jumping($quiz, $rid) {
  foreach ($_SESSION['quiz_' . $quiz->nid]['previous_quiz_questions'] as $previous_question) {
    // tried db_merge() instead of separate SELECT and INSERT query
    // but it marks all the questions as skipped.
    $condition = array(
      ':rid' => $rid,
      ':nid' => $previous_question['nid'],
      ':vid' => $previous_question['vid']
    );
    $duplicate = db_query('SELECT * FROM {quiz_node_results_answers} WHERE result_id = :rid AND question_nid = :nid AND question_vid = :vid', $condition)->fetchAssoc();
    if (empty($duplicate)) {
      db_insert('quiz_node_results_answers')
        ->fields(array(
            'result_id' => $rid,
            'question_nid' => $previous_question['nid'],
            'question_vid' => $previous_question['vid'],
            'is_skipped' => 1,
            'answer_timestamp' => REQUEST_TIME,
            'number' => $previous_question['number'],
            'tid' => isset($previous_question['tid']) ? $previous_question['tid'] : 0,
        ))
        ->execute();
    }
  }
}

/**
 * Get the quiz name variable and set it as a constant so we don't have to keep
 * calling it in every function.
 *
 * @return
 *   Quiz name variable.
 */
function _quiz_get_quiz_name() {
  // @todo: remove all the quiz_name stuff? Isn't there better ways to do this?
  return variable_get('quiz_name', 'Quiz');
}

/**
 * Get an array of feedback options.
 *
 * @return
 *   An array of feedback options.
 */
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'),
  );
}

/**
 * Takes a time element and prepares to send it to form_date().
 *
 * @param $time
 *   The time to be turned into an array. This can be:
 *   - A timestamp when from the database.
 *   - An array (day, month, year) when previewing.
 *   - NULL for new nodes.
 *
 * @return
 *   An array for form_date (day, month, year).
 */
function _quiz_form_prepare_date($time = '', $offset = 0) {
  // If this is empty, get the current time.
  if ($time == '') {
    $time = REQUEST_TIME + $offset * 86400;
  }
  // If we are previewing, $time will be an array so just pass it through.
  $time_array = array();
  if (is_array($time)) {
    $time_array = $time;
  }
  // Otherwise build the array from the timestamp.
  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;
}

/**
 * This function was copied from the triggers module as to prevent having to be
 * dependent on that module for the actions to work.  The trigger function is
 * called trigger_options().
 *
 * @param $type
 *   One of 'node', 'user', 'comment'.
 *
 * @return
 *   Array keyed by action ID.
 */
function quiz_action_options($type = 'all') {
  $options = array(t('Choose an action'));
  foreach (actions_actions_map(actions_get_all_actions()) as $aid => $action) {
    $options[$action['type']][$aid] = $action['label'];
  }

  if ($type == 'all') {
    return $options;
  }
  else {
    $options[$type][0] = t('Choose an action');
    // Lets sort it to get the choose an action back first.
    ksort($options[$type]);
    return $options[$type];
  }
}

/**
 * Returns an array with quiz titles keyed with quiz node ids.
 *
 * @return array
 *   Options suitable for a form, in which the value is nid.
 */
function quiz_get_quiz_options() {
  $options = array();
  $results = db_query('SELECT n.nid, n.title, n.type FROM {node} n WHERE n.type = :type', array(':type' => 'quiz'));
  foreach ($results as $result) {
    $options[$result->nid] = drupal_substr(check_plain($result->title), 0, 30);
  }
  return $options;
}

/**
 * Tells whether a question node is being viewed inside/outside of quiz.
 *
 * @return
 *   Boolean.
 */
function _quiz_is_taking_context() {
  return arg(2) == 'take';
}

/**
 * Makes, saves and returns a new quiz node.
 *
 * @param $title
 *   The title of the new node.
 *
 * @return
 *   New quiz node object.
 */
function quiz_make_new($title) {
  global $user;
  $new_node = new stdClass();
  $new_node->title = $title;
  $new_node->type = 'quiz';
  $new_node->status = 1;
  // Get default user settings.
  $settings = _quiz_load_user_settings();
  if (!$settings) {
    $settings = _quiz_load_user_settings(variable_get('quiz_def_uid', 1));
  }
  $settings += _quiz_get_node_defaults();
  foreach ($settings as $key => $value) {
    $new_node->{$key} = $value;
  }
  $new_node->auto_created = TRUE;
  $new_node->uid = $user->uid;
  node_save($new_node);
  if (is_numeric($new_node->nid)) {
    drupal_set_message(t('Quiz %title has been created.', array('%title' => $title)));
  }
  return $new_node;
}

/**
 * Retrieves the quiz node from the menu router.
 *
 * @return
 *   Quiz node, if found, or FALSE if quiz node can't be retrieved from the menu
 *   router.
 */
function quiz_get_quiz_from_menu() {
  if ($to_return = menu_get_object('quiz_type_access', 4)) {
    return $to_return;
  }
  //TODO: FIX it. This seems to return NULL in feedback page.
  $node = menu_get_object();
  return (is_object($node) && $node->type == 'quiz') ? $node : FALSE;
}

/**
 * Finds out if a quiz has been answered or not.
 *
 * @return
 *   TRUE if there exists answers to the current question.
 */
function quiz_has_been_answered($node) {
  if (!isset($node->nid)) {
    return FALSE;
  }
  $query = db_select('quiz_node_results', 'qnr');
  $query->addField('qnr', 'result_id');
  $query->condition('nid', $node->nid);
  $query->condition('vid', $node->vid);
  $query->range(0,1);
  return $query->execute()->rowCount() > 0;
}

/**
 * If a quiz is saved as not randomized we should make sure all random questions
 * are converted to always.
 *
 * @param $node
 *   Quiz node.
 */
function _quiz_check_num_random(&$node) {
  if ($node->randomization == 2) {
    return;
  }
  db_delete('quiz_node_relationship')
    ->condition('question_status', QUESTION_RANDOM)
    ->condition('parent_vid', $node->vid)
    ->execute();
}

/**
 * If a quiz is saved with random categories we should make sure all questions
 * are removed from the quiz
 *
 * @param $node
 *   Quiz node.
 */
function _quiz_check_num_always(&$node) {
  if ($node->randomization != 3) {
    return;
  }
  db_delete('quiz_node_relationship')
    ->condition('parent_vid', $node->vid)
    ->execute();
}

/**
 * Determine who should have access to the My results tab.
 */
function _quiz_user_results_access($user_id) {
  global $user;
  return ($user_id == $user->uid && user_access('view own quiz results')) || user_access('view any quiz results');
}

/**
 * Copies questions when a quiz is translated.
 *
 * @param $node
 *   The new translated quiz node.
 */
function quiz_copy_questions($node) {
  // Find original questions.
  $query = db_query('SELECT child_nid, child_vid, question_status, weight, max_score, auto_update_max_score FROM {quiz_node_relationship}
          WHERE parent_vid = :parent_vid', array(':parent_vid' => $node->translation_source->vid));
  foreach ($query as $res_o) {
    $original_question = node_load($res_o->child_nid);

    // Set variables we can't or won't carry with us to the translated node to
    // NULL.
    $original_question->nid = $original_question->vid = $original_question->created = $original_question->changed = NULL;
    $original_question->revision_timestamp = $original_question->menu = $original_question->path = NULL;
    $original_question->files = array();
    if (isset($original_question->book['mlid'])) {
      $original_question->book['mlid'] = NULL;
    }

    // Set the correct language.
    $original_question->language = $node->language;

    // Save the node.
    node_save($original_question);

    // Save the relationship between the new question and the quiz.
    $sql = 'INSERT INTO {quiz_node_relationship}
            (parent_nid, parent_vid, child_nid, child_vid, question_status, weight, max_score, auto_update_max_score)
            VALUES(:parent_nid, :parent_vid, :child_nid, :child_vid, :question_status, :weight, :max_score, :auto_update_max_score)';
    db_query($sql, array(':parent_nid' => $node->nid, ':parent_vid' => $node->vid, ':child_nid' => $original_question->nid, ':child_vid' => $original_question->vid, ':question_status' => $res_o->question_status, ':weight' => $res_o->weight, ':max_score' => $res_o->max_score, ':auto_update_max_score' => $res_o->auto_update_max_score));
  }
}

/**
 * Deletes results for a quiz according to the keep results setting
 *
 * @param $quiz
 *  The quiz node to be maintained
 * @param $rid
 *  The result id of the latest result for the current user
 * @return
 *  TRUE if results where deleted.
 */
function _quiz_maintain_results($quiz, $rid) {
  global $user;

  // Do not delete results for anonymous users
  if ($user->uid == 0) {
    return;
  }

  switch ($quiz->keep_results) {
    case QUIZ_KEEP_ALL:
      return FALSE;
    case QUIZ_KEEP_BEST:
      $best_result_id = db_query('SELECT result_id FROM {quiz_node_results}
          WHERE nid = :nid AND uid = :uid AND is_evaluated = :is_evaluated
          ORDER BY score DESC',
          array(':nid' => $quiz->nid, ':uid' => $user->uid, ':is_evaluated' => 1)
        )
        ->fetchField();
      if (!$best_result_id) {
        return;
      }
      $res = db_query('SELECT result_id FROM {quiz_node_results}
          WHERE nid = :nid AND uid = :uid AND result_id != :best_rid AND is_evaluated = :is_evaluated',
          array(':nid' => $quiz->nid, ':uid' => $user->uid, ':is_evaluated' => 1, ':best_rid' => $best_result_id)
        );
      $rids = array();
      while ($rid2 = $res->fetchField()) {
        $rids[] = $rid2;
      }
      quiz_delete_results($rids);
      return !empty($rids);
    case QUIZ_KEEP_LATEST:
      $res = db_query('SELECT result_id FROM {quiz_node_results}
              WHERE nid = :nid AND uid = :uid AND is_evaluated = :is_evaluated AND result_id != :result_id', array(':nid' => $quiz->nid, ':uid' => $user->uid, ':is_evaluated' => 1, ':result_id' => $rid));
      $rids = array();
      while ($rid2 = $res->fetchField()) {
        $rids[] = $rid2;
      }
      quiz_delete_results($rids);
      return !empty($rids);
  }
}

/**
 * Delete quiz results.
 *
 * @param $rids
 *   Result ids for the results to be deleted.
 */
function quiz_delete_results($rids) {
  if (empty($rids)) {
    return;
  }
  $sql = 'SELECT result_id, question_nid, question_vid FROM {quiz_node_results_answers}
          WHERE result_id IN(:result_id)';
  $result = db_query($sql, array(':result_id' => $rids));
  foreach ($result as $record) {
    quiz_question_delete_result($record->result_id, $record->question_nid, $record->question_vid);
  }

  db_delete('quiz_node_results_answers')
    ->condition('result_id', $rids, 'IN')
    ->execute();

  db_delete('quiz_node_results')
    ->condition('result_id', $rids, 'IN')
    ->execute();

}

/**
 * Returns the users default settings.
 *
 * @param $node
 *   Quiz node.
 * @param $uid
 *   (optional) The uid of the user to get the settings for. Defaults to the
 *   current user (NULL).
 *
 * @return
 *   An array of settings. The array is empty in case no settings are available.
 */
function _quiz_load_user_settings($uid = NULL) {
  // The def_uid property is the default user id. It is used if there are no
  // settings store for the current user.
  $uid = isset($uid) ? $uid : $GLOBALS['user']->uid;

  $query = db_select('quiz_user_settings', 'qus')
    ->fields('qus')
    ->condition('uid', $uid);
  $res = $query->execute()->fetchAssoc();
  if (!empty($res)) {
    foreach ($res as $key => $value) {
      if (!in_array($key, array('nid', 'vid', 'uid'))) {
        $settings[$key] = $value;
      }
    }
    // TODO : Reviews this later.
    $settings['resultoptions'][] = db_select('quiz_node_result_options', 'qnro')
      ->fields('qnro')
      ->condition('nid', $res['nid'])
      ->condition('vid', $res['vid'])
      ->execute()
      ->fetchAll();
    return $settings;
  }
  return array();
}

/**
 * Fetch settings from a node and save them as the users default settings.
 *
 * @param $node
 *   Quiz node.
 */
function _quiz_save_user_settings($node) {
  global $user;
  $node = (object) $node;
  // We do not save settings if the node has been created by the system,
  // or if the user haven't requested it
  if (isset($node->auto_created) || !isset($node->remember_settings) || !$node->remember_settings) {
    return FALSE;
  }

  $summary_pass_format = filter_fallback_format();
  if (isset($node->summary_pass['format']) && !empty($node->summary_pass['format'])) {
    $summary_pass_format = $node->summary_pass['format'];
  }

  $summary_default_format = filter_fallback_format();
  if (isset($node->summary_default['format']) && !empty($node->summary_default['format'])) {
    $summary_default_format = $node->summary_default['format'];
  }

  db_merge('quiz_user_settings')
    ->key(array(
      'uid' => $user->uid
    ))
    ->fields(array(
      'uid'                     => isset($node->uid) ? $node->uid : $node->save_def_uid,
      'nid'                     => $node->nid,
      'vid'                     => $node->vid,
      'aid'                     => isset($node->aid) ? $node->aid : 0,
      'pass_rate'               => $node->pass_rate,
      'summary_pass'            => isset($node->summary_pass['value']) ? $node->summary_pass['value'] : '',
      'summary_pass_format'     => $summary_pass_format,
      'summary_default'         => $node->summary_default['value'],
      'summary_default_format'  => $summary_default_format,
      'randomization'           => $node->randomization,
      'backwards_navigation'    => $node->backwards_navigation,
      'keep_results'            => $node->keep_results,
      'repeat_until_correct'    => $node->repeat_until_correct,
      'feedback_time'           => $node->feedback_time,
      'display_feedback'        => $node->display_feedback,
      'takes'                   => $node->takes,
      'show_attempt_stats'      => $node->show_attempt_stats,
      'time_limit'              => isset($node->time_limit) ? $node->time_limit : 0,
      'quiz_always'             => $node->quiz_always,
      'has_userpoints'          => isset($node->has_userpoints) ? $node->has_userpoints : 0,
      'allow_skipping'          => $node->allow_skipping,
      'allow_resume'            => $node->allow_resume,
      'allow_jumping'           => $node->allow_jumping,
      'show_passed'             => $node->show_passed,
    ))
    ->execute();
  drupal_set_message(t('Default settings have been saved'));
}

/**
 * Get the number of compulsory questions for a quiz.
 *
 * @param $nid
 *   Quiz node id.
 * @param $vid
 *   Quiz node version id.
 * @return
 *   Number of compulsory questions.
 */
function _quiz_get_num_always_questions($vid) {
  return db_query('SELECT COUNT(*) FROM {quiz_node_relationship} qnr
          JOIN {node} n ON n.nid = qnr.child_nid
          WHERE n.status=1 AND qnr.parent_vid = :parent_vid AND qnr.question_status = :question_status', array(':parent_vid' => $vid, ':question_status' => QUESTION_ALWAYS))->fetchField();
}

/**
 * Modifies the format fieldset.
 *
 * Adds a class to all the format fieldsets and removes unwanted strings.
 * A javascript is added by the forms theme function to make sure all format
 * selectors follows the body field format selector.
 * Used when there are multiple format selectors on one page.
 *
 * Could be a deprecated function in d7
 *
 * @param $format
 *   The format fieldset.
 */
function _quiz_format_mod(&$format) {
  $format['#attributes']['class'] = array('quiz-filter');
  if (isset($format['format'])) {
    $format['format']['guidelines']['#value'] = ' ';
    foreach ($format as $key => $value) {
      if (is_numeric($key)) {
        $format[$key]['#value'] = ' ';
      }
    }
  }
}

/**
 * Limit the year options to the years 1970 - 2030 for form items of type date.
 *
 * Some systems don't support all the dates the forms api lets you choose from.
 * This function limits the options to dates most systems support.
 *
 * @param $form_element
 *   Form element of type date.
 *
 * @return
 *   Form element with a more limited set of years to choose from.
 */
function _quiz_limit_year_options($form_element) {
  $form_element['year']['#options'] = drupal_map_assoc(range(1970, 2030));
  return $form_element;
}

/**
 * Format a number of seconds to a hh:mm:ss format.
 *
 * @param $time_in_sec
 *   Integers time in seconds.
 *
 * @return
 *   String time in min : sec format.
 */
function _quiz_format_duration($time_in_sec) {
  $hours = intval($time_in_sec / 3600);
  $min = intval(($time_in_sec - $hours * 3600) / 60);
  $sec = $time_in_sec % 60;
  if (strlen($min) == 1) {
    $min = '0' . $min;
  }
  if (strlen($sec) == 1) {
    $sec = '0' . $sec;
  }
  return "$hours:$min:$sec";
}

/**
 * Helper function used when validating integers.
 *
 * @param $value
 *   The value to be validated.
 * @param $min
 *   The minimum value $value is allowed to be.
 * @param $max
 *   The maximum value $value is allowed to be.
 *
 * @return
 *   TRUE if integer in the allowed range. FALSE otherwise.
 */
function _quiz_is_int($value, $min = 1, $max = NULL) {
  $to_return = ((string) $value === (string) (int) $value);
  // $value is not an integer.
  if (!$to_return) {
    return FALSE;
  }
  // $value is too small.
  if ($value < $min) {
    return FALSE;
  }
  // $value is too big.
  if (isset($max)) {
    if ($value > $max) {
      return FALSE;
    }
  }
  // $value is an integer in the allowed range.
  return TRUE;
}

/**
 * Helper function used when validating plain text.
 *
 * @param $value
 *   The value to be validated.
 *
 * @return
 *   TRUE if plain text FALSE otherwise.
 */
function _quiz_is_plain($value) {
  return ($value === check_plain($value));
}

/**
 * Helper function used when figuring out if a textfield or textarea is empty.
 *
 * Solves a problem with some wysiwyg editors inserting spaces and tags without content.
 *
 * @param $html
 *   The html to evaluate
 *
 * @return
 *   TRUE if the field is empty(can still be tags there) false otherwise.
 */
function _quiz_is_empty_html($html) {
  return drupal_strlen(trim(str_replace('&nbsp;', '', strip_tags($html, '<img><object><embed>')))) == 0;
}

/**
 * Check if this is the last question of the quiz
 */
function _quiz_is_last_question() {
  return count($_SESSION['quiz_' . intval(arg(1))]['quiz_questions']) < 2;
}
