View source
<?php
namespace Drupal\quiz\Entity;
use Drupal;
use Drupal\Core\Entity\ContentEntityBase;
use Drupal\Core\Entity\EntityChangedInterface;
use Drupal\Core\Entity\EntityChangedTrait;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\quiz\Util\QuizUtil;
use Drupal\rules\Engine\RulesComponent;
use Drupal\user\EntityOwnerInterface;
use Drupal\user\EntityOwnerTrait;
use function count;
use function quiz_get_feedback_options;
class QuizResult extends ContentEntityBase implements EntityOwnerInterface, EntityChangedInterface {
use EntityOwnerTrait;
use EntityChangedTrait;
public function getLayout() {
if ($this
->isNew()) {
return [];
}
$quiz_result_answers = Drupal::entityTypeManager()
->getStorage('quiz_result_answer')
->loadByProperties([
'result_id' => $this
->id(),
]);
$quiz_question_relationship = Drupal::entityTypeManager()
->getStorage('quiz_question_relationship')
->loadByProperties([
'quiz_vid' => $this
->get('vid')
->getString(),
]);
$id_qqr = [];
foreach ($quiz_question_relationship as $rel) {
$id_qqr[$rel
->get('question_id')
->getString()] = $rel;
}
$layout = [];
foreach ($quiz_result_answers as $quiz_result_answer) {
$layout[$quiz_result_answer
->get('number')
->getString()] = $quiz_result_answer;
$question_id = $quiz_result_answer
->get('question_id')
->getString();
if (isset($id_qqr[$question_id])) {
$quiz_result_answer->qqr_id = $id_qqr[$question_id]
->get('qqr_id')
->getString();
$quiz_result_answer->qqr_pid = $id_qqr[$question_id]
->get('qqr_pid')
->getString();
}
}
ksort($layout, SORT_NUMERIC);
return $layout;
}
public function label() {
$quiz = $this
->getQuiz();
$user = $this
->get('uid')
->referencedEntities()[0];
return t('@user\'s @quiz result in "@title"', [
'@user' => $user
->getDisplayName(),
'@quiz' => QuizUtil::getQuizName(),
'@title' => $quiz
->get('title')
->getString(),
]);
}
public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
$fields = parent::baseFieldDefinitions($entity_type);
$fields += static::ownerBaseFieldDefinitions($entity_type);
$fields['result_id'] = BaseFieldDefinition::create('integer')
->setRequired(TRUE)
->setLabel('Quiz result ID');
$fields['qid'] = BaseFieldDefinition::create('entity_reference')
->setRequired(TRUE)
->setSetting('target_type', 'quiz')
->setLabel(t('Quiz'));
$fields['vid'] = BaseFieldDefinition::create('integer')
->setRequired(TRUE)
->setLabel('Quiz revision ID');
$fields['time_start'] = BaseFieldDefinition::create('timestamp')
->setLabel('Attempt start time');
$fields['time_end'] = BaseFieldDefinition::create('timestamp')
->setLabel('Attempt end time');
$fields['released'] = BaseFieldDefinition::create('boolean')
->setLabel('Released')
->setDefaultValue(0);
$fields['score'] = BaseFieldDefinition::create('integer')
->setLabel('Score');
$fields['is_invalid'] = BaseFieldDefinition::create('boolean')
->setDefaultValue(0)
->setLabel('Invalid');
$fields['is_evaluated'] = BaseFieldDefinition::create('boolean')
->setDefaultValue(0)
->setLabel('Evaluated');
$fields['attempt'] = BaseFieldDefinition::create('integer')
->setRequired(TRUE)
->setDefaultValue(1)
->setLabel('Attempt');
$fields['type'] = BaseFieldDefinition::create('string')
->setRequired(TRUE)
->setLabel('Result type');
$fields['created'] = BaseFieldDefinition::create('created')
->setLabel('Created');
$fields['changed'] = BaseFieldDefinition::create('changed')
->setLabel('Changed');
return $fields;
}
public function save() {
if ($this
->get('time_start')
->isEmpty()) {
$this
->set('time_start', \Drupal::time()
->getRequestTime());
}
$new = $this
->isNew();
if ($new) {
if ($this
->get('uid')
->getString() == 0) {
$this->attempt = 1;
}
else {
$efq = \Drupal::entityQuery('quiz_result');
$result = $efq
->range(0, 1)
->condition('qid', $this
->get('qid')
->getString())
->condition('uid', $this
->get('uid')
->getString())
->sort('attempt', 'DESC')
->execute();
if (!empty($result)) {
$keys = array_keys($result);
$existing = QuizResult::load(reset($keys));
$this
->set('attempt', $existing
->get('attempt')
->getString() + 1);
}
}
}
if (!$new) {
$original = \Drupal::entityTypeManager()
->getStorage('quiz_result')
->loadUnchanged($this
->id());
}
parent::save();
if ($new) {
$quiz = \Drupal::entityTypeManager()
->getStorage('quiz')
->loadRevision($this
->get('vid')
->getString());
$questions = $quiz
->buildLayout();
if (empty($questions)) {
\Drupal::messenger()
->addError(t('Not enough questions were found. Please add more questions before trying to take this @quiz.', array(
'@quiz' => QuizUtil::getQuizName(),
)));
return FALSE;
}
if (in_array($this->build_on_last, [
'correct',
'all',
]) && ($quiz_result_old = self::findOldResult($this))) {
$quiz_result_old
->copyToQuizResult($this);
}
else {
$i = 0;
$j = 0;
foreach ($questions as $question) {
$quizQuestion = \Drupal::entityTypeManager()
->getStorage('quiz_question')
->loadRevision($question['vid']);
$quiz_result_answer = QuizResultAnswer::create([
'result_id' => $this
->id(),
'question_id' => $question['qqid'],
'question_vid' => $question['vid'],
'type' => $quizQuestion
->bundle(),
'tid' => !empty($question['tid']) ? $question['tid'] : NULL,
'number' => ++$i,
'display_number' => $quizQuestion
->isQuestion() ? ++$j : NULL,
]);
$quiz_result_answer
->save();
}
}
}
if (isset($original) && !$original
->get('is_evaluated')->value && $this
->get('is_evaluated')->value) {
$this
->maintainResults();
}
}
function getAccount() {
return $this
->get('uid')
->referencedEntities()[0];
}
function maintainResults() {
$db = \Drupal::database();
$quiz = $this
->getQuiz();
$user = $this
->getAccount();
if ($user
->id() == 0) {
return FALSE;
}
$result_ids = [];
switch ((int) $quiz
->get('keep_results')
->getString()) {
case Quiz::KEEP_ALL:
break;
case Quiz::KEEP_BEST:
$best_result_id = $db
->select('quiz_result', 'qnr')
->fields('qnr', [
'result_id',
])
->condition('qnr.qid', $quiz
->id())
->condition('qnr.uid', $user
->id())
->condition('qnr.is_evaluated', 1)
->condition('qnr.is_invalid', 0)
->orderBy('score', 'DESC')
->execute()
->fetchField();
if ($best_result_id) {
$result_ids = $db
->select('quiz_result', 'qnr')
->fields('qnr', [
'result_id',
])
->condition('qnr.qid', $quiz
->id())
->condition('qnr.uid', $user
->id())
->condition('qnr.is_evaluated', 1)
->condition('qnr.is_invalid', 0)
->condition('qnr.result_id', $best_result_id, '!=')
->execute()
->fetchCol('result_id');
}
break;
case Quiz::KEEP_LATEST:
$result_ids = $db
->select('quiz_result', 'qnr')
->fields('qnr', [
'result_id',
])
->condition('qnr.qid', $quiz
->id())
->condition('qnr.uid', $user
->id())
->condition('qnr.is_evaluated', 1)
->condition('qnr.is_invalid', 0)
->condition('qnr.result_id', $this
->id(), '!=')
->execute()
->fetchCol('result_id');
break;
}
if ($result_ids) {
$db
->update('quiz_result')
->fields([
'is_invalid' => 1,
])
->condition('result_id', $result_ids, 'IN')
->execute();
return TRUE;
}
return FALSE;
}
function setQuestion($question_number) {
$quiz_session = \Drupal::service('quiz.session');
$quiz = $this
->getQuiz();
$quiz_session
->setCurrentQuestion($quiz, $question_number);
}
function canReview($option) {
$config = Drupal::config('quiz.settings');
$quiz = \Drupal::entityTypeManager()
->getStorage('quiz')
->loadRevision($this
->get('vid')
->getString());
$admin = $quiz
->access('update');
if ($config
->get('override_admin_feedback') && $admin) {
$review_options['end'] = $config
->get('admin_review_options_end');
$review_options['question'] = $config
->get('admin_review_options_question');
}
else {
if ($quiz
->get('review_options')
->get(0)) {
$review_options = $quiz
->get('review_options')
->get(0)
->getValue();
}
else {
$review_options = [];
}
}
$all_shows = [];
$rules = \Drupal::moduleHandler()
->moduleExists('rules');
$feedbackTypes = QuizFeedbackType::loadMultiple();
foreach ($review_options as $time_key => $shows) {
$component = $feedbackTypes[$time_key]
->getComponent();
$component
->setContextValue('quiz_result', $this);
if ($component
->getExpression()
->executeWithState($component
->getState())) {
$all_shows += array_filter($shows);
}
}
return !empty($all_shows[$option]);
}
function finalize() {
$questions = $this
->getLayout();
foreach ($questions as $qinfo) {
if (empty($qinfo->is_skipped) && empty($qinfo->answer_timestamp)) {
$qinfo->is_skipped = 1;
$qinfo
->save();
}
}
$score = $this
->score();
if (!isset($score['percentage_score'])) {
$score['percentage_score'] = 0;
}
$this
->set('released', 1);
$this
->set('is_evaluated', $score['is_evaluated']);
$this
->set('score', $score['percentage_score']);
$this
->set('time_end', \Drupal::time()
->getRequestTime());
$this
->save();
return $this;
}
function score() {
$quiz_result_answers = $this
->getLayout();
$numeric_score = $possible_score = 0;
$is_evaluated = 1;
foreach ($quiz_result_answers as $quiz_result_answer) {
$numeric_score += $quiz_result_answer
->getPoints();
$possible_score += $quiz_result_answer
->getMaxScore();
if (!$quiz_result_answer
->isEvaluated()) {
$is_evaluated = 0;
}
}
return [
'question_count' => count($quiz_result_answers),
'possible_score' => $possible_score,
'numeric_score' => $numeric_score,
'percentage_score' => $possible_score == 0 ? 0 : round($numeric_score * 100 / $possible_score),
'is_evaluated' => $is_evaluated,
];
}
public function delete() {
$entities = \Drupal::entityTypeManager()
->getStorage('quiz_result_answer')
->loadByProperties([
'result_id' => $this
->id(),
]);
foreach ($entities as $entity) {
$entity
->delete();
}
parent::delete();
}
public function findOldResult() {
$efq = \Drupal::entityQuery('quiz_result');
$result = $efq
->condition('uid', $this
->get('uid')
->getString())
->condition('qid', $this
->get('qid')
->getString())
->condition('vid', $this
->get('vid')
->getString())
->condition('result_id', (int) $this
->id(), '!=')
->condition('time_start', 0, '>')
->sort('time_start', 'DESC')
->range(0, 1)
->execute();
if (!empty($result)) {
return QuizResult::load(key($result));
}
return NULL;
}
public function hasReview() {
foreach (quiz_get_feedback_options() as $option => $label) {
if ($this
->canReview($option)) {
return TRUE;
}
}
return FALSE;
}
public function toUrl($rel = 'canonical', array $options = []) {
$url = parent::toUrl($rel, $options);
$url
->setRouteParameter('quiz', $this
->get('qid')
->getString());
return $url;
}
public function getQuiz() {
return Drupal::entityTypeManager()
->getStorage('quiz')
->loadRevision($this
->get('vid')
->getString());
}
function copyToQuizResult(QuizResult $result_new) {
foreach ($this
->getLayout() as $qra) {
if (($result_new->build_on_last == 'all' || $qra
->isCorrect()) && !$qra
->isSkipped()) {
$duplicate = $qra
->createDuplicate();
$duplicate
->set('uuid', \Drupal::service('uuid')
->generate());
}
else {
$duplicate = QuizResultAnswer::create([
'type' => $qra
->bundle(),
]);
foreach ($qra
->getFields() as $name => $field) {
if (!in_array($name, [
'result_answer_id',
'uuid',
]) && is_a($field
->getFieldDefinition(), '\\Drupal\\Core\\Field\\BaseFieldDefinition')) {
$duplicate
->set($name, $field
->getValue());
}
}
}
$duplicate
->set('result_id', $result_new
->id());
$duplicate
->save();
}
}
function isTimeReached() {
$quiz = $this
->getQuiz();
$config = \Drupal::config('quiz.settings');
$time_limit = $quiz
->get('time_limit')
->getString();
$time_limit_buffer = $config
->get('time_limit_buffer', 5);
$time_start = $this
->get('time_start')
->getString();
$request_time = \Drupal::time()
->getRequestTime();
return $time_limit > 0 && $request_time > $time_start + $time_limit + $time_limit_buffer;
}
}