closedquestion-8.x-3.x-dev/src/Question/CqQuestionBalance.php

src/Question/CqQuestionBalance.php
<?php

namespace Drupal\closedquestion\Question;

use Drupal\closedquestion\Entity\ClosedQuestionInterface;
use Drupal\closedquestion\Question\Mapping\CqFeedback;
use Drupal\Core\Form\FormStateInterface;

/**
 * Class CqQuestionBalance.
 *
 * Implementation of the Balance question type.
 *
 * A balance equation takes the form of:
 * accumulation = transport + reaction
 *
 * In this question type the teacher can define a list of terms for each of
 * these and ask the student to pick the correct ones.
 *
 * @package Drupal\closedquestion\Question
 */
class CqQuestionBalance extends CqQuestionAbstract {

  /**
   * HTML containing the question-text.
   *
   * @var string
   */
  private $text;

  /**
   * The base-name used for form elements.
   *
   * @var string
   */
  private $formElementName = '';

  /**
   * List of options for the three sections of the balance equation.
   *
   * @var array
   *   An associative array containing the three keys defined in $SECTIONS. Each
   *   entry contains a list of CqOption
   */
  private $options = [];

  /**
   * List of feedback items to use as general hints.
   *
   * @var \Drupal\closedquestion\Question\Mapping\CqFeedback[]
   */
  private $hints = [];

  /**
   * The feedback to show for each section if that section is correct.
   *
   * @var array
   *   An associative array containing the three keys defined in $SECTIONS. Each
   *   value is a CqFeedback.
   */
  private $correctFeeback;

  /**
   * Should feedback be inline or not.
   *
   * Place the feedback for each option next to the option instead of in the
   * feedback are below the question?
   *
   * @var int
   *   1 to use inline feedback, any other value to not use inline feedback.
   */
  private $inlineFeedback = 0;

  /**
   * Definition section names.
   *
   * @var string[]
   */
  private $SECTIONS = array('acc', 'flow', 'prod');

  /**
   * Constructs a balance 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;
    $this->formElementName = 'xq_balance_question' . $this->closedQuestion->id() . '_';
  }

  /**
   * Implements CqQuestionAbstract::getOutput()
   */
  public function getOutput() {
    $this->initialise();
    $retval = $this->formBuilder->getForm('\Drupal\closedquestion\Form\QuestionForm', $this->closedQuestion);
    $retval['#prefix'] = $this->prefix;
    $retval['#suffix'] = $this->postfix;
    return $retval;
  }

  /**
   * Implements CqQuestionAbstract::getFeedbackItems()
   */
  public function getFeedbackItems() {
    $tries = $this->userAnswer->getTries();
    $answer = $this->userAnswer->getAnswer();
    $feedback = array();
    if ($answer == NULL) {
      // If there is no answer, don't check any further.
      return $feedback;
    }
    if (!$this->isCorrect()) {
      foreach ($this->hints as $fb) {
        if ($fb->inRange($tries)) {
          $feedback[] = $fb;
        }
      }

      if ($answer !== NULL) {
        foreach ($this->SECTIONS as $section) {

          $sectionCorrect = TRUE;
          foreach ($this->options[$section] as $optionNr => $option) {
            $optionName = $section . $optionNr;
            if (!$option->correctlyAnswered($answer[$section][$optionName])) {
              $sectionCorrect = FALSE;
            }
            if (!$this->inlineFeedback) {
              $feedbacks = $option->getFeedback($tries, $answer[$section][$optionName]);
              foreach ($feedbacks as $fb) {
                $feedback[] = $fb;
              }
            }
          }

          if ($sectionCorrect) {
            if ($this->correctFeeback[$section]) {
              $feedback[] = $this->correctFeeback[$section];
            }
          }
        }
      }
    }
    else {
      if ($this->correctFeeback != NULL) {
        $feedback[] = $this->correctFeeback['all'];
      }
    }

    // Finally, ask external systems if they want to add extra feedback.
    $feedback = array_merge($feedback, $this->fireGetExtraFeedbackItems($this, $tries));
    return $feedback;
  }

  /**
   * Parses XML section.
   *
   * Parses the part of the XML that defines the options for a section of the
   * balance equation.
   *
   * @param \DOMNode $sectionNode
   *   The XML DOMNode containing the definitions for the section.
   */
  private function parseSection(\DOMNode $sectionNode) {
    $sectionName = $sectionNode->nodeName;
    foreach ($sectionNode->childNodes as $node) {
      switch ($node->nodeName) {
        case 'option':
          $this->options[$sectionName][] = new CqOption($node, $this);
          break;

        case 'correct':
          $this->correctFeeback[$sectionName] = CqFeedback::newCqFeedback($node, $this);
          break;

        case '#text':
        case '#comment':
          break;

        default:
          $this->messenger->addMessage(t('Unknown node: @nodename', array('@nodename' => $node->nodeName)));
          break;
      }
    }
  }

