You are here

trait QuizQuestionEntityTrait in Quiz 8.6

Same name and namespace in other branches
  1. 8.5 src/Entity/QuizQuestionEntityTrait.php \Drupal\quiz\Entity\QuizQuestionEntityTrait
  2. 6.x src/Entity/QuizQuestionEntityTrait.php \Drupal\quiz\Entity\QuizQuestionEntityTrait

A trait all Quiz question strongly typed entity bundles must use.

Hierarchy

File

src/Entity/QuizQuestionEntityTrait.php, line 16

Namespace

Drupal\quiz\Entity
View source
trait QuizQuestionEntityTrait {

  /*
   * QUESTION IMPLEMENTATION FUNCTIONS
   *
   * This part acts as a contract(/interface) between the question-types and the
   * rest of the system.
   *
   * Question types are made by extending these generic methods and abstract
   * methods.
   */

  /**
   * Allow question types to override the body field title.
   *
   * @return string
   *   The title for the body field.
   */
  public function getBodyFieldTitle() {
    return t('Question');
  }

  /**
   * Returns a node form to quiz_question_form.
   *
   * Adds default form elements, and fetches question type specific elements
   * from their implementation of getCreationForm.
   *
   * @param array $form_state
   *
   * @return array
   *   An renderable FAPI array.
   */
  public function getNodeForm(array &$form_state = NULL) {
    $user = \Drupal::currentUser();
    $form = array();

    // Mark this form to be processed by quiz_form_alter. quiz_form_alter will
    // among other things hide the revision fieldset if the user don't have
    // permission to control the revisioning manually.
    $form['#quiz_check_revision_access'] = TRUE;

    // Allow user to set title?
    if (user_access_test_user_access('edit question titles')) {
      $form['helper']['#theme'] = 'quiz_question_creation_form';
      $form['title'] = array(
        '#type' => 'textfield',
        '#title' => t('Title'),
        '#maxlength' => 255,
        '#default_value' => $this->node->title,
        '#required' => TRUE,
        '#description' => t('Add a title that will help distinguish this question from other questions. This will not be seen during the @quiz.', array(
          '@quiz' => _quiz_get_quiz_name(),
        )),
      );
    }
    else {
      $form['title'] = array(
        '#type' => 'value',
        '#value' => $this->node->title,
      );
    }

    // Store quiz id in the form.
    $form['quiz_nid'] = array(
      '#type' => 'hidden',
    );
    $form['quiz_vid'] = array(
      '#type' => 'hidden',
    );
    if (isset($_GET['quiz_nid']) && isset($_GET['quiz_vid'])) {
      $form['quiz_nid']['#value'] = intval($_GET['quiz_nid']);
      $form['quiz_vid']['#value'] = intval($_GET['quiz_vid']);
    }

    // Identify this node as a quiz question type so that it can be recognized
    // by other modules effectively.
    $form['is_quiz_question'] = array(
      '#type' => 'value',
      '#value' => TRUE,
    );
    if (!empty($this->node->nid)) {
      if ($properties = entity_load('quiz_question', FALSE, array(
        'nid' => $this->node->nid,
        'vid' => $this->node->vid,
      ))) {
        $quiz_question = reset($properties);
      }
    }
    $form['feedback'] = array(
      '#type' => 'text_format',
      '#title' => t('Question feedback'),
      '#default_value' => !empty($quiz_question->feedback) ? $quiz_question->feedback : '',
      '#format' => !empty($quiz_question->feedback_format) ? $quiz_question->feedback_format : filter_default_format(),
      '#description' => t('This feedback will show when configured and the user answers a question, regardless of correctness.'),
    );

    // Add question type specific content.
    $form = array_merge($form, $this
      ->getCreationForm($form_state));
    if (\Drupal::config('quiz.settings')
      ->get('auto_revisioning', 1) && $this
      ->hasBeenAnswered()) {
      $log = t('The current revision has been answered. We create a new revision so that the reports from the existing answers stays correct.');
      $this->node->revision = 1;
      $this->node->log = $log;
    }
    return $form;
  }

  /**
   * Retrieve information relevant for viewing the node.
   *
   * (This data is generally added to the node's extra field.)
   *
   * @return array
   *   Content array.
   */
  public function getNodeView() {
    $content = array();
    return $content;
  }

  /**
   * Get the form through which the user will answer the question.
   *
   * Question types should populate the form with selected values from the
   * current result if possible.
   *
   * @param FormStateInterface $form_state
   *   Form state.
   * @param QuizResultAnswer $quizQuestionResultAnswer
   *   The quiz result answer.
   *
   * @return array
   *   Form array.
   */
  public function getAnsweringForm(FormStateInterface $form_state, QuizResultAnswer $quizQuestionResultAnswer) {
    $form = array();
    $form['#element_validate'] = [
      [
        static::class,
        'getAnsweringFormValidate',
      ],
    ];
    return $form;
  }

  /**
   * Get the maximum possible score for this question.
   *
   * @return int
   */
  public abstract function getMaximumScore();

  /**
   * Finds out if a question has been answered or not.
   *
   * This function also returns TRUE if a quiz that this question belongs to
   * have been answered. Even if the question itself haven't been answered.
   * This is because the question might have been rendered and a user is about
   * to answer it...
   *
   * @return bool
   *   TRUE if question has been answered or is about to be answered...
   */
  public function hasBeenAnswered() {
    $result = \Drupal::entityQuery('quiz_result_answer')
      ->condition('question_vid', $this
      ->getRevisionId())
      ->range(0, 1)
      ->execute();
    return !empty($result);
  }

