class QuizQuestionAnsweringForm in Quiz 8.5
Same name and namespace in other branches
- 8.6 src/Form/QuizQuestionAnsweringForm.php \Drupal\quiz\Form\QuizQuestionAnsweringForm
- 6.x src/Form/QuizQuestionAnsweringForm.php \Drupal\quiz\Form\QuizQuestionAnsweringForm
Hierarchy
- class \Drupal\Core\Form\FormBase implements ContainerInjectionInterface, FormInterface uses DependencySerializationTrait, LoggerChannelTrait, MessengerTrait, LinkGeneratorTrait, RedirectDestinationTrait, UrlGeneratorTrait, StringTranslationTrait
- class \Drupal\quiz\Form\QuizQuestionAnsweringForm
Expanded class hierarchy of QuizQuestionAnsweringForm
1 file declares its use of QuizQuestionAnsweringForm
- QuizQuestionController.php in src/
Controller/ QuizQuestionController.php
File
- src/
Form/ QuizQuestionAnsweringForm.php, line 11
Namespace
Drupal\quiz\FormView source
class QuizQuestionAnsweringForm extends FormBase {
/**
* Get the form to show to the quiz taker.
*
* @param array $form
* @param array $form_state
* @param array $questions
* A list of question nodes to get answers from.
* @param int $result_id
* The result ID for this attempt.
*
* @return array
* An renderable FAPI array.
*/
public function buildForm(array $form, FormStateInterface $form_state) : array {
/* @var $questions \Drupal\quiz\Entity\QuizQuestion[] */
$questions = $form_state
->getBuildInfo()['args'][0];
$quiz_result = QuizResult::load($form_state
->getBuildInfo()['args'][1]);
$quiz = \Drupal::entityTypeManager()
->getStorage('quiz')
->loadRevision($quiz_result
->get('vid')
->getString());
// Take quiz and result in the form.
$form['#quiz'] = array(
'nid' => $quiz->nid,
'vid' => $quiz->vid,
);
$form['#quiz_result'] = $quiz_result;
if (!is_array($questions)) {
// One single question (or page?)
if ($questions
->bundle() == 'page') {
foreach ($quiz_result
->getLayout() as $qra) {
if ($qra
->get('question_id')
->getString() == $questions
->id()) {
// Found the page in the layout.
// Add the page as the first "question".
$questions = array(
$questions,
);
foreach ($quiz_result
->getLayout() as $qra2) {
// Get all page children.
if ($qra2->qqr_pid == $qra->qqr_id) {
// This question belongs in the requested page.
$questions[] = $qra2
->getQuizQuestion();
}
}
break;
}
}
}
else {
// Make this a page of 1 question.
$questions = array(
$questions
->id() => $questions,
);
}
}
$form['#attributes']['class'] = array(
'answering-form',
);
$form['#tree'] = TRUE;
foreach ($questions as $question) {
$class = Html::getClass('quiz-question-' . $question
->bundle());
// Element for a single question.
// @todo Instead of doing this load I think we can refer back to the layout.
$qra = $question
->getResponse($quiz_result);
$element = $question
->getAnsweringForm($form_state, $qra);
// Render the question using the "question" view mode.
$build = \Drupal::entityTypeManager()
->getViewBuilder('quiz_question')
->view($question, 'question');
$form['question'][$question
->id()] = array(
'#attributes' => array(
'class' => array(
$class,
),
),
'#type' => 'container',
'header' => !$qra
->get('display_number')
->isEmpty() ? array(
'#markup' => "<h2>" . t("Question @question", array(
'@question' => $qra
->get('display_number')
->getString(),
)) . "</h2>",
) : NULL,
'question' => $build,
'answer' => $element,
);
$form['question'][$question
->id()]['answer']['#quiz_result_answer'] = $qra;
$blank_and_change = $qra
->get('is_skipped')
->getString() && $quiz
->get('allow_change_blank')
->getString();
if (!$quiz
->get('allow_change')
->getString() && !$qra
->get('answer_timestamp')
->isEmpty()) {
if ($blank_and_change) {
// Allow it.
}
else {
// This question was already answered, or answering blank question is
// disabled.
$form['question'][$question
->id()]['#disabled'] = TRUE;
if (empty($form_state
->getUserInput())) {
// Only show message if not submitting.
\Drupal::messenger()
->addWarning(t('Changing answers is disabled.'));
}
}
}
if ($quiz
->get('mark_doubtful')
->getString() && $question
->isQuestion()) {
$form['question'][$question
->id()]['is_doubtful'] = array(
'#type' => 'checkbox',
'#title' => t('Doubtful?'),
'#default_value' => $qra
->get('is_doubtful')
->getString(),
);
}
}
$is_last = $quiz
->isLastQuestion();
$form['navigation']['#type'] = 'actions';
$form['navigation']['#theme'] = 'quiz_question_navigation_form';
$form['navigation']['submit_hidden'] = array(
'#weight' => -9999,
'#type' => 'submit',
'#value' => $is_last ? t('Finish') : t('Next'),
'#attributes' => array(
'style' => 'display: none',
),
);
if ($quiz
->get('backwards_navigation')
->getString() && $_SESSION['quiz'][$quiz
->id()]['current'] != 1) {
// Backwards navigation enabled, and we are looking at not the first
// question. @todo detect when on the first page.
$form['navigation']['back'] = array(
'#weight' => 10,
'#type' => 'submit',
'#value' => t('Back'),
'#submit' => array(
'::submitBack',
),
'#limit_validation_errors' => array(),
);
if ($is_last) {
$form['navigation']['#last'] = TRUE;
$form['navigation']['last_text'] = array(
'#weight' => 0,
'#markup' => '<p><em>' . t('This is the last question. Press Finish to deliver your answers') . '</em></p>',
);
}
}
$form['navigation']['submit'] = array(
'#weight' => 30,
'#type' => 'submit',
'#value' => $is_last ? t('Finish') : t('Next'),
'#ajax' => [],
);
if ($is_last && $quiz
->get('backwards_navigation')
->getString() && !$quiz
->get('repeat_until_correct')
->getString()) {
// Display a confirmation dialogue if this is the last question and a user
// is able to navigate backwards but not forced to answer correctly.
$form['#attributes']['class'][] = 'quiz-answer-confirm';
$form['#attributes']['data-confirm-message'] = t("By proceeding you won't be able to go back and edit your answers.");
$form['#attached']['library'][] = 'quiz/confirm';
}
if ($quiz
->get('allow_skipping')
->getString()) {
$form['navigation']['skip'] = array(
'#weight' => 20,
'#type' => 'submit',
'#value' => $is_last ? t('Leave blank and finish') : t('Leave blank'),
'#access' => $question->type == 'quiz_directions' ? FALSE : TRUE,
'#submit' => array(
'::submitBlank',
),
'#limit_validation_errors' => array(),
);
}
return $form;
}
public function getFormId() {
return 'quiz_question_answering_form';
}
/**
* Submit handler for the question answering form.
*
* There is no validation code here, but there may be feedback code for
* correct feedback.
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$config = $this
->config('quiz.settings');
$result_id = $form_state
->getBuildInfo()['args'][1];
$feedback_count = 0;
$quiz_result = QuizResult::load($result_id);
$quiz = \Drupal::entityTypeManager()
->getStorage('quiz')
->loadRevision($quiz_result
->get('vid')
->getString());
$time_reached = $quiz
->get('time_limit')
->getString() > 0 && \Drupal::time()
->getRequestTime() > $quiz_result
->get('time_start')
->getString() + $quiz
->get('time_limit')
->getString() + $config
->get('time_limit_buffer', 5);
$layout = $quiz_result
->getLayout();
if ($time_reached) {
// Too late.
// @todo move to quiz_question_answering_form_validate(), and then put all
// the "quiz end" logic in a sharable place. We just need to not fire the
// logic that saves all the users answers.
\Drupal::messenger()
->addError(t('The last answer was not submitted, as the time ran out.'));
}
else {
$submitted = $form_state
->getValue('question');
foreach ($layout as $qra) {
if (isset($submitted[$qra
->get('question_id')
->getString()])) {
// User submitted a response to this question.
$qqid = $qra
->get('question_id')
->getString();
$qra
->set('answer_timestamp', \Drupal::time()
->getRequestTime());
// Get the unscaled points awarded from the question response
// implementation and then apply the weighted ratio in this quiz. For
// example a MCQ question itself may be worth 4 points but worth 10
// points in this quiz. A score of 2 would mean 5 points being
// recorded.
$qra->points_awarded = $qra
->score($form_state
->getValues()['question'][$qqid]) * $qra
->getWeightedRatio();
// Mark question as not skipped, in case it was skipped previously.
$qra->is_skipped = FALSE;
// Mark as doubtful.
$qra->is_doubtful = !empty($form_state
->getValues()['question'][$qqid]['is_doubtful']) ? 1 : 0;
$qra
->save();
// Does this question type have feedback? We need to track it across pages.
$feedback_count += $qra
->getQuizQuestion()
->hasFeedback();
// Increment the question position.
$quiz_result
->setQuestion($qra
->get('number')
->getString() + 1);
}
}
}
// Wat do?
$next_number = $_SESSION['quiz'][$quiz
->id()]['current'];
if ($time_reached || !isset($layout[$_SESSION['quiz'][$quiz
->id()]['current']])) {
// If this is the last question, finalize the quiz.
$this
->submitFinalize($form, $form_state);
}
else {
// No question feedback. Go to next question.
$form_state
->setRedirect('quiz.question.take', [
'quiz' => $quiz
->id(),
'question_number' => $_SESSION['quiz'][$quiz
->id()]['current'],
]);
}
if ($quiz
->get('review_options')
->get(0) && !empty($quiz
->get('review_options')
->get(0)
->getValue()['question']) && array_filter($quiz
->get('review_options')
->get(0)
->getValue()['question']) && $feedback_count) {
// This page contains questions that can provide feedback, and question
// feedback is enabled on the quiz.
$form_state
->setRedirect('quiz.question.feedback', [
'quiz' => $quiz
->id(),
'question_number' => $next_number - 1,
]);
// @todo d8 this is used by ajax_quiz I think
//$form_state['feedback'] = TRUE;
}
}
/**
* Submit action for "leave blank".
*/
public function submitBlank(array $form, FormStateInterface $form_state) {
$quiz_result = QuizResult::load($form_state
->getBuildInfo()['args'][1]);
$quiz = \Drupal::entityTypeManager()
->getStorage('quiz')
->loadRevision($quiz_result
->get('vid')
->getString());
$questions = $quiz_result
->getLayout();
if (!empty($form_state
->getUserInput()['question'])) {
// Loop over all question inputs provided, and record them as skipped.
foreach (array_keys($form_state
->getUserInput()['question']) as $qqid) {
foreach ($quiz_result
->getLayout() as $idx => $qra) {
// Find the blank submitted question in the current layout.
if ($qra
->get('question_id')
->getString() == $qqid) {
// Reset the question and mark it as taken, so restrictions like
// backwards navigation function correctly.
$qra
->set('is_skipped', TRUE);
$qra
->set('is_correct', FALSE);
$qra
->set('points_awarded', FALSE);
$qra
->set('answer_timestamp', \Drupal::time()
->getRequestTime());
$qra
->save();
}
}
$quiz_result
->setQuestion($_SESSION['quiz'][$quiz
->id()]['current'] + 1);
}
}
else {
// Advance to next question, no input here.
$quiz_result
->setQuestion($quiz, $_SESSION['quiz'][$quiz
->id()]['current'] + 1);
}
// Advance to next question.
$form_state
->setRedirect('quiz.question.take', [
'quiz' => $quiz
->id(),
'question_number' => $_SESSION['quiz'][$quiz
->id()]['current'],
]);
$layout = $quiz_result
->getLayout();
if (!isset($layout[$_SESSION['quiz'][$quiz
->id()]['current']])) {
// If this is the last question, finalize the quiz.
$this
->submitFinalize($form, $form_state);
}
}
/**
* Helper function to finalize a quiz attempt.
*
* @see quiz_question_answering_form_submit()
* @see quiz_question_answering_form_submit_blank()
*/
function submitFinalize(array $form, FormStateInterface $form_state) {
$quiz_result = QuizResult::load($form_state
->getBuildInfo()['args'][1]);
$quiz = \Drupal::entityTypeManager()
->getStorage('quiz')
->loadRevision($quiz_result
->get('vid')
->getString());
// No more questions. Score quiz and finalize result.
$quiz_result
->finalize();
if (empty($quiz->review_options['question']) || !array_filter($quiz->review_options['question']) || empty($form_state['feedback'])) {
// Only redirect to question results if there is not question feedback.
// /** @todo D8*/
$form_state
->setRedirect('entity.quiz_result.canonical', [
'quiz' => $quiz
->id(),
'quiz_result' => $quiz_result
->id(),
]);
}
// Remove all information about this quiz from the session.
// @todo but for anon, we might have to keep some so they could access
// results
// When quiz is completed we need to make sure that even though the quiz has
// been removed from the session, that the user can still access the
// feedback for the last question, THEN go to the results page.
$_SESSION['quiz']['temp']['result_id'] = $quiz_result
->id();
unset($_SESSION['quiz'][$quiz
->id()]);
}
/**
* Submit handler for "back".
*/
function submitBack(&$form, FormStateInterface $form_state) {
// Back a question.
$quiz = \Drupal::entityTypeManager()
->getStorage('quiz')
->loadRevision($form['#quiz']['vid']
->getString());
$quiz_result = QuizResult::load($_SESSION['quiz'][$quiz
->id()]['result_id']);
$quiz_result
->setQuestion($_SESSION['quiz'][$quiz
->id()]['current'] - 1);
// I think this is for going back to a page
// $layout = $quiz_result->getLayout();
// $question = $layout[$_SESSION['quiz'][$quiz->nid]['current']];
// if (!empty($question['qqr_pid'])) {
// foreach ($layout as $question2) {
// if ($question2['qnr_id'] == $question['qqr_pid']) {
// quiz_question_goto($quiz, $question2['number']);
// }
// }
// }
$form_state
->setRedirect('quiz.question.take', [
'quiz' => $quiz
->id(),
'question_number' => $_SESSION['quiz'][$quiz
->id()]['current'],
]);
}
public function validateForm(array &$form, FormStateInterface $form_state) {
$config = $this
->config('quiz.settings');
$quiz_result = QuizResult::load($form_state
->getBuildInfo()['args'][1]);
/* @var $quiz Drupal\quiz\Entity\Quiz */
$quiz = \Drupal::entityTypeManager()
->getStorage('quiz')
->loadRevision($quiz_result
->get('vid')
->getString());
$questions = $quiz_result
->getLayout();
$time_reached = !$quiz
->get('time_limit')
->isEmpty() && \Drupal::time()
->getRequestTime() > $quiz_result
->get('time_start')
->getString() + $quiz
->get('time_limit')
->getString() + $config
->get('quiz_time_limit_buffer', 5);
if ($time_reached) {
// Let's not validate anything, because the input won't get saved in submit
// either.
return;
}
}
}
Members
Name | Modifiers | Type | Description | Overrides |
---|---|---|---|---|
DependencySerializationTrait:: |
protected | property | An array of entity type IDs keyed by the property name of their storages. | |
DependencySerializationTrait:: |
protected | property | An array of service IDs keyed by property name used for serialization. | |
DependencySerializationTrait:: |
public | function | 1 | |
DependencySerializationTrait:: |
public | function | 2 | |
FormBase:: |
protected | property | The config factory. | 1 |
FormBase:: |
protected | property | The request stack. | 1 |
FormBase:: |
protected | property | The route match. | |
FormBase:: |
protected | function | Retrieves a configuration object. | |
FormBase:: |
protected | function | Gets the config factory for this form. | 1 |
FormBase:: |
private | function | Returns the service container. | |
FormBase:: |
public static | function |
Instantiates a new instance of this class. Overrides ContainerInjectionInterface:: |
87 |
FormBase:: |
protected | function | Gets the current user. | |
FormBase:: |
protected | function | Gets the request object. | |
FormBase:: |
protected | function | Gets the route match. | |
FormBase:: |
protected | function | Gets the logger for a specific channel. | |
FormBase:: |
protected | function |
Returns a redirect response object for the specified route. Overrides UrlGeneratorTrait:: |
|
FormBase:: |
public | function | Resets the configuration factory. | |
FormBase:: |
public | function | Sets the config factory for this form. | |
FormBase:: |
public | function | Sets the request stack object to use. | |
LinkGeneratorTrait:: |
protected | property | The link generator. | 1 |
LinkGeneratorTrait:: |
protected | function | Returns the link generator. | |
LinkGeneratorTrait:: |
protected | function | Renders a link to a route given a route name and its parameters. | |
LinkGeneratorTrait:: |
public | function | Sets the link generator service. | |
LoggerChannelTrait:: |
protected | property | The logger channel factory service. | |
LoggerChannelTrait:: |
protected | function | Gets the logger for a specific channel. | |
LoggerChannelTrait:: |
public | function | Injects the logger channel factory. | |
MessengerTrait:: |
protected | property | The messenger. | 29 |
MessengerTrait:: |
public | function | Gets the messenger. | 29 |
MessengerTrait:: |
public | function | Sets the messenger. | |
QuizQuestionAnsweringForm:: |
public | function |
Get the form to show to the quiz taker. Overrides FormInterface:: |
|
QuizQuestionAnsweringForm:: |
public | function |
Returns a unique string identifying the form. Overrides FormInterface:: |
|
QuizQuestionAnsweringForm:: |
function | Submit handler for "back". | ||
QuizQuestionAnsweringForm:: |
public | function | Submit action for "leave blank". | |
QuizQuestionAnsweringForm:: |
function | Helper function to finalize a quiz attempt. | ||
QuizQuestionAnsweringForm:: |
public | function |
Submit handler for the question answering form. Overrides FormInterface:: |
|
QuizQuestionAnsweringForm:: |
public | function |
Form validation handler. Overrides FormBase:: |
|
RedirectDestinationTrait:: |
protected | property | The redirect destination service. | 1 |
RedirectDestinationTrait:: |
protected | function | Prepares a 'destination' URL query parameter for use with \Drupal\Core\Url. | |
RedirectDestinationTrait:: |
protected | function | Returns the redirect destination service. | |
RedirectDestinationTrait:: |
public | function | Sets the redirect destination service. | |
StringTranslationTrait:: |
protected | property | The string translation service. | 1 |
StringTranslationTrait:: |
protected | function | Formats a string containing a count of items. | |
StringTranslationTrait:: |
protected | function | Returns the number of plurals supported by a given language. | |
StringTranslationTrait:: |
protected | function | Gets the string translation service. | |
StringTranslationTrait:: |
public | function | Sets the string translation service to use. | 2 |
StringTranslationTrait:: |
protected | function | Translates a string to the current language or to a given language. | |
UrlGeneratorTrait:: |
protected | property | The url generator. | |
UrlGeneratorTrait:: |
protected | function | Returns the URL generator service. | |
UrlGeneratorTrait:: |
public | function | Sets the URL generator service. | |
UrlGeneratorTrait:: |
protected | function | Generates a URL or path for a specific route based on the given parameters. |