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\Routing\RouteMatchInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\field\Entity\FieldConfig;
use Drupal\quiz\Entity\QuizQuestionRelationship;
use Drupal\quiz\Entity\QuizResult;
use Drupal\quiz\Entity\QuizResultAnswerType;
use Drupal\quiz\Plugin\QuizQuestionPluginManager;
define('QUIZ_QUESTION_RANDOM', 0);
define('QUIZ_QUESTION_ALWAYS', 1);
define('QUIZ_QUESTION_NEVER', 2);
define('QUIZ_KEEP_BEST', 0);
define('QUIZ_KEEP_LATEST', 1);
define('QUIZ_KEEP_ALL', 2);
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>The quiz module has a plethora of permission options. Unless you take care setting your permissions, the quiz module might not do everything you want it to do.</p>
<p>For more information about quiz, and resources on how to use quiz, see the <a href="http://drupal.org/project/quiz">Quiz project website</a></p>');
}
}
function quiz_cron() {
$db = Drupal::database();
$result_ids = array();
$rm_time = Drupal::config('quiz.settings')
->get('remove_partial_quiz_record');
if ($rm_time) {
$res = $db
->select('quiz_result', 'qnr')
->fields('qnr', array(
'result_id',
))
->condition('time_end', 0)
->where('(:request_time - time_start) > :remove_time', array(
':request_time' => Drupal::time()
->getRequestTime(),
':remove_time' => $rm_time,
))
->execute();
while ($result_id = $res
->fetchField()) {
$result_ids[$result_id] = $result_id;
}
}
$rm_time = Drupal::config('quiz.settings')
->get('remove_invalid_quiz_record');
if ($rm_time) {
$query = $db
->select('quiz_result', 'qnr');
$query
->fields('qnr', array(
'result_id',
));
$query
->join('quiz', 'qnp', 'qnr.vid = qnp.vid');
$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() - $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);
}
function quiz_menu() {
$items['admin/quiz/reports'] = array(
'title' => '@quiz reports and scoring',
'title arguments' => array(
'@quiz' => _quiz_get_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'),
);
if (Drupal::moduleHandler()
->moduleExists('devel_generate')) {
$items['admin/config/development/generate/quiz'] = array(
'title' => 'Generate quiz',
'description' => 'Generate a given number of quizzes and questions.',
'access arguments' => array(
'administer quiz configuration',
),
'page callback' => 'drupal_get_form',
'page arguments' => array(
'quiz_generate_form',
),
'file' => 'quiz.devel.inc',
);
}
return $items;
}
function quiz_theme($existing, $type, $theme, $path) {
return array(
'quiz_progress' => array(
'variables' => array(
'current' => NULL,
'total' => NULL,
),
),
'question_selection_table' => array(
'render element' => 'form',
),
'quiz_answer_result' => array(
'variables' => array(),
),
'quiz_report_form' => array(
'render element' => 'form',
'path' => $path . '/theme',
'template' => 'quiz-report-form',
),
'quiz_question_score' => array(
'variables' => array(
'score' => NULL,
'max_score' => NULL,
'class' => NULL,
),
'template' => 'quiz-question-score',
),
'quiz_jumper' => array(
'variables' => array(
'total' => 0,
'current' => 0,
'siblings' => 0,
),
),
'quiz_pager' => array(
'variables' => array(
'total' => 0,
'current' => 0,
'siblings' => 0,
),
),
'quiz_questions_page' => array(
'render element' => 'form',
),
);
}
function quiz_update_defaults($node) {
$user = Drupal::currentUser();
$entity = clone $node;
if (Drupal::config('quiz.settings')
->get('use_passfail', 1)) {
$entity->summary_pass = is_array($node->summary_pass) ? $node->summary_pass['value'] : $node->summary_pass;
$entity->summary_pass_format = is_array($node->summary_pass) ? $node->summary_pass['format'] : $node->summary_pass_format;
}
$entity->summary_default = is_array($node->summary_default) ? $node->summary_default['value'] : $node->summary_default;
$entity->summary_default_format = is_array($node->summary_default) ? $node->summary_default['format'] : $node->summary_default_format;
$quiz_props = clone $entity;
$quiz_props->uid = 0;
quiz_save_properties($quiz_props);
if (!empty($node->remember_settings)) {
$user_defaults = clone $quiz_props;
$user_defaults->nid = 0;
$user_defaults->vid = 0;
$user_defaults->uid = $user
->id();
quiz_save_properties($user_defaults);
}
if (!empty($node->remember_global)) {
$global_defaults = clone $quiz_props;
$global_defaults->uid = 0;
$global_defaults->nid = 0;
$global_defaults->vid = 0;
quiz_save_properties($global_defaults);
}
}
function quiz_entity_extra_field_info() {
$extra = array();
$extra['quiz']['quiz'] = array(
'display' => array(
'take' => array(
'label' => t('Take @quiz button', array(
'@quiz' => _quiz_get_quiz_name(),
)),
'description' => t('The take button.'),
'weight' => 10,
),
'stats' => array(
'label' => t('@quiz summary', array(
'@quiz' => _quiz_get_quiz_name(),
)),
'description' => t('@quiz summary', array(
'@quiz' => _quiz_get_quiz_name(),
)),
'weight' => 9,
),
),
);
$options = quiz_get_feedback_options();
foreach (QuizResultAnswerType::loadMultiple() as $bundle) {
$extra['quiz_result_answer'][$bundle
->id()]['display']['table'] = array(
'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] = array(
'label' => $label,
'description' => t('Feedback for @label.', array(
'@label' => $label,
)),
'weight' => 0,
'visible' => FALSE,
);
}
}
$extra['quiz_result']['quiz_result']['display'] = array(
'score' => array(
'label' => t('Score'),
'description' => t('The score of the result.'),
'weight' => 1,
),
'questions' => array(
'label' => t('Questions'),
'description' => t('The questions in this result.'),
'weight' => 2,
),
'summary' => array(
'label' => t('Summary'),
'description' => t('The summary and pass/fail text.'),
'weight' => 3,
),
);
return $extra;
}
function _quiz_get_node_defaults() {
return (object) array(
'allow_change' => 1,
'allow_change_blank' => 0,
'allow_jumping' => 0,
'allow_resume' => 1,
'allow_skipping' => 1,
'always_available' => TRUE,
'backwards_navigation' => 1,
'build_on_last' => '',
'keep_results' => 2,
'mark_doubtful' => 0,
'max_score' => 0,
'max_score_for_random' => 1,
'number_of_random_questions' => 0,
'pass_rate' => 75,
'quiz_always' => 1,
'quiz_close' => 0,
'quiz_open' => 0,
'randomization' => 0,
'repeat_until_correct' => 0,
'review_options' => array(
'question' => array(),
'end' => array(
'attempt' => 'attempt',
'choice' => 'choice',
'quiz_question_view_full' => 'quiz_question_view_full',
),
),
'show_attempt_stats' => 1,
'show_passed' => 1,
'summary_default' => '',
'summary_default_format' => filter_fallback_format(),
'summary_pass' => '',
'summary_pass_format' => filter_fallback_format(),
'takes' => 0,
'time_limit' => 0,
'result_type' => 'quiz_result',
);
}
function quiz_node_presave($node) {
if ($node->type == 'quiz') {
if (!empty($node->aid) && ($aid = actions_function_lookup($node->aid))) {
$node->aid = $aid;
}
if (Drupal::config('quiz.settings')
->get('auto_revisioning', 1)) {
$node->revision = quiz_has_been_answered($node) ? 1 : 0;
}
$defaults = quiz_get_defaults();
foreach ($defaults as $property => $value) {
if (!isset($node->{$property})) {
$node->{$property} = $defaults->{$property};
}
}
}
if (isset($node->is_quiz_question) && Drupal::config('quiz.settings')
->get('auto_revisioning', 1)) {
$node->revision = quiz_question_has_been_answered($node) ? 1 : 0;
}
}
function quiz_node_prepare($node) {
if ($node->type == 'quiz' && !isset($node->nid)) {
if (arg(0) == 'node') {
$user = Drupal::currentUser();
if (!node_load_multiple(array(), array(
'uid' => $user
->id(),
'type' => 'quiz',
))) {
Drupal::messenger()
->addMessage(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_get_quiz_name(),
)));
}
}
$settings = quiz_get_defaults();
foreach ($settings as $key => $value) {
if (!isset($node->{$key})) {
$node->{$key} = $value;
}
}
}
if (isset($node->is_quiz_question)) {
if (Drupal::config('quiz.settings')
->get('auto_revisioning', 1)) {
$node->revision = quiz_question_has_been_answered($node) ? 1 : 0;
}
}
}
function quiz_user_cancel($edit, $account, $method) {
if ($method == 'user_cancel_reassign') {
db_query("UPDATE {quiz_result} SET uid = 0 WHERE uid = :uid", array(
':uid' => $account
->id(),
));
}
}
function quiz_user_delete($account) {
if (Drupal::config('quiz.settings')
->get('durod', 0)) {
_quiz_delete_users_results($account
->id());
}
}
function _quiz_delete_users_results($uid) {
$res = db_query("SELECT result_id FROM {quiz_result} WHERE uid = :uid", array(
':uid' => $uid,
));
$result_ids = array();
while ($result_id = $res
->fetchField()) {
$result_ids[] = $result_id;
}
entity_delete_multiple('quiz_result', $result_ids);
}
function quiz_is_passed($uid, $nid, $vid) {
$passed = Drupal::database()
->query('SELECT COUNT(result_id) AS passed_count FROM {quiz_result} qnrs
INNER JOIN {quiz} USING (vid, qid)
WHERE qnrs.vid = :vid
AND qnrs.qid = :qid
AND qnrs.uid = :uid
AND score >= pass_rate', array(
':vid' => $vid,
':qid' => $nid,
':uid' => $uid,
))
->fetchField();
return $passed !== FALSE && $passed > 0;
}
function quiz_quiz_access(EntityInterface $entity, $operation, AccountInterface $account) {
if ($operation == 'take') {
$user_is_admin = $entity
->access('update');
if (!$entity
->get('quiz_date')
->isEmpty()) {
$quiz_open = Drupal::time()
->getRequestTime() >= strtotime($entity
->get('quiz_date')
->get(0)
->getValue()['value']);
$quiz_closed = Drupal::time()
->getRequestTime() >= strtotime($entity
->get('quiz_date')
->get(0)
->getValue()['end_value']);
if (!$quiz_open || $quiz_closed) {
if ($user_is_admin) {
$hooks['admin_ignore_date'] = array(
'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.', array(
'@quiz' => _quiz_get_quiz_name(),
)),
);
}
else {
if ($quiz_closed) {
return AccessResultForbidden::forbidden((string) t('This @quiz is closed.', array(
'@quiz' => _quiz_get_quiz_name(),
)));
}
if (!$quiz_open) {
return AccessResultForbidden::forbidden((string) t('This @quiz is not yet open.', array(
'@quiz' => _quiz_get_quiz_name(),
)));
}
}
}
}
if ($entity
->get('takes')
->getString() > 0) {
$taken = db_query("SELECT COUNT(*) AS takes FROM {quiz_result} WHERE uid = :uid AND qid = :qid", array(
':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');
if ($taken) {
if (false && $user_is_admin) {
$hooks['owner_limit'] = array(
'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.', array(
'@quiz' => _quiz_get_quiz_name(),
)),
);
}
elseif ($taken >= $entity
->get('takes')
->getString()) {
if ($entity->allow_resume && $entity
->getResumeableResult($account)) {
}
elseif (!isset($_SESSION['quiz'][$entity
->id()])) {
$hooks['attempt_limit'] = array(
'success' => FALSE,
'message' => (string) t('You have already taken this @quiz @really. You may not take it again.', array(
'@quiz' => _quiz_get_quiz_name(),
'@really' => $taken_times,
)),
);
}
}
elseif ($entity->show_attempt_stats) {
$hooks['attempt_limit'] = array(
'success' => TRUE,
'message' => (string) t("You can only take this @quiz @allowed. You have taken it @really.", array(
'@quiz' => _quiz_get_quiz_name(),
'@allowed' => $allowed_times,
'@really' => $taken_times,
)),
'weight' => -10,
);
}
}
}
if ($entity->show_passed && $account
->id() && quiz_is_passed($account
->id(), $entity
->id(), $entity
->getRevisionId())) {
$hooks['already_passed'] = array(
'success' => TRUE,
'message' => (string) t('You have already passed this @quiz.', array(
'@quiz' => _quiz_get_quiz_name(),
)),
'weight' => 10,
);
}
if (!empty($hooks)) {
foreach ($hooks as $hook) {
if (!$hook['success']) {
return AccessResultForbidden::forbidden($hook['message'], array(
'@quiz' => _quiz_get_quiz_name(),
));
}
}
}
if (!empty($hooks)) {
foreach ($hooks as $hook) {
if ($hook['success']) {
if (Drupal::routeMatch()
->getRouteName() == 'entity.quiz.canonical') {
Drupal::messenger()
->addWarning($hook['message']);
}
return [
AccessResultAllowed::allowed($hook['message'], array(
'@quiz' => _quiz_get_quiz_name(),
)),
];
}
}
}
if (!Drupal::currentUser()
->hasPermission('access quiz') || !$entity
->access('view')) {
return [
AccessResultForbidden::forbidden((string) t('You are not allowed to take this @quiz.', array(
'@quiz' => _quiz_get_quiz_name(),
))),
];
}
}
}
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;
}
function quiz_get_sub_questions($qqr_pid, &$questions) {
$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',
'qnr_id',
'qqr_pid',
'child_nid',
'child_vid',
));
$query
->addField('n', 'vid', 'latest_vid');
$query
->innerJoin('node_revision', 'nr', 'n.nid = nr.nid');
$query
->innerJoin('quiz_question_relationship', 'qnr', 'nr.vid = qnr.child_vid');
$query
->condition('qqr_pid', $qqr_pid);
$query
->orderBy('weight');
$result = $query
->execute();
foreach ($result as $question) {
$questions[] = $question;
}
}
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;
}
function _quiz_get_quiz_name() {
$quiz = Drupal::entityTypeManager()
->getDefinition('quiz');
return $quiz
->getLabel();
}
function quiz_copy_questions($node) {
$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);
$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;
}
$original_question->language = $node->language;
node_save($original_question);
$quiz_question_relationship = (object) 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,
);
entity_save('quiz_question_relationship', $quiz_question_relationship);
}
}
function quiz_get_defaults() {
$user = Drupal::currentUser();
$entity = entity_load('quiz', FALSE, array(
'uid' => $user
->id(),
'nid' => 0,
'vid' => 0,
), TRUE);
if (count($entity)) {
$defaults = clone reset($entity);
unset($defaults->nid, $defaults->uid, $defaults->vid);
return $defaults;
}
$entity = entity_load('quiz', FALSE, array(
'uid' => 0,
'nid' => 0,
'vid' => 0,
), TRUE);
if (count($entity)) {
$defaults = clone reset($entity);
unset($defaults->nid, $defaults->uid, $defaults->vid);
return $defaults;
}
return _quiz_get_node_defaults();
}
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}";
}
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 += array(
'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', array(
'@quiz' => _quiz_get_quiz_name(),
)),
);
Drupal::moduleHandler()
->alter('quiz_feedback_options', $feedback_options);
return $feedback_options;
}
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'] = array(
'#type' => 'checkbox',
'#title' => t('Show this field on @quiz start.', array(
'@quiz' => _quiz_get_quiz_name(),
)),
'#default_value' => $field
->getThirdPartySetting('quiz', 'show_field', TRUE),
'#description' => t('If checked, this field will be presented when starting a quiz.'),
);
}
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)) {
if (!$field_definition
->getThirdPartySetting('quiz', 'show_field')) {
return AccessResult::forbidden('quiz_show_field');
}
}
}
return AccessResult::neutral();
}
function quiz_entity_bundle_info() {
$type = Drupal::service('plugin.manager.quiz.question');
$question_types = $type
->getDefinitions();
$bundles = array();
foreach ($question_types as $key => $question_type) {
$bundles['quiz_question'][$key] = [
'label' => $question_type['label'],
];
}
}
function quiz_page_attachments(&$page) {
$page['#attached']['library'][] = 'quiz/styles';
}
function quiz_query_random_alter(AlterableInterface $query) {
$query
->orderRandom();
}
function _quiz_pagination_helper($total, $perpage = NULL, $current = NULL, $siblings = NULL) {
$result = array();
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;
}