  /**
   * Overrides CqQuestionAbstract::loadXml()
   */
  public function loadXml(\DOMNode $dom) {
    parent::loadXml($dom);


    foreach ($dom->childNodes as $node) {
      if (in_array($node->nodeName, $this->SECTIONS)) {
        $this->parseSection($node);
      }
      else {
        $name = mb_strtolower($node->nodeName);
        switch ($name) {
          case 'text':
            $this->text = $this->xmlLib->getTextContent($node, $this);
            break;

          case 'hint':
            $this->hints[] = CqFeedback::newCqFeedback($node, $this);
            break;

          case 'correct':
            $this->correctFeeback['all'] = CqFeedback::newCqFeedback($node, $this);
            break;

          default:
            if (!in_array($name, $this->knownElements)) {
              $this->messenger->addMessage(t('Unknown node: @nodename', array('@nodename' => $node->nodeName)));
            }
            break;
        }
      }
    }
    $attribs = $dom->attributes;
    $item = $attribs->getNamedItem('inlinefeedback');
    if ($item !== NULL) {
      $this->inlineFeedback = (int) $item->value;
    }
  }

  /**
   * Implements CqQuestionAbstract::getForm()
   */
  public function getForm($formState) {
    $answer = $this->userAnswer->getAnswer();
    $tries = $this->userAnswer->getTries();
    if ($answer === NULL) {
      $answer = array();
    }

    // The question part is themed.
    $form['question']['#theme'] = 'closedquestion_question_balance';
    $form['question']['questionText'] = array(
      '#type' => 'item',
      '#markup' => $this->text,
    );

    $optionsFinal = array();
    foreach ($this->SECTIONS as $section) {
      foreach ($this->options[$section] as $optionNr => $option) {
        $optionName = $section . $optionNr;
        $optionsFinal[$section][$optionName] = $option->getText();
        // If we use inline feedback, we add the feedback to the option.
        if ($tries > 0 && $this->inlineFeedback) {
          $render = $option->getFeedback($tries, $answer[$section][$optionName]);
          $render['#theme'] = 'closedquestion_feedback_list';
          $optionsFinal[$section][$optionName] .= $this->renderer->render($render)->__toString();
        }
      }
    }


    for ($i = 0; $i < count($this->SECTIONS); $i++) {
      $secName = $this->SECTIONS[$i];
      if (isset($answer[$secName])) {
        $answers = $answer[$secName];
      }
      else {
        $answers = array();
      }

      $form['question']['options' . $secName] = array(
        '#type' => 'checkboxes',
        '#title' => '',
        '#options' => $optionsFinal[$secName],
        '#default_value' => $answers,
      );
    }

    // Insert standard feedback and submit elements.
    $wrapper_id = 'cq-feedback-wrapper_' . $this->formElementName;
    $this->insertFeedback($form, $wrapper_id);
    $this->insertSubmit($form, $wrapper_id);
    return $form;
  }

  /**
   * Implements CqQuestionAbstract::checkCorrect()
   */
  public function checkCorrect() {
    $answer = $this->userAnswer->getAnswer();
    $correct = TRUE;
    foreach ($this->options as $sectionName => $optionList) {
      foreach ($optionList as $optionNr => $option) {
        $optionName = $sectionName . $optionNr;
        if (!$option->correctlyAnswered($answer[$sectionName][$optionName])) {
          return FALSE;
        }
      }
    }
    return $correct;
  }

  /**
   * Implements CqQuestionAbstract::submitAnswer()
   */
  public function submitAnswer($form, FormStateInterface $form_state) {
    foreach ($this->SECTIONS as $secName) {
      $answer[$secName] = $form_state->getValue('options' . $secName);
    }
    $this->userAnswer->setAnswer($answer);
    $correct = $this->isCorrect(TRUE);
    if ($this->userAnswer->answerHasChanged()) {
      if (!$correct) {
        $this->userAnswer->increaseTries();
      }
      $this->userAnswer->store();
    }
  }

  /**
   * Implements CqQuestionAbstract::getAllText()
   */
  public function getAllText() {
    $this->initialise();
    $retval = array();
    $retval['text']['#markup'] = $this->text;

    // Hints.
    $retval['hints'] = array(
      '#theme' => 'closedquestion_feedback_list',
      '#extended' => TRUE,
    );
    foreach ($this->hints as $fbitem) {
      $retval['hints']['items'][] = $fbitem->getAllText();
    }

    // Options.
    $retval['options'] = array(
      '#theme' => 'closedquestion_option_list',
      'items' => array(),
      '#extended' => TRUE,
    );
    foreach ($this->options as $name => $section) {
      $retval['options']['items'][$name] = array(
        '#theme' => 'closedquestion_option_list',
        'items' => array(),
        '#extended' => TRUE,
      );
      foreach ($section as $option) {
        $retval['options']['items'][$name]['items'][] = $option->getAllText();
      }
    }

    $retval['#theme'] = 'closedquestion_question_general_text';

    return $retval;
  }

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc