quiz_maker-1.0.6/quiz_maker.module
quiz_maker.module
<?php
/**
* @file
* Hook implementations for the Quiz Maker module.
*/
use Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException;
use Drupal\Component\Plugin\Exception\PluginNotFoundException;
use Drupal\Core\Config\FileStorage;
use Drupal\Core\Entity\EntityFormInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityStorageException;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element;
use Drupal\Core\Url;
use Drupal\quiz_maker\Entity\Question;
use Drupal\quiz_maker\Entity\QuestionAnswer;
use Drupal\quiz_maker\Entity\QuestionType;
use Drupal\quiz_maker\Entity\QuizResultFeedbackParagraph;
use Drupal\quiz_maker\Entity\QuizResultType;
use Drupal\quiz_maker\QuizInterface;
use Drupal\quiz_maker\QuizResultInterface;
use Drupal\taxonomy\TermInterface;
use Drupal\user\UserInterface;
/**
* Implements hook_page_attachments().
*
* Add Quiz CSS to all pages.
*/
function quiz_maker_page_attachments(&$page): void {
$page['#attached']['library'][] = 'quiz_maker/styles';
}
/**
* Implements hook_theme().
*/
function quiz_maker_theme(): array {
return [
'quiz' => ['render element' => 'elements'],
'question' => ['render element' => 'elements'],
'question_answer' => ['render element' => 'elements'],
'quiz_result' => ['render element' => 'elements'],
'question_response' => ['render element' => 'elements'],
'question_matching_answer' => [
'variables' => [
'matching_question' => NULL,
'matching_answer' => NULL,
],
],
'timer' => [
'variables' => [
'hours' => NULL,
'minutes' => NULL,
'seconds' => NULL,
],
],
];
}
/**
* Prepares variables for quiz templates.
*
* Default template: quiz-maker-quiz.html.twig.
*
* @param array $variables
* An associative array containing:
* - elements: An associative array containing the quiz information and any
* fields attached to the entity.
* - attributes: HTML attributes for the containing element.
*/
function template_preprocess_quiz(array &$variables): void {
$variables['view_mode'] = $variables['elements']['#view_mode'];
foreach (Element::children($variables['elements']) as $key) {
$variables['content'][$key] = $variables['elements'][$key];
}
}
/**
* Implements hook_user_cancel().
*/
function quiz_maker_user_cancel($edit, UserInterface $account, $method): void {
switch ($method) {
case 'user_cancel_block_unpublish':
// Unpublish quizzes.
$storage = \Drupal::entityTypeManager()->getStorage('quiz');
$quiz_ids = $storage->getQuery()
->condition('uid', $account->id())
->condition('status', 1)
->accessCheck(FALSE)
->execute();
/** @var \Drupal\quiz_maker\QuizInterface $quiz */
foreach ($storage->loadMultiple($quiz_ids) as $quiz) {
$quiz->set('status', FALSE)->save();
}
// Unpublish questions.
$storage = \Drupal::entityTypeManager()->getStorage('question');
$question_ids = $storage->getQuery()
->condition('uid', $account->id())
->condition('status', 1)
->accessCheck(FALSE)
->execute();
/** @var \Drupal\quiz_maker\Entity\Question $question */
foreach ($storage->loadMultiple($question_ids) as $question) {
$question->set('status', FALSE)->save();
}
// Unpublish question answers.
$storage = \Drupal::entityTypeManager()->getStorage('question_answer');
$question_answer_ids = $storage->getQuery()
->condition('uid', $account->id())
->condition('status', 1)
->accessCheck(FALSE)
->execute();
/** @var \Drupal\quiz_maker\Entity\QuestionAnswer $question_answer */
foreach ($storage->loadMultiple($question_answer_ids) as $question_answer) {
$question_answer->set('status', FALSE)->save();
}
// Unpublish quiz results.
$storage = \Drupal::entityTypeManager()->getStorage('quiz_result');
$quiz_result_ids = $storage->getQuery()
->condition('uid', $account->id())
->condition('status', 1)
->accessCheck(FALSE)
->execute();
/** @var \Drupal\quiz_maker\QuizResultInterface $quiz_result */
foreach ($storage->loadMultiple($quiz_result_ids) as $quiz_result) {
$quiz_result->set('status', FALSE)->save();
}
// Unpublish question responses.
$storage = \Drupal::entityTypeManager()->getStorage('question_response');
$question_response_ids = $storage->getQuery()
->condition('uid', $account->id())
->condition('status', 1)
->accessCheck(FALSE)
->execute();
/** @var \Drupal\quiz_maker\Entity\QuestionResponse $question_response */
foreach ($storage->loadMultiple($question_response_ids) as $question_response) {
$question_response->set('status', FALSE)->save();
}
break;
case 'user_cancel_reassign':
// Anonymize quizzes.
$storage = \Drupal::entityTypeManager()->getStorage('quiz');
$quiz_ids = $storage->getQuery()
->condition('uid', $account->id())
->accessCheck(FALSE)
->execute();
/** @var \Drupal\quiz_maker\QuizInterface $quiz */
foreach ($storage->loadMultiple($quiz_ids) as $quiz) {
$quiz->setOwnerId(0)->save();
}
// Anonymize questions.
$storage = \Drupal::entityTypeManager()->getStorage('question');
$question_ids = $storage->getQuery()
->condition('uid', $account->id())
->accessCheck(FALSE)
->execute();
/** @var \Drupal\quiz_maker\Entity\Question $question */
foreach ($storage->loadMultiple($question_ids) as $question) {
$question->setOwnerId(0)->save();
}
// Anonymize question answers.
$storage = \Drupal::entityTypeManager()->getStorage('question_answer');
$question_answer_ids = $storage->getQuery()
->condition('uid', $account->id())
->accessCheck(FALSE)
->execute();
/** @var \Drupal\quiz_maker\Entity\QuestionAnswer $question_answer */
foreach ($storage->loadMultiple($question_answer_ids) as $question_answer) {
$question_answer->setOwnerId(0)->save();
}
// Anonymize quiz results.
$storage = \Drupal::entityTypeManager()->getStorage('quiz_result');
$quiz_result_ids = $storage->getQuery()
->condition('uid', $account->id())
->accessCheck(FALSE)
->execute();
/** @var \Drupal\quiz_maker\QuizResultInterface $quiz_result */
foreach ($storage->loadMultiple($quiz_result_ids) as $quiz_result) {
$quiz_result->setOwnerId(0)->save();
}
// Anonymize question responses.
$storage = \Drupal::entityTypeManager()->getStorage('question_response');
$question_response_ids = $storage->getQuery()
->condition('uid', $account->id())
->accessCheck(FALSE)
->execute();
/** @var \Drupal\quiz_maker\Entity\QuestionResponse $question_response */
foreach ($storage->loadMultiple($question_response_ids) as $question_response) {
$question_response->setOwnerId(0)->save();
}
break;
}
}
/**
* Implements hook_ENTITY_TYPE_predelete() for user entities.
*/
function quiz_maker_user_predelete(UserInterface $account): void {
// Delete quizzes that belong to this account.
/** @var \Drupal\Core\Entity\RevisionableStorageInterface $storage */
$storage = \Drupal::entityTypeManager()->getStorage('quiz');
$quiz_ids = $storage->getQuery()
->condition('uid', $account->id())
->accessCheck(FALSE)
->execute();
$storage->delete(
$storage->loadMultiple($quiz_ids)
);
// Delete old revisions.
$quiz_ids = $storage->getQuery()
->allRevisions()
->condition('uid', $account->id())
->accessCheck(FALSE)
->execute();
foreach (array_keys($quiz_ids) as $revision_id) {
$storage->deleteRevision($revision_id);
}
// Delete questions that belong to this account.
/** @var \Drupal\Core\Entity\RevisionableStorageInterface $storage */
$storage = \Drupal::entityTypeManager()->getStorage('question');
$question_ids = $storage->getQuery()
->condition('uid', $account->id())
->accessCheck(FALSE)
->execute();
$storage->delete(
$storage->loadMultiple($question_ids)
);
// Delete old revisions.
$question_ids = $storage->getQuery()
->allRevisions()
->condition('uid', $account->id())
->accessCheck(FALSE)
->execute();
foreach (array_keys($question_ids) as $revision_id) {
$storage->deleteRevision($revision_id);
}
// Delete question answers that belong to this account.
$storage = \Drupal::entityTypeManager()->getStorage('question_answer');
$question_answer_ids = $storage->getQuery()
->condition('uid', $account->id())
->accessCheck(FALSE)
->execute();
$storage->delete(
$storage->loadMultiple($question_answer_ids)
);
// Delete quiz results that belong to this account.
$storage = \Drupal::entityTypeManager()->getStorage('quiz_result');
$quiz_result_ids = $storage->getQuery()
->condition('uid', $account->id())
->accessCheck(FALSE)
->execute();
$storage->delete(
$storage->loadMultiple($quiz_result_ids)
);
// Delete question responses that belong to this account.
$storage = \Drupal::entityTypeManager()->getStorage('question_response');
$question_response_ids = $storage->getQuery()
->condition('uid', $account->id())
->accessCheck(FALSE)
->execute();
$storage->delete(
$storage->loadMultiple($question_response_ids)
);
}
/**
* Prepares variables for question templates.
*
* Default template: quiz-maker-question.html.twig.
*
* @param array $variables
* An associative array containing:
* - elements: An associative array containing the question information and
* any fields attached to the entity.
* - attributes: HTML attributes for the containing element.
*/
function template_preprocess_question(array &$variables): void {
$variables['view_mode'] = $variables['elements']['#view_mode'];
foreach (Element::children($variables['elements']) as $key) {
$variables['content'][$key] = $variables['elements'][$key];
}
}
/**
* Prepares variables for question answer templates.
*
* Default template: quiz-maker-question-answer.html.twig.
*
* @param array $variables
* An associative array containing:
* - elements: An associative array containing the question answer
* information and any fields attached to the entity.
* - attributes: HTML attributes for the containing element.
*/
function template_preprocess_question_answer(array &$variables): void {
$variables['view_mode'] = $variables['elements']['#view_mode'];
foreach (Element::children($variables['elements']) as $key) {
$variables['content'][$key] = $variables['elements'][$key];
}
}
/**
* Prepares variables for quiz result templates.
*
* Default template: quiz-maker-quiz-result.html.twig.
*
* @param array $variables
* An associative array containing:
* - elements: An associative array containing the quiz result information
* and any fields attached to the entity.
* - attributes: HTML attributes for the containing element.
*/
function template_preprocess_quiz_result(array &$variables): void {
/** @var \Drupal\quiz_maker\QuizResultInterface $quiz_result */
$quiz_result = $variables['elements']['#quiz_result'];
$variables['result_feedback'] = $quiz_result->getFeedback();
$variables['view_mode'] = $variables['elements']['#view_mode'];
foreach (Element::children($variables['elements']) as $key) {
$variables['content'][$key] = $variables['elements'][$key];
}
}
/**
* Prepares variables for question response templates.
*
* Default template: quiz-maker-question-response.html.twig.
*
* @param array $variables
* An associative array containing:
* - elements: An associative array containing the question response
* information and any fields attached to the entity.
* - attributes: HTML attributes for the containing element.
*/
function template_preprocess_question_response(array &$variables): void {
$variables['view_mode'] = $variables['elements']['#view_mode'];
foreach (Element::children($variables['elements']) as $key) {
$variables['content'][$key] = $variables['elements'][$key];
}
}
/**
* Implements hook_preprocess_HOOK() for quiz result.
*/
function quiz_maker_preprocess_quiz_result(array &$variables): void {
/** @var \Drupal\quiz_maker\QuizResultInterface $quiz_result */
$quiz_result = $variables['elements']['#quiz_result'];
$variables['take_quiz'] = quiz_maker_get_quiz_take_link($quiz_result->getQuiz(), t('Take quiz again'));
$variables['back_to_quiz'] = quiz_maker_get_quiz_link($quiz_result->getQuiz(), t('Go back to quiz'));
$variables['is_quiz_completed'] = in_array(
$quiz_result->getState(),
[QuizResultType::COMPLETED, QuizResultType::EVALUATED]
);
}
/**
* Implements hook_preprocess_HOOK() for quiz.
*/
function quiz_maker_preprocess_quiz(array &$variables): void {
/** @var \Drupal\quiz_maker\QuizInterface $quiz */
$quiz = $variables['elements']['#quiz'];
$label = 'Take quiz';
if (!empty($quiz->getResults(\Drupal::currentUser(), ['state' => QuizResultType::DRAFT]))) {
$label = 'Continue quiz';
}
if (is_array($quiz->allowTaking(\Drupal::currentUser()))) {
$variables['take_quiz'] = [
'#type' => 'container',
];
$reasons = $quiz->allowTaking(\Drupal::currentUser());
// Render all reasons.
foreach ($reasons as $reason) {
$variables['take_quiz'][] = [
'#type' => 'html_tag',
'#tag' => 'p',
'#value' => $reason,
];
}
}
else {
$variables['take_quiz'] = quiz_maker_get_quiz_take_link($quiz, $label);
}
// If user is Anonymous we should add session id to view args.
$variables['quiz_results'] = \Drupal::currentUser()->isAnonymous() ?
views_embed_view('quiz_results',
'user_quiz_results',
\Drupal::currentUser()->id(),
\Drupal::service('session_manager')->getId()
) :
views_embed_view(
'quiz_results',
'user_quiz_results',
\Drupal::currentUser()->id()
);
}
/**
* Get quiz take link.
*
* @param \Drupal\quiz_maker\QuizInterface $quiz
* The quiz.
* @param string $label
* The label.
*
* @return array
* The link.
*/
function quiz_maker_get_quiz_take_link(QuizInterface $quiz, string $label): array {
return [
'#type' => 'link',
'#title' => $label,
'#url' => Url::fromRoute('quiz.take', ['quiz' => $quiz->id()]),
'#attributes' => [
'class' => ['button', 'button--primary'],
],
'#access' => is_bool($quiz->allowTaking(\Drupal::currentUser())) ? $quiz->allowTaking(\Drupal::currentUser()) : FALSE,
];
}
/**
* Get quiz take link.
*
* @param \Drupal\quiz_maker\QuizInterface $quiz
* The quiz.
*
* @return array
* The link.
*/
function quiz_maker_get_quiz_link(QuizInterface $quiz, string $label): array {
return [
'#type' => 'link',
'#title' => $label,
'#url' => Url::fromRoute('entity.quiz.canonical', ['quiz' => $quiz->id()]),
'#attributes' => [
'class' => ['button', 'button--danger'],
],
];
}
/**
* Implements hook_entity_presave().
*/
function quiz_maker_entity_presave(EntityInterface $entity): void {
// Add default answers to question if it has.
if ($entity instanceof Question) {
$question_type = QuestionType::load($entity->bundle());
$question_instance = $entity->getPluginInstance();
if (!$question_instance->getAnswers()) {
$bundle = $question_type->getAnswerType();
$default_answers_data = $entity->getDefaultAnswersData();
foreach ($default_answers_data as $answer_data) {
try {
/** @var \Drupal\quiz_maker\Entity\QuestionAnswer $answer */
$answer = \Drupal::entityTypeManager()->getStorage('question_answer')->create(
[
'bundle' => $bundle,
'label' => $answer_data['label'],
'answer' => $answer_data['answer'],
'is_correct' => $answer_data['is_correct'],
]
);
$answer->save();
$entity->addAnswer($answer);
}
catch (InvalidPluginDefinitionException | PluginNotFoundException | EntityStorageException $e) {
\Drupal::logger('quiz_maker')->error($e->getMessage());
}
}
}
if ($entity->getTag() instanceof TermInterface) {
/** @var \Drupal\quiz_maker\Service\QuizHelper $quiz_helper */
$quiz_helper = \Drupal::service('quiz_maker.quiz_helper');
// Update quizzes which have tag of these questions.
$quiz_helper->updateQuizzesWithTag($entity->getTag());
}
}
if ($entity instanceof QuestionAnswer) {
$answer_instance = $entity->getPluginInstance();
if ($answer_instance->isAlwaysCorrect()) {
$answer_instance->setCorrect(TRUE);
}
if ($answer_instance->isAlwaysInCorrect()) {
$answer_instance->setCorrect(FALSE);
}
}
}
/**
* Implements hook_preprocess_views_view_table().
*/
function quiz_maker_preprocess_views_view_table(&$variables): void {
$view = $variables['view'];
if ($view->id() === 'quiz_results' && $view->current_display === 'user_quiz_results') {
$variables['attributes'] = [
'class' => ['info-table'],
];
}
}
/**
* Implements hook_form_alter().
*/
function quiz_maker_form_alter(&$form, FormStateInterface $form_state, $form_id) {
if (str_contains($form_id, 'question_form') && isset($form['answers'])) {
$form_object = $form_state->getFormObject();
if (!($form_object instanceof EntityFormInterface)) {
return;
}
$entity_bundle = $form_object->getEntity()->bundle();
$entity_type = QuestionType::load($entity_bundle);
/** @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entity_type_bundle_info */
$entity_type_bundle_info = \Drupal::service('entity_type.bundle.info');
$bundles_info = $entity_type_bundle_info->getBundleInfo('question');
if (isset($bundles_info[$entity_bundle])) {
$form['answers']['widget']['actions']['bundle'] = [
'#type' => 'hidden',
'#value' => $entity_type->getAnswerType(),
];
}
}
}
/**
* Implements hook_inline_entity_form_entity_form_alter().
*/
function quiz_maker_inline_entity_form_entity_form_alter(array &$entity_form, FormStateInterface &$form_state) {
if ($entity_form['#entity_type'] === 'question') {
$entity_bundle = $entity_form['#bundle'];
$entity_type = QuestionType::load($entity_bundle);
/** @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entity_type_bundle_info */
$entity_type_bundle_info = \Drupal::service('entity_type.bundle.info');
$bundles_info = $entity_type_bundle_info->getBundleInfo('question');
if (isset($bundles_info[$entity_bundle])) {
$entity_form['answers']['widget']['actions']['bundle'] = [
'#type' => 'hidden',
'#value' => $entity_type->getAnswerType(),
];
}
}
}
/**
* Implements hook_entity_base_field_info_alter().
*/
function quiz_maker_entity_base_field_info_alter(&$fields, EntityTypeInterface $entity_type) {
$entity_reference_fields = [
'quiz' => [
'field_name' => 'questions',
'target_entity_id' => 'question',
],
'question' => [
'field_name' => 'answers',
'target_entity_id' => 'question_answer',
],
'quiz_result' => [
'field_name' => 'responses',
'target_entity_id' => 'question_response',
],
];
if (in_array($entity_type->id(), array_keys($entity_reference_fields))) {
$data = $entity_reference_fields[$entity_type->id()];
/** @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entity_type_bundle_info */
$entity_type_bundle_info = \Drupal::service('entity_type.bundle.info');
$target_bundles = $entity_type_bundle_info->getBundleInfo($data['target_entity_id']);
$bundles = $entity_type_bundle_info->getBundleInfo($entity_type->id());
$bundles = array_keys($bundles);
$result = [];
foreach ($target_bundles as $key => $bundle) {
$result[$key] = $key;
}
$default_bundle = reset($bundles);
$fields[$data['field_name']]
->setTargetBundle($default_bundle)
->setSetting('handler_settings', ['target_bundles' => $result]);
}
if ($entity_type->id() === 'question_response') {
// Add new field "Feedback" to question response entity.
$fields['feedback'] = BaseFieldDefinition::create('text_long')
->setLabel(t('Feedback'))
->setName('feedback')
->setTranslatable(TRUE)
->setTargetEntityTypeId('question_response')
->setDisplayOptions('form', [
'type' => 'text_textarea',
'weight' => 10,
])
->setDisplayConfigurable('form', TRUE)
->setDisplayOptions('view', [
'type' => 'text_default',
'label' => 'above',
'weight' => 10,
])
->setDisplayConfigurable('view', TRUE);
}
}
/**
* Implements hook_entity_operation_alter().
*/
function quiz_maker_entity_operation_alter(array &$operations, EntityInterface $entity) {
if ($entity instanceof QuizResultInterface && $entity->getState() === QuizResultType::ON_REVIEW
) {
$review_url = Url::fromRoute('quiz_maker.quiz_result_review', ['quiz_result' => $entity->id()]);
$operations['review'] = [
'title' => t('Review'),
'weight' => -1,
'url' => $review_url,
];
}
if ($entity instanceof QuizInterface) {
$revisions_url = Url::fromRoute('entity.quiz.version_history', ['quiz' => $entity->id()]);
$operations['revisions'] = [
'title' => t('Revisions'),
'weight' => 101,
'url' => $revisions_url,
];
}
}
/**
* Implements hook_theme_suggestions_HOOK_alter().
*/
function quiz_maker_theme_suggestions_question_answer_alter(array &$suggestions, array $variables) {
if (isset($variables['elements']['#question_answer'])) {
/** @var \Drupal\quiz_maker\Entity\QuestionAnswer $question_answer */
$question_answer = $variables['elements']['#question_answer'];
if (isset($variables['elements']['#view_mode'])) {
$suggestions[] = 'question_answer__' . $variables['elements']['#view_mode'];
$suggestions[] = 'question_answer__' . $variables['elements']['#view_mode'] . '__' . $question_answer->bundle();
$suggestions[] = 'question_answer__' . $variables['elements']['#view_mode'] . '__' . $question_answer->bundle() . '__' . $question_answer->id();
}
}
}
/**
* Implements hook_theme_suggestions_HOOK_alter().
*/
function quiz_maker_theme_suggestions_question_alter(array &$suggestions, array $variables) {
if (isset($variables['elements']['#question_answer'])) {
/** @var \Drupal\quiz_maker\Entity\Question $question */
$question = $variables['elements']['#question'];
if (isset($variables['elements']['#view_mode'])) {
$suggestions[] = 'question__' . $variables['elements']['#view_mode'];
$suggestions[] = 'question__' . $variables['elements']['#view_mode'] . '__' . $question->bundle();
$suggestions[] = 'question__' . $variables['elements']['#view_mode'] . '__' . $question->bundle() . '__' . $question->id();
}
}
}
/**
* Implements hook_theme_suggestions_HOOK_alter().
*/
function quiz_maker_theme_suggestions_question_response_alter(array &$suggestions, array $variables) {
if (isset($variables['elements']['#question_response'])) {
/** @var \Drupal\quiz_maker\Entity\QuestionResponse $question_response */
$question_response = $variables['elements']['#question_response'];
if (isset($variables['elements']['#view_mode'])) {
$suggestions[] = 'question_response__' . $variables['elements']['#view_mode'];
$suggestions[] = 'question_response__' . $variables['elements']['#view_mode'] . '__' . $question_response->bundle();
$suggestions[] = 'question_response__' . $variables['elements']['#view_mode'] . '__' . $question_response->bundle() . '__' . $question_response->id();
}
}
}
/**
* Implements hook_theme_suggestions_HOOK_alter().
*/
function quiz_maker_theme_suggestions_quiz_alter(array &$suggestions, array $variables) {
if (isset($variables['elements']['#quiz'])) {
/** @var \Drupal\quiz_maker\QuizInterface $quiz */
$quiz = $variables['elements']['#quiz'];
if (isset($variables['elements']['#view_mode'])) {
$suggestions[] = 'quiz__' . $variables['elements']['#view_mode'];
$suggestions[] = 'quiz__' . $variables['elements']['#view_mode'] . '__' . $quiz->bundle();
$suggestions[] = 'quiz__' . $variables['elements']['#view_mode'] . '__' . $quiz->bundle() . '__' . $quiz->id();
}
}
}
/**
* Write config.
*
* @param string $config_name
* The config name.
* @param string $directory
* The config directory.
*/
function _quiz_maker_import_config(string $config_name, string $directory): void {
$config_path = \Drupal::service('extension.list.module')->getPath('quiz_maker') . $directory;
$source = new FileStorage($config_path);
/** @var \Drupal\Core\Config\StorageInterface $config_storage */
$config_storage = \Drupal::service('config.storage');
$config_storage->write($config_name, $source->read($config_name));
}
/**
* Implements hook_entity_type_alter().
*/
function quiz_maker_entity_type_alter(array &$entity_types): void {
if (isset($entity_types['paragraph'])) {
$entity_types['paragraph']->setClass(QuizResultFeedbackParagraph::class);
}
}
