closedquestion-8.x-3.x-dev/src/Question/CqQuestionSequence.php
src/Question/CqQuestionSequence.php
<?php
namespace Drupal\closedquestion\Question;
use Drupal\closedquestion\Entity\ClosedQuestionInterface;
use Drupal\closedquestion\Question\Mapping\CqFeedback;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Url;
/**
* Class CqQuestionSequence.
*
* Implementation of the Sequence question type. A Sequence is a set of
* questions that behave as being one multi-part question.
*
* @package Drupal\closedquestion\Question
*/
class CqQuestionSequence extends CqQuestionAbstract implements CqListenerQuestionInterface {
/**
* The list of questions in the sequence.
*
* @var \Drupal\closedquestion\Question\CqQuestionInterface[]
*/
private $subQuestions = array();
/**
* The index of the question in the sequence.
*
* @var int
*/
private $currentIndex = 0;
/**
* The bit of html with the links to the previous and next questions.
*
* @var string
*/
private $backNext = '';
/**
* Toggle to see if the back and next links have been generated yet.
*
* @var bool
*/
private $backNextFixed = FALSE;
/**
* Constructs a Select&Order question object.
*
* @param CqUserAnswerInterface $userAnswer
* The CqUserAnswerInterface to use for storing the student's answer.
* @param \Drupal\closedquestion\Entity\ClosedQuestionInterface $closedQuestion
* Closed question entity.
*/
public function __construct(CqUserAnswerInterface $userAnswer, ClosedQuestionInterface $closedQuestion) {
parent::__construct();
$this->userAnswer = $userAnswer;
$this->closedQuestion = $closedQuestion;
}
/**
* Implements CqQuestionAbstract::getFeedbackItems()
*/
public function getFeedbackItems() {
$feedback = $this->subQuestions[$this->currentIndex]->getFeedbackItems();
return array_merge($feedback, $this->fireGetExtraFeedbackItems($this, $this->getTries()));
}
/**
* Overrides CqQuestionAbstract::getFeedbackFormItem()
*/
public function getFeedbackFormItem() {
return $this->subQuestions[$this->currentIndex]->getFeedbackFormItem();
}
/**
* Implements CqQuestionAbstract::submitAnswer()
*/
public function submitAnswer($form, FormStateInterface $form_state) {
$this->subQuestions[$this->currentIndex]->submitAnswer($form, $form_state);
$this->isCorrect(TRUE);
$this->userAnswer->store();
$this->fixBackNextLinks();
}
/**
* Implements CqQuestionAbstract::checkCorrect()
*/
public function checkCorrect() {
$retval = TRUE;
$tries = 0;
foreach ($this->subQuestions as $question) {
$tries += max(0, $question->onceCorrect() - 1);
if (!$question->isCorrect()) {
$retval = FALSE;
}
}
$this->userAnswer->setTries($tries);
return $retval;
}
/**
* Implements CqQuestionAbstract::getOutput()
*/
public function getOutput() {
$this->initialise();
if (!$this->backNextFixed) {
$this->fixBackNextLinks();
}
$form['backnext'] = array(
'#type' => 'item',
'#markup' => $this->backNext,
'#prefix' => '<h2>',
'#suffix' => '</h2>',
);
if (isset($this->subQuestions[$this->currentIndex])) {
$form['question'] = array(
'cur_question' => $this->subQuestions[$this->currentIndex]->getOutput(),
'#prefix' => $this->prefix,
'#suffix' => $this->postfix,
);
}
return $form;
}
/**
* Overrides CqQuestionAbstract::loadXml()
*/
public function loadXml(\DOMNode $dom) {
parent::loadXml($dom);
$i = 0;
foreach ($dom->childNodes as $child) {
if (mb_strtolower($child->nodeName) == 'question') {
$uac = new CqUserAnswerClient($this->userAnswer, $i);
$question = _closedquestion_closedquestion_question_factory($child->getAttribute('type'), $uac, $this->closedQuestion);
if ($question) {
$question->loadXML($child);
$this->subQuestions[] = & $question;
$question->addListener($this);
unset($question);
$i++;
}
unset($uac);
}
}
$answer = $this->userAnswer->getAnswer();
if (isset($_REQUEST['CqQS_' . $this->closedQuestion->id() . '_Step'])) {
$this->currentIndex = (int) $_REQUEST['CqQS_' . $this->closedQuestion->id() . '_Step'];
$answer['ci'] = $this->currentIndex;
$this->userAnswer->setAnswer($answer);
$this->userAnswer->store();
}
else {
$this->currentIndex = isset($answer['ci']) ? (int) $answer['ci'] : 0;
}
if ($this->currentIndex < 0 || $this->currentIndex >= count($this->subQuestions)) {
$this->currentIndex = 0;
}
}
/**
* Implements CqQuestionAbstract::getForm()
*/
public function getForm($formState) {
$retval = $this->subQuestions[$this->currentIndex]->getForm($formState);
return $retval;
}
/**
* Overrides CqQuestionAbstract::reset()
*/
public function reset() {
$this->userAnswer->reset();
}
/**
* Implements CqQuestionAbstract::getAllText()
*/
public function getAllText() {
$this->initialise();
$retval = array();
$nr = 0;
foreach ($this->subQuestions as $question) {
$nr++;
$retval['items'][] = array(
'title' => t('Sub-Question @nr', array('@nr' => $nr)),
'question' => $question->getAllText(),
);
}
$retval['#theme'] = 'closedquestion_question_sequence_text';
return $retval;
}
/**
* Creates the back and next links.
*/
private function fixBackNextLinks() {
$path = $this->usedPath;
if ($path) {
/** @var \Drupal\Core\Url|bool $url */
$url = \Drupal::service('path-validator')->getUrlIfValid($path);
$routeName = $url ? $url->getRouteName() : '<current>';
}
else {
$routeName = '<current>';
}
$prevUrl = '';
$nextUrl = '';
if ($this->currentIndex > 0 && $this->subQuestions[$this->currentIndex - 1]->isCorrect()) {
$prevUrl = Url::fromRoute(
$routeName, [], [
'query' => [
'CqQS_' . $this->closedQuestion->id() . '_Step' => $this->currentIndex - 1,
],
]
)->toString();
}
if ($this->currentIndex + 1 < count($this->subQuestions) && $this->subQuestions[$this->currentIndex]->isCorrect()) {
$nextUrl = Url::fromRoute(
$routeName, [], [
'query' => [
'CqQS_' . $this->closedQuestion->id() . '_Step' => $this->currentIndex + 1,
],
]
)->toString();
}
$variables = array(
'#theme' => 'closedquestion_sequence_back_next',
'#index' => $this->currentIndex,
'#total' => count($this->subQuestions),
'#prev_url' => $prevUrl,
'#next_url' => $nextUrl,
);
$this->backNext = $this->renderer->render($variables)->__toString();
$this->backNextFixed = TRUE;
}
/**
* Implements CqQuestionAbstract::FirstSolutionFound()
*/
public function firstSolutionFound($tries) {
// A sub question is answered correctly, but we don't need that info.
}
/**
* Implements CqQuestionAbstract::getExtraFeedbackItems()
*/
public function getExtraFeedbackItems($caller, $tries) {
$retval = [];
if (!empty($this->backNext)) {
$fbItem = new CqFeedback();
$fbItem->initWithValues($this->backNext, 0, 9999);
$retval[] = $fbItem;
}
return array_merge($retval, $this->fireGetExtraFeedbackItems($this, $tries));
}
}
