You are here

quiz.module in Quiz 6.x

Contains quiz.module

File

quiz.module
View source
<?php

use Drupal\Core\Access\AccessResult;
use Drupal\Core\Access\AccessResultAllowed;
use Drupal\Core\Access\AccessResultForbidden;
use Drupal\Core\Database\Query\AlterableInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\field\Entity\FieldConfig;
use Drupal\quiz\Entity\Quiz;
use Drupal\quiz\Entity\QuizResult;
use Drupal\quiz\Entity\QuizResultAnswerType;
use Drupal\quiz\Entity\QuizResultType;
use Drupal\quiz\Entity\QuizType;
use Drupal\quiz\Services\QuizSessionInterface;
use Drupal\quiz\Util\QuizUtil;
use Drupal\views\ViewExecutable;

/**
 * @file
 * Contains quiz.module
 */

/**
 * Implements hook_help().
 */
function quiz_help($route_name, RouteMatchInterface $route_match) {
  switch ($route_name) {
    case 'help.page.quiz':
      return t('<p>The quiz module allows users to administer a quiz, as a sequence of questions, and track the answers given. It allows for the creation of questions (and their answers), and organizes these questions into a quiz. Its target audience includes educational institutions, online training programs, employers, and people who just want to add a fun activity for their visitors to their Drupal site.</p>
<p>For more information about Quiz, and resources on how to use Quiz, see the <a href="https://drupal.org/project/quiz">Quiz project website</a></p>');
  }
}

/**
 * Implements hook_cron().
 */
function quiz_cron() {
  $db = Drupal::database();
  $result_ids = [];

  // Remove old quiz results that haven't been finished.
  $old_rm_time = Drupal::config('quiz.settings')
    ->get('remove_partial_quiz_record');

  // $time = 0 for never.
  if ($old_rm_time) {
    $res = $db
      ->select('quiz_result', 'qnr')
      ->fields('qnr', [
      'result_id',
    ])
      ->condition('time_end', 0)
      ->where('(:request_time - time_start) > :remove_time', [
      ':request_time' => Drupal::time()
        ->getRequestTime(),
      ':remove_time' => $old_rm_time,
    ])
      ->execute();
    while ($result_id = $res
      ->fetchField()) {
      $result_ids[$result_id] = $result_id;
    }
  }

  // Remove invalid quiz results.
  $inv_rm_time = Drupal::config('quiz.settings')
    ->get('remove_invalid_quiz_record');

  // $time = 0 for never.
  if ($inv_rm_time) {
    $query = $db
      ->select('quiz_result', 'qnr');
    $query
      ->fields('qnr', [
      'result_id',
    ]);
    $query
      ->join('quiz', 'qnp', 'qnr.vid = qnp.vid');

    // If the user has a limited amount of takes we don't delete invalid
    // results.
    $db_or = $query
      ->orConditionGroup();
    $db_or
      ->isNull('qnp.takes');
    $db_or
      ->condition('qnp.takes', 0);
    $query
      ->condition($db_or);
    $query
      ->condition('qnr.is_invalid', 1);
    $query
      ->condition('qnr.time_end', Drupal::time()
      ->getRequestTime() - $inv_rm_time, '<=');
    $res = $query
      ->execute();
    while ($result_id = $res
      ->fetchField()) {
      $result_ids[$result_id] = $result_id;
    }
  }
  $quiz_results = QuizResult::loadMultiple($result_ids);
  Drupal::entityTypeManager()
    ->getStorage('quiz_result')
    ->delete($quiz_results);
}

/**
 * Implements hook_menu().
 */
function quiz_menu() {
  if (Drupal::moduleHandler()
    ->moduleExists('devel_generate')) {
    $items['admin/config/development/generate/quiz'] = [
      'title' => 'Generate quiz',
      'description' => 'Generate a given number of quizzes and questions.',
      'access arguments' => [
        'administer quiz configuration',
      ],
      'page callback' => 'drupal_get_form',
      'page arguments' => [
        'quiz_generate_form',
      ],
      'file' => 'quiz.devel.inc',
    ];
  }
  return $items;
}

/**
 * Implements hook_theme().
 */
function quiz_theme($existing, $type, $theme, $path) {
  return [
    'quiz' => [
      'render element' => 'elements',
    ],
    'quiz_question' => [
      'render element' => 'elements',
    ],
    'quiz_result' => [
      'render element' => 'elements',
    ],
    'quiz_progress' => [
      'variables' => [
        'current' => NULL,
        'total' => NULL,
      ],
    ],
    'question_selection_table' => [
      'render element' => 'form',
    ],
    'quiz_answer_result' => [
      'variables' => [],
    ],
    'quiz_report_form' => [
      'render element' => 'form',
      'path' => $path . '/theme',
      'template' => 'quiz-report-form',
    ],
    'quiz_question_score' => [
      'variables' => [
        'score' => NULL,
        'max_score' => NULL,
        'class' => NULL,
      ],
      'template' => 'quiz-question-score',
    ],
    'quiz_jumper' => [
      'variables' => [
        'total' => NULL,
        'form' => NULL,
      ],
    ],
    'quiz_pager' => [
      'variables' => [
        'total' => 0,
        'current' => 0,
        'siblings' => 0,
      ],
    ],
    'quiz_questions_page' => [
      'render element' => 'form',
    ],
  ];
}

/**
 * Implements hook_field_extra_fields().
 *
 * Add extra fields for Quiz entities.
 */
function quiz_entity_extra_field_info() {
  $extra = [];

  // Add extra fields for a take quiz button and stats table.
  foreach (QuizType::loadMultiple() as $bundle) {
    $extra['quiz'][$bundle
      ->id()] = [
      'display' => [
        'take' => [
          'label' => t('Take @quiz button', [
            '@quiz' => QuizUtil::getQuizName(),
          ]),
          'description' => t('The take button.'),
          'weight' => 10,
        ],
        'stats' => [
          'label' => t('@quiz summary', [
            '@quiz' => QuizUtil::getQuizName(),
          ]),
          'description' => t('@quiz summary', [
            '@quiz' => QuizUtil::getQuizName(),
          ]),
          'weight' => 9,
        ],
      ],
    ];
  }

  // Allow for configurable feedback bits on the quiz result answer.
  $options = quiz_get_feedback_options();
  foreach (QuizResultAnswerType::loadMultiple() as $bundle) {
    $extra['quiz_result_answer'][$bundle
      ->id()]['display']['table'] = [
      'label' => t('Feedback table'),
      'description' => t('A table of feedback.'),
      'weight' => 0,
      'visible' => TRUE,
    ];
    foreach ($options as $option => $label) {
      $extra['quiz_result_answer'][$bundle
        ->id()]['display'][$option] = [
        'label' => $label,
        'description' => t('Feedback for @label.', [
          '@label' => $label,
        ]),
        'weight' => 0,
        'visible' => FALSE,
      ];
    }
  }

  // Allow for configurable feedback bits on the quiz result.
  foreach (QuizResultType::loadMultiple() as $bundle) {
    $extra['quiz_result'][$bundle
      ->id()]['display'] = [
      'score' => [
        'label' => t('Score'),
        'description' => t('The score of the result.'),
        'weight' => 1,
      ],
      'questions' => [
        'label' => t('Questions'),
        'description' => t('The questions in this result.'),
        'weight' => 2,
      ],
      'summary' => [
        'label' => t('Summary'),
        'description' => t('The summary and pass/fail text.'),
        'weight' => 3,
      ],
    ];
  }
  return $extra;
}

/**
 * Implements hook_user_cancel().
 *
 * Reassign Quiz results to the anonymous user, if requested.
 */
function quiz_user_cancel($edit, $account, $method) {
  if ($method == 'user_cancel_reassign') {
    Drupal::database()
      ->query("UPDATE {quiz_result} SET uid = 0 WHERE uid = :uid", [
      ':uid' => $account
        ->id(),
    ]);
  }
}

/**
 * Implements hook_user_delete().
 */
function quiz_user_delete($account) {
  if (Drupal::config('quiz.settings')
    ->get('durod', 0)) {
    _quiz_delete_users_results($account
      ->id());
  }
}

/**
 * Deletes all results associated with a given user.
 *
 * @param int $uid
 *   The users id.
 */
function _quiz_delete_users_results($uid) {
  $res = Drupal::database()
    ->query("SELECT result_id FROM {quiz_result} WHERE uid = :uid", [
    ':uid' => $uid,
  ]);
  $result_ids = [];
  while ($result_id = $res
    ->fetchField()) {
    $result_ids[] = $result_id;
  }
  $controller = \Drupal::entityTypeManager()
    ->getStorage('quiz_result');
  $entities = $controller
    ->loadMultiple($result_ids);
  $controller
    ->delete($entities);
}

/**
 * Implements hook_quiz_access().
 *
 * Can a user take this quiz?
 */
function quiz_quiz_access(EntityInterface $entity, $operation, AccountInterface $account) {
  if ($operation == 'take') {
    $user_is_admin = $entity
      ->access('update');

    // Make sure this is available.
    if (!$entity
      ->get('quiz_date')
      ->isEmpty()) {

      // Compare current GMT time to the open and close dates (which should still
      // be in GMT time).
      $request_time = Drupal::time()
        ->getRequestTime();
      $quiz_date = $entity
        ->get('quiz_date')
        ->get(0)
        ->getValue();
      $quiz_open = $request_time >= strtotime($quiz_date['value']);
      $quiz_closed = $request_time >= strtotime($quiz_date['end_value']);
      if (!$quiz_open || $quiz_closed) {
        if ($user_is_admin) {
          $hooks['admin_ignore_date'] = [
            'success' => TRUE,
            'message' => (string) 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.', [
              '@quiz' => QuizUtil::getQuizName(),
            ]),
          ];
        }
        else {
          if ($quiz_closed) {
            return AccessResultForbidden::forbidden((string) t('This @quiz is closed.', [
              '@quiz' => QuizUtil::getQuizName(),
            ]));
          }
          if (!$quiz_open) {
            return AccessResultForbidden::forbidden((string) t('This @quiz is not yet open.', [
              '@quiz' => QuizUtil::getQuizName(),
            ]));
          }
        }
      }
    }

    // Check to see if this user is allowed to take the quiz again:
    if ($entity
      ->get('takes')
      ->getString() > 0) {
      $taken = Drupal::database()
        ->query('SELECT COUNT(*) AS takes FROM {quiz_result} WHERE uid = :uid AND qid = :qid', [
        ':uid' => $account
          ->id(),
        ':qid' => $entity
          ->id(),
      ])
        ->fetchField();
      $t = Drupal::translation();
      $allowed_times = $t
        ->formatPlural($entity
        ->get('takes')
        ->getString(), '1 time', '@count times');
      $taken_times = $t
        ->formatPlural($taken, '1 time', '@count times');

      // The user has already taken this quiz.
      if ($taken) {
        if (FALSE && $user_is_admin) {
          $hooks['owner_limit'] = [
            'success' => TRUE,
            'message' => (string) 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.', [
              '@quiz' => QuizUtil::getQuizName(),
            ]),
          ];
        }
        elseif ($taken >= $entity
          ->get('takes')
          ->getString()) {

          /* @var $quiz_session QuizSessionInterface */
          $quiz_session = Drupal::service('quiz.session');
          if ($entity->allow_resume->value && $entity
            ->getResumeableResult($account)) {

            // Quiz is resumable and there is an active attempt, so we should
            // allow them to finish it as it won't be creating a new attempt. This
            // is the blocker, so we do nothing here. The resume handles in the
            // take function.
          }
          elseif (!$quiz_session
            ->isTakingQuiz($entity)) {

            // If result is in session, don't check the attempt limit. @todo would
            // be to split up "take" into something like "start" and "continue" an
            // attempt.
            $hooks['attempt_limit'] = [
              'success' => FALSE,
              'message' => (string) t('You have already taken this @quiz @really. You may not take it again.', [
                '@quiz' => QuizUtil::getQuizName(),
                '@really' => $taken_times,
              ]),
            ];
          }
        }
        elseif ($entity->show_attempt_stats->value) {
          $hooks['attempt_limit'] = [
            'success' => TRUE,
            'message' => (string) t("You can only take this @quiz @allowed. You have taken it @really.", [
              '@quiz' => QuizUtil::getQuizName(),
              '@allowed' => $allowed_times,
              '@really' => $taken_times,
            ]),
            'weight' => -10,
          ];
        }
      }
    }

    // Check to see if the user is registered, and user alredy passed this quiz.
    if ($entity->show_passed->value && $account
      ->id() && $entity
      ->isPassed($account)) {
      $hooks['already_passed'] = [
        'success' => TRUE,
        'message' => (string) t('You have already passed this @quiz.', [
          '@quiz' => QuizUtil::getQuizName(),
        ]),
        'weight' => 10,
      ];
    }
    if (!empty($hooks)) {
      foreach ($hooks as $hook) {
        if (!$hook['success']) {
          return AccessResultForbidden::forbidden($hook['message'], [
            '@quiz' => QuizUtil::getQuizName(),
          ]);
        }
      }
    }
    if (!empty($hooks)) {
      foreach ($hooks as $hook) {
        if ($hook['success']) {
          if (Drupal::routeMatch()
            ->getRouteName() == 'entity.quiz.canonical') {

            // Only display if we are viewing the quiz.
            Drupal::messenger()
              ->addWarning($hook['message']);
          }
          return [
            AccessResultAllowed::allowed($hook['message'], [
              '@quiz' => QuizUtil::getQuizName(),
            ]),
          ];
        }
      }
    }

    // Check permission and node access.
    if (!Drupal::currentUser()
      ->hasPermission('access quiz') || !$entity
      ->access('view')) {
      return [
        AccessResultForbidden::forbidden((string) t('You are not allowed to take this @quiz.', [
          '@quiz' => QuizUtil::getQuizName(),
        ])),
      ];
    }
  }
}

