quiz_maker-1.0.6/src/Service/QuizSession.php
src/Service/QuizSession.php
<?php
namespace Drupal\quiz_maker\Service;
use Drupal\Component\Datetime\TimeInterface;
use Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException;
use Drupal\Component\Plugin\Exception\PluginNotFoundException;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\Logger\LoggerChannelFactoryInterface;
use Drupal\Core\Logger\LoggerChannelInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Session\SessionManagerInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\TempStore\PrivateTempStore;
use Drupal\Core\TempStore\PrivateTempStoreFactory;
use Drupal\Core\TempStore\TempStoreException;
use Drupal\quiz_maker\Entity\Question;
use Drupal\quiz_maker\Entity\QuizResult;
use Drupal\quiz_maker\QuestionInterface;
use Drupal\quiz_maker\QuizInterface;
use Drupal\quiz_maker\QuizResultInterface;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
/**
* Quiz Session class.
*/
class QuizSession {
use StringTranslationTrait;
/**
* The quiz key in quiz session.
*/
const QUIZ = 'quiz';
/**
* The quiz result key in quiz session.
*/
const QUIZ_RESULT = 'quiz_result';
/**
* The current question number key in quiz session.
*/
const CURRENT_QUESTION_NUMBER = 'current_question_number';
/**
* The questions key in quiz session.
*/
const QUESTIONS = 'questions';
/**
* The session id key in quiz session.
*/
const SESSION_ID = 'session_id';
/**
* The logger channel.
*
* @var \Drupal\Core\Logger\LoggerChannelInterface
*/
protected LoggerChannelInterface $logger;
/**
* The langcode.
*
* @var string
*/
protected string $langcode;
/**
* Private tempstore.
*
* @var \Drupal\Core\TempStore\PrivateTempStore
*/
protected PrivateTempStore $session;
/**
* Constructs a QuizManager object.
*/
public function __construct(
protected EntityTypeManagerInterface $entityTypeManager,
protected LoggerChannelFactoryInterface $loggerChannelFactory,
protected EventDispatcherInterface $eventDispatcher,
protected PrivateTempStoreFactory $privateTempStoreFactory,
protected QuizResultManager $quizResultManager,
protected AccountInterface $currentUser,
protected LanguageManagerInterface $languageManager,
protected TimeInterface $time,
protected SessionManagerInterface $sessionManager,
) {
$this->session = $privateTempStoreFactory->get('quiz_session');
$this->logger = $loggerChannelFactory->get('quiz_maker');
$this->langcode = $languageManager->getCurrentLanguage()->getId();
}
/**
* Get Quiz session data.
*
* @param \Drupal\quiz_maker\QuizInterface $quiz
* The quiz.
*
* @return array
* The data
*/
public function getSessionData(QuizInterface $quiz): array {
if (!$this->hasSession($quiz)) {
return [];
}
if ($this->currentUser->isAnonymous()) {
$session_data = $this->session->get($quiz->id());
if (isset($session_data[self::SESSION_ID]) && $session_data[self::SESSION_ID] === $this->sessionManager->getId()) {
return $session_data;
}
}
return $this->session->get($quiz->id());
}
/**
* Check if user have quiz session.
*
* @param \Drupal\quiz_maker\QuizInterface $quiz
* The Quiz.
*
* @return bool
* TRUE when has, otherwise FALSE.
*/
public function hasSession(QuizInterface $quiz): bool {
if ($this->session->get($quiz->id())) {
$session_data = $this->session->get($quiz->id());
// For Anonymous users need to check Quiz Result Session ID.
if ($this->currentUser->isAnonymous() &&
isset($session_data[self::SESSION_ID]) &&
isset($session_data[self::QUIZ_RESULT])
) {
try {
return (bool) $this->entityTypeManager->getStorage('quiz_result')
->loadByProperties([
'id' => $session_data[self::QUIZ_RESULT],
'session_id' => $session_data[self::SESSION_ID],
]);
}
catch (InvalidPluginDefinitionException | PluginNotFoundException $e) {
$this->logger->error($e->getMessage());
return FALSE;
}
}
return isset($session_data[self::QUIZ_RESULT]) && QuizResult::load($session_data[self::QUIZ_RESULT]);
}
return FALSE;
}
/**
* Start quiz session - set params.
*
* @param \Drupal\quiz_maker\QuizInterface $quiz
* The quiz.
*
* @return bool
* TRUE when session was started successfully, otherwise FALSE.
*/
public function startSession(QuizInterface $quiz): bool {
// Start session if it isn't started.
if (!$this->sessionManager->isStarted()) {
session_start();
$this->sessionManager->regenerate();
$this->sessionManager->start();
}
$quizResult = $this->quizResultManager->createQuizResult($this->currentUser, $quiz, $this->langcode, $this->sessionManager->getId());
$questions = $quiz->getQuestions();
if ($quiz->randomizeQuestionSequence()) {
shuffle($questions);
}
$questions_ids = array_map(function ($question) {
/** @var \Drupal\quiz_maker\Entity\Question $question */
return $question->id();
}, $questions);
try {
$this->session->set($quiz->id(), [
self::QUIZ => $quiz->id(),
self::QUIZ_RESULT => $quizResult->id(),
self::CURRENT_QUESTION_NUMBER => 0,
self::QUESTIONS => $questions_ids,
self::SESSION_ID => $this->sessionManager->getId(),
]);
return TRUE;
}
catch (TempStoreException $e) {
$this->logger->error($e->getMessage());
return FALSE;
}
}
/**
* Finish quiz - clear all session data.
*
* @param \Drupal\quiz_maker\QuizInterface $quiz
* The quiz.
* @param array $response_data
* The response data.
*
* @return bool
* TRUE when session was finished successfully, otherwise FALSE.
*/
public function finishSession(QuizInterface $quiz, array $response_data): bool {
if ($response_data) {
$question = $this->getCurrentQuestion($quiz);
$this->quizResultManager->updateQuizResult($this->getQuizResult($quiz), $question, $response_data, $this->langcode);
}
$this->quizResultManager->completeQuizResult($this->getQuizResult($quiz), $this->langcode);
// Delete session for quiz.
try {
$this->session->delete($quiz->id());
return TRUE;
}
catch (TempStoreException $e) {
$this->logger->error($e->getMessage());
return FALSE;
}
}
/**
* Get current question.
*
* @param \Drupal\quiz_maker\QuizInterface $quiz
* The quiz.
*
* @return \Drupal\quiz_maker\QuestionInterface|null
* The question or null.
*/
public function getCurrentQuestion(QuizInterface $quiz): ?QuestionInterface {
$session_data = $this->getSessionData($quiz);
if ($session_data) {
$question_number = $session_data[self::CURRENT_QUESTION_NUMBER];
$question_ids = $session_data[self::QUESTIONS];
if (isset($question_ids[$question_number])) {
return Question::load($question_ids[$question_number]);
}
}
return NULL;
}
/**
* Get current question number.
*
* @param \Drupal\quiz_maker\QuizInterface $quiz
* The quiz.
*
* @return int
* The question number.
*/
public function getCurrentQuestionNumber(QuizInterface $quiz): int {
$session_data = $this->getSessionData($quiz);
return $session_data[self::CURRENT_QUESTION_NUMBER] ?? 0;
}
/**
* Set current question number.
*
* @param \Drupal\quiz_maker\QuizInterface $quiz
* The quiz.
* @param int $number
* The question number.
*
* @return bool
* TRUE when question number was updated, otherwise FALSE.
*/
public function setCurrentQuestionNumber(QuizInterface $quiz, int $number): bool {
return $this->updateSessionData($quiz, self::CURRENT_QUESTION_NUMBER, $number);
}
/**
* Get quiz result.
*
* @param \Drupal\quiz_maker\QuizInterface $quiz
* The quiz.
*
* @return \Drupal\quiz_maker\QuizResultInterface|null
* The quiz result or null.
*/
public function getQuizResult(QuizInterface $quiz): ?QuizResultInterface {
$session_data = $this->getSessionData($quiz);
$quiz_result_id = $session_data[self::QUIZ_RESULT] ?? NULL;
if ($quiz_result_id) {
return QuizResult::load($quiz_result_id);
}
return NULL;
}
/**
* Get questions of current session.
*
* @param \Drupal\quiz_maker\QuizInterface $quiz
* The quiz.
*
* @return array
* The array of questions.
*/
public function getQuestions(QuizInterface $quiz): array {
$session_data = $this->getSessionData($quiz);
$question_ids = $session_data[self::QUESTIONS];
if ($question_ids) {
$question = Question::loadMultiple($question_ids);
// Reset array keys to use it as sequence.
return array_values($question);
}
return [];
}
/**
* Increment current question number.
*
* @param \Drupal\quiz_maker\QuizInterface $quiz
* The quiz.
* @param array $response_data
* Response data.
*
* @return int
* The new question number.
*/
public function incrementQuestionNumber(QuizInterface $quiz, array $response_data): int {
$question = $this->getCurrentQuestion($quiz);
$this->quizResultManager->updateQuizResult($this->getQuizResult($quiz), $question, $response_data, $this->langcode);
$question_number = $this->getCurrentQuestionNumber($quiz);
$question_number++;
$this->updateSessionData($quiz, self::CURRENT_QUESTION_NUMBER, $question_number);
return $question_number;
}
/**
* Decrement current question number.
*
* @param \Drupal\quiz_maker\QuizInterface $quiz
* The quiz.
*
* @return int
* The new question number.
*/
public function decrementQuestionNumber(QuizInterface $quiz): int {
$question_number = $this->getCurrentQuestionNumber($quiz);
$question_number--;
$this->updateSessionData($quiz, self::CURRENT_QUESTION_NUMBER, $question_number);
return $question_number;
}
/**
* Get how much time left to end of quiz if quiz has time limit.
*
* @param \Drupal\quiz_maker\QuizInterface $quiz
* The quiz.
* @param int $time_limit
* The timestamp of rime limit.
*
* @return int
* The timestamp of time left.
*/
public function getTimeLeft(QuizInterface $quiz, int $time_limit): int {
$end_time = (int) $this->getQuizResult($quiz)->get('created')->value + $time_limit;
return $end_time - $this->time->getCurrentTime();
}
/**
* Update quiz session data.
*
* @param \Drupal\quiz_maker\QuizInterface $quiz
* The quiz.
* @param string $key
* The key of data array.
* @param mixed $value
* The value of data array.
*
* @return bool
* TRUE when data was update successfully, otherwise FALSE.
*/
private function updateSessionData(QuizInterface $quiz, string $key, mixed $value): bool {
// Check if user has quiz session.
if (!$this->hasSession($quiz)) {
return FALSE;
}
// Set new key value and save.
$session_data = $this->getSessionData($quiz);
$session_data[$key] = $value;
try {
$this->session->set($quiz->id(), $session_data);
return TRUE;
}
catch (TempStoreException $e) {
$this->logger->error($e->getMessage());
return FALSE;
}
}
/**
* Kill quiz session.
*
* @param \Drupal\quiz_maker\QuizInterface $quiz
* The quiz.
*
* @return bool
* TRUE when session was killed successfully, otherwise FALSE.
*/
public function kill(QuizInterface $quiz): bool {
try {
$this->session->delete($quiz->id());
return TRUE;
}
catch (TempStoreException $e) {
$this->logger->error($e->getMessage());
return FALSE;
}
}
}