  /**
   * Determines if the user can view the correct answers.
   *
   * @return true|null
   *   TRUE if the view may include the correct answers to the question.
   */
  public function viewCanRevealCorrect() {
    $user = \Drupal::currentUser();
    $reveal_correct[] = user_access_test_user_access('view any quiz question correct response');
    $reveal_correct[] = $user
      ->id() == $this->node->uid;
    if (array_filter($reveal_correct)) {
      return TRUE;
    }
  }

  /**
   * Utility function that returns the format of the node body.
   *
   * @return string|null
   *   The format of the node body
   */
  protected function getFormat() {
    $node = isset($this->node) ? $this->node : $this->question;
    $body = field_get_items('node', $node, 'body');
    return isset($body[0]['format']) ? $body[0]['format'] : NULL;
  }

  /**
   * This may be overridden in subclasses. If it returns true,
   * it means the max_score is updated for all occurrences of
   * this question in quizzes.
   *
   * @return bool
   */
  protected function autoUpdateMaxScore() {
    return FALSE;
  }

  /**
   * Validate a user's answer.
   *
   * @param array $element
   *   The form element of this question.
   * @param mixed $form_state
   *   Form state.
   */
  public static function getAnsweringFormValidate(array &$element, FormStateInterface $form_state) {
    $quiz = \Drupal::entityTypeManager()
      ->getStorage('quiz')
      ->loadRevision($form_state
      ->getCompleteForm()['#quiz']['vid']
      ->getString());
    $qqid = $element['#array_parents'][1];

    // There was an answer submitted.

    /* @var $qra QuizResultAnswer */
    $qra = $element['#quiz_result_answer'];

    // Temporarily score the answer.
    $score = $qra
      ->score($form_state
      ->getValue('question')[$qqid]);

    // @todo kinda hacky here, we have to scale it temporarily so isCorrect()
    // works
    $qra
      ->set('points_awarded', $qra
      ->getWeightedRatio() * $score);
    if ($quiz
      ->get('repeat_until_correct')
      ->getString() && !$qra
      ->isCorrect() && $qra
      ->isEvaluated()) {
      $form_state
        ->setErrorByName('', t('The answer was incorrect. Please try again.'));

      // Show feedback after incorrect answer.
      $view_builder = Drupal::entityTypeManager()
        ->getViewBuilder('quiz_result_answer');
      $element['feedback'] = $view_builder
        ->view($qra);
      $element['feedback']['#weight'] = 100;
      $element['feedback']['#parents'] = [];
    }
  }

  /**
   * Is this question graded?
   *
   * Questions like Quiz Directions, Quiz Page, and Scale are not.
   *
   * By default, questions are expected to be gradeable
   *
   * @return bool
   */
  public function isGraded() {
    return TRUE;
  }

  /**
   * Does this question type give feedback?
   *
   * Questions like Quiz Directions and Quiz Pages do not.
   *
   * By default, questions give feedback
   *
   * @return bool
   */
  public function hasFeedback() {
    return TRUE;
  }

  /**
   * Is this "question" an actual question?
   *
   * For example, a Quiz Page is not a question, neither is a "quiz directions".
   *
   * Returning FALSE here means that the question will not be numbered, and
   * possibly other things.
   *
   * @return bool
   */
  public function isQuestion() {
    return TRUE;
  }

  /**
   * Get the response to this question in a quiz result.
   *
   * @return QuizResultAnswer
   */
  public function getResponse(QuizResult $quiz_result) {
    $entities = \Drupal::entityTypeManager()
      ->getStorage('quiz_result_answer')
      ->loadByProperties([
      'result_id' => $quiz_result
        ->id(),
      'question_id' => $this
        ->id(),
      'question_vid' => $this
        ->getRevisionId(),
    ]);
    return reset($entities);
  }

}

Members

Namesort descending Modifiers Type Description Overrides
QuizQuestionEntityTrait::autoUpdateMaxScore protected function This may be overridden in subclasses. If it returns true, it means the max_score is updated for all occurrences of this question in quizzes.
QuizQuestionEntityTrait::getAnsweringForm public function Get the form through which the user will answer the question. 7
QuizQuestionEntityTrait::getAnsweringFormValidate public static function Validate a user's answer. 5
QuizQuestionEntityTrait::getBodyFieldTitle public function Allow question types to override the body field title.
QuizQuestionEntityTrait::getFormat protected function Utility function that returns the format of the node body.
QuizQuestionEntityTrait::getMaximumScore abstract public function Get the maximum possible score for this question. 8
QuizQuestionEntityTrait::getNodeForm public function Returns a node form to quiz_question_form.
QuizQuestionEntityTrait::getNodeView public function Retrieve information relevant for viewing the node. 4
QuizQuestionEntityTrait::getResponse public function Get the response to this question in a quiz result.
QuizQuestionEntityTrait::hasBeenAnswered public function Finds out if a question has been answered or not.
QuizQuestionEntityTrait::hasFeedback public function Does this question type give feedback? 2
QuizQuestionEntityTrait::isGraded public function Is this question graded? 2
QuizQuestionEntityTrait::isQuestion public function Is this "question" an actual question? 2
QuizQuestionEntityTrait::viewCanRevealCorrect public function Determines if the user can view the correct answers.