/**
 * Retrieve question type plugins.
 *
 * @return array
 *   Array of question types.
 */
function quiz_get_question_types() {
  $pluginManager = Drupal::service('plugin.manager.quiz.question');
  $plugins = $pluginManager
    ->getDefinitions();
  if (empty($plugins)) {
    Drupal::messenger()
      ->addWarning(t('You need to install and enable at least one question type to use Quiz.'));
  }
  return $plugins;
}

/**
 * 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 array
 *   A list of quizzes.
 * @deprecated
 *
 */
function _quiz_get_quizzes($uid = 0) {
  $results = [];
  $query = Drupal::database()
    ->select('node', 'n')
    ->fields('n', [
    'nid',
    'vid',
    'title',
    'uid',
    'created',
  ])
    ->fields('u', [
    '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;
}

/**
 * Format a number of seconds to a hh:mm:ss format.
 *
 * @param $time_in_sec
 *   Integers time in seconds.
 *
 * @return string
 *   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}";
}

/**
 * Get the feedback options for Quizzes.
 */
function quiz_get_feedback_options() {
  $feedback_options = Drupal::moduleHandler()
    ->invokeAll('quiz_feedback_options');
  $view_modes = Drupal::service('entity_display.repository')
    ->getViewModes('quiz_question');
  $feedback_options["quiz_question_view_full"] = t('Question') . ': ' . 'Full';
  foreach ($view_modes as $view_mode => $info) {
    $feedback_options["quiz_question_view_" . $view_mode] = t('Question') . ': ' . $info['label'];
  }
  $feedback_options += [
    'attempt' => t('Attempt'),
    'choice' => t('Choices'),
    'correct' => t('Whether correct'),
    'score' => t('Score'),
    'answer_feedback' => t('Answer feedback'),
    'question_feedback' => t('Question feedback'),
    'solution' => t('Correct answer'),
    'quiz_feedback' => t('@quiz feedback', [
      '@quiz' => QuizUtil::getQuizName(),
    ]),
  ];
  Drupal::moduleHandler()
    ->alter('quiz_feedback_options', $feedback_options);
  return $feedback_options;
}

/**
 * Implements hook_form_FORM_ID_alter().
 *
 * Adds a checkbox for controlling field edit access to fields added to
 * quizzes.
 */
function quiz_form_field_config_edit_form_alter(&$form, FormStateInterface $form_state) {
  $field = $form_state
    ->getFormObject()
    ->getEntity();
  if ($field
    ->getTargetEntityTypeId() != 'quiz_result') {
    return;
  }
  $form['third_party_settings']['quiz']['show_field'] = [
    '#type' => 'checkbox',
    '#title' => t('Show this field on @quiz start.', [
      '@quiz' => QuizUtil::getQuizName(),
    ]),
    '#default_value' => $field
      ->getThirdPartySetting('quiz', 'show_field', TRUE),
    '#description' => t('If checked, this field will be presented when starting a quiz.'),
  ];
}

/**
 * Implements hook_field_access().
 *
 * Don't show the user fields that weren't marked as quiz result fields.
 */
function quiz_entity_field_access($operation, FieldDefinitionInterface $field_definition, AccountInterface $account, FieldItemListInterface $items = NULL) {
  if ($field_definition
    ->getTargetEntityTypeId() == 'quiz_result') {
    if (is_a($field_definition, FieldConfig::class)) {

      /* @var $field_definition FieldConfig */
      if (!$field_definition
        ->getThirdPartySetting('quiz', 'show_field')) {
        return AccessResult::forbidden('quiz_show_field');
      }
    }
  }
  return AccessResult::neutral();
}

/**
 * Implements hook_page_attachments().
 *
 * Add Quiz CSS to all pages.
 */
function quiz_page_attachments(&$page) {
  $page['#attached']['library'][] = 'quiz/styles';
}

/**
 * Implements hook_query_TAG_alter().
 *
 * Add randomization to the categorized question build generated by
 * entityQuery().
 */
function quiz_query_quiz_random_alter(AlterableInterface $query) {
  $query
    ->orderRandom();
}

/**
 * Help us with special pagination.
 *
 * Why not the Drupal theme_pager()?
 *
 * It uses query strings. We have access on each menu argument (quiz question
 * number) so we unfortunately cannot use it.
 */
function _quiz_pagination_helper($total, $perpage = NULL, $current = NULL, $siblings = NULL) {
  $result = [];
  if (isset($total, $perpage) === TRUE) {
    $result = range(1, ceil($total / $perpage));
    if (isset($current, $siblings) === TRUE) {
      if (($siblings = floor($siblings / 2) * 2 + 1) >= 1) {
        $result = array_slice($result, max(0, min(count($result) - $siblings, intval($current) - ceil($siblings / 2))), $siblings);
      }
    }
  }
  return $result;
}

/**
 * Implements hook_views_data_alter().
 *
 * Add the wildcard Quiz result answer fields to Views.
 */
function quiz_views_data_alter(&$data) {
  $data['quiz_result']['quiz_result_answers'] = [
    'title' => 'All answers',
    'help' => 'Display all answers for a Quiz result in separate columns.',
    'field' => [
      'id' => 'quiz_result_answers',
    ],
  ];
  $data['quiz_result']['quiz_result_answer'] = [
    'title' => 'Single answers',
    'help' => 'Display an answer for a specific question in a Quiz result.',
    'field' => [
      'id' => 'quiz_result_answer',
    ],
  ];
}

/**
 * Implements hook_views_pre_view().
 *
 * Replace the static field with dynamic fields.
 *
 * @todo this only works on a single quiz, with the first argument being a quiz
 * ID (e.g. quiz/1/results). Should be expanded to make argument configurable.
 */
function quiz_views_pre_view(ViewExecutable $view, $display_id, array &$args) {
  $fields = $view
    ->getHandlers('field');
  foreach ($fields as $field_name => $field) {
    if ($field['id'] == 'quiz_result_answers') {
      $quiz = Drupal::service('entity_type.manager')
        ->getStorage('quiz')
        ->load($args[0]);
      $i = 0;
      foreach ($quiz
        ->getQuestions() as $quizQuestionRelationship) {
        $quizQuestion = $quizQuestionRelationship
          ->getQuestion();
        if ($quizQuestion
          ->isGraded()) {
          $i++;
          $newfield = [];
          $newfield['id'] = 'quiz_result_answer';
          $newfield['field'] = 'quiz_result_answer';
          $newfield['table'] = 'quiz_result';
          $newfield['alter'] = [];
          $newfield['label'] = t('@num. @question', [
            '@num' => $i,
            '@question' => $quizQuestion
              ->get('title')->value,
          ]);
          $newfield['qqid'] = $quizQuestion
            ->id();
          $newfield['entity_type'] = 'quiz_result';
          $newfield['plugin_id'] = 'quiz_result_answer';
          $view
            ->setHandler($view->current_display, 'field', 'answer_' . $quizQuestion
            ->id(), $newfield);
        }
      }
      $fields = $view
        ->getHandlers('field');

      // Remove placeholder field.
      $view
        ->setHandlerOption($view->current_display, 'field', $field_name, 'exclude', TRUE);
    }
  }
}

/**
 * Prepares variables for quiz templates.
 *
 * Default template: quiz.html.twig.
 *
 * @param array $variables
 *   An associative array containing:
 *   - elements: An associative array containing rendered fields.
 *   - attributes: HTML attributes for the containing element.
 */
function template_preprocess_quiz(array &$variables) {

  /** @var Quiz $quiz */
  $quiz = $variables['elements']['#quiz'];
  $variables['quiz'] = $quiz;

  // Helpful $content variable for templates.
  $variables['content'] = [];
  foreach (Element::children($variables['elements']) as $key) {
    $variables['content'][$key] = $variables['elements'][$key];
  }
}

/**
 * Prepares variables for quiz-question templates.
 *
 * Default template: quiz-question.html.twig.
 *
 * @param array $variables
 *   An associative array containing:
 *   - elements: An associative array containing rendered fields.
 *   - attributes: HTML attributes for the containing element.
 */
function template_preprocess_quiz_question(array &$variables) {

  /** @var \Drupal\quiz\Entity\QuizResult $quiz_question */
  $quiz_question = $variables['elements']['#quiz_question'];
  $variables['quiz_question'] = $quiz_question;

  // Helpful $content variable for templates.
  $variables['content'] = [];
  foreach (Element::children($variables['elements']) as $key) {
    $variables['content'][$key] = $variables['elements'][$key];
  }
}

/**
 * Prepares variables for quiz-result templates.
 *
 * Default template: quiz-result.html.twig.
 *
 * @param array $variables
 *   An associative array containing:
 *   - elements: An associative array containing rendered fields.
 *   - attributes: HTML attributes for the containing element.
 */
function template_preprocess_quiz_result(array &$variables) {

  /** @var \Drupal\quiz\Entity\QuizResult $quiz_result */
  $quiz_result = $variables['elements']['#quiz_result'];
  $variables['quiz_result'] = $quiz_result;

  // Helpful $content variable for templates.
  $variables['content'] = [];
  foreach (Element::children($variables['elements']) as $key) {
    $variables['content'][$key] = $variables['elements'][$key];
  }
}

Functions

Namesort descending Description
quiz_cron Implements hook_cron().
quiz_entity_extra_field_info Implements hook_field_extra_fields().
quiz_entity_field_access Implements hook_field_access().
quiz_form_field_config_edit_form_alter Implements hook_form_FORM_ID_alter().
quiz_get_feedback_options Get the feedback options for Quizzes.
quiz_get_question_types Retrieve question type plugins.
quiz_help Implements hook_help().
quiz_menu Implements hook_menu().
quiz_page_attachments Implements hook_page_attachments().
quiz_query_quiz_random_alter Implements hook_query_TAG_alter().
quiz_quiz_access Implements hook_quiz_access().
quiz_theme Implements hook_theme().
quiz_user_cancel Implements hook_user_cancel().
quiz_user_delete Implements hook_user_delete().
quiz_views_data_alter Implements hook_views_data_alter().
quiz_views_pre_view Implements hook_views_pre_view().
template_preprocess_quiz Prepares variables for quiz templates.
template_preprocess_quiz_question Prepares variables for quiz-question templates.
template_preprocess_quiz_result Prepares variables for quiz-result templates.
_quiz_delete_users_results Deletes all results associated with a given user.
_quiz_format_duration Format a number of seconds to a hh:mm:ss format.
_quiz_get_quizzes Get a list of all available quizzes.
_quiz_pagination_helper Help us with special pagination.