322 lines
9.8 KiB
PHP
322 lines
9.8 KiB
PHP
<?php
|
|
|
|
/**
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; under version 2
|
|
* of the License (non-upgradable).
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*
|
|
* Copyright (c) 2013-2016 (original work) Open Assessment Technologies SA (under the project TAO-PRODUCT);
|
|
*
|
|
*/
|
|
|
|
use oat\tao\model\state\StateStorage;
|
|
use qtism\common\storage\IStream;
|
|
use qtism\runtime\tests\AbstractSessionManager;
|
|
use qtism\common\storage\MemoryStream;
|
|
use qtism\runtime\storage\binary\BinaryAssessmentTestSeeker;
|
|
use qtism\runtime\storage\binary\AbstractQtiBinaryStorage;
|
|
use qtism\runtime\storage\common\StorageException;
|
|
use qtism\data\AssessmentTest;
|
|
use qtism\runtime\tests\AssessmentTestSession;
|
|
use qtism\runtime\storage\binary\QtiBinaryStreamAccess;
|
|
use oat\taoQtiTest\models\files\QtiFlysystemFileManager;
|
|
use oat\oatbox\service\ServiceManager;
|
|
use oat\oatbox\service\ServiceManagerAwareInterface;
|
|
use oat\oatbox\service\ServiceManagerAwareTrait;
|
|
|
|
/**
|
|
* A QtiSm AssessmentTestSession Storage Service implementation for TAO.
|
|
*
|
|
* It is able to retrieve test sessions related to a given user and a given
|
|
* test definition.
|
|
*
|
|
* @author Jérôme Bogaerts <jerome@taotesting.com>
|
|
*
|
|
*/
|
|
class taoQtiTest_helpers_TestSessionStorage extends AbstractQtiBinaryStorage implements ServiceManagerAwareInterface
|
|
{
|
|
use ServiceManagerAwareTrait;
|
|
use oat\oatbox\mutex\LockTrait;
|
|
|
|
/**
|
|
* The last recorded error.
|
|
*
|
|
* @var integer
|
|
*/
|
|
private $lastError = -1;
|
|
|
|
/**
|
|
* The URI (Uniform Resource Identifier) of the user the Test Session belongs to.
|
|
*
|
|
* @var string
|
|
*/
|
|
private $userUri;
|
|
|
|
/**
|
|
* @var AssessmentTestSession
|
|
*/
|
|
private static $session;
|
|
|
|
/**
|
|
* Create a new TestSessionStorage object.
|
|
*
|
|
* @param AbstractSessionManager $manager The session manager to be used to create new AssessmentTestSession and AssessmentItemSession objects.
|
|
* @param BinaryAssessmentTestSeeker $seeker The seeker making able the storage engine to index AssessmentTest's components.
|
|
* @param string $userUri The URI (Uniform Resource Identifier) of the user the Test Session belongs to.
|
|
*/
|
|
public function __construct(AbstractSessionManager $manager, BinaryAssessmentTestSeeker $seeker, $userUri)
|
|
{
|
|
parent::__construct($manager, $seeker);
|
|
$this->setUserUri($userUri);
|
|
}
|
|
|
|
/**
|
|
* Get the last retrieved error. -1 means
|
|
* no error.
|
|
*
|
|
* @return integer
|
|
*/
|
|
public function getLastError()
|
|
{
|
|
return $this->lastError;
|
|
}
|
|
|
|
/**
|
|
* Set the last retrieved error. -1 means
|
|
* no error.
|
|
*
|
|
* @param integer $lastError
|
|
*/
|
|
public function setLastError($lastError)
|
|
{
|
|
$this->lastError = $lastError;
|
|
}
|
|
|
|
/**
|
|
* Get the URI (Uniform Resource Identifier) of the user the Test Session belongs to.
|
|
*
|
|
* @return string
|
|
*/
|
|
public function getUserUri()
|
|
{
|
|
return $this->userUri;
|
|
}
|
|
|
|
/**
|
|
* Set the URI (Uniform Resource Identifier) of the user the Test Session belongs to.
|
|
*
|
|
* @param string $userUri
|
|
*/
|
|
public function setUserUri($userUri)
|
|
{
|
|
$this->userUri = $userUri;
|
|
}
|
|
|
|
/**
|
|
* @param AssessmentTest $test
|
|
* @param string $sessionId
|
|
* @param bool $forReadingOnly
|
|
* @return AssessmentTestSession
|
|
* @throws StorageException
|
|
*/
|
|
public function retrieve(AssessmentTest $test, $sessionId, $forReadingOnly = false)
|
|
{
|
|
if ($forReadingOnly === false) {
|
|
return $this->retrieveSessionInWriteMode($test, $sessionId);
|
|
} else {
|
|
return $this->retrieveSessionInReadMode($test, $sessionId);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param AssessmentTest $test
|
|
* @param string $sessionId
|
|
* @return taoQtiTest_helpers_TestSession
|
|
* @throws StorageException
|
|
*/
|
|
private function retrieveSessionInReadMode(AssessmentTest $test, string $sessionId): taoQtiTest_helpers_TestSession
|
|
{
|
|
if (!$this->sessionExists($sessionId)) {
|
|
$this->setLastError(-1);
|
|
self::$session = parent::retrieve($test, $sessionId);
|
|
self::$session->setReadOnly(true);
|
|
}
|
|
|
|
return self::$session;
|
|
}
|
|
|
|
/**
|
|
* @param AssessmentTest $test
|
|
* @param string $sessionId
|
|
* @return taoQtiTest_helpers_TestSession
|
|
* @throws StorageException
|
|
*/
|
|
private function retrieveSessionInWriteMode(AssessmentTest $test, string $sessionId): taoQtiTest_helpers_TestSession
|
|
{
|
|
if ($this->sessionExists($sessionId) && self::$session->isLocked()) {
|
|
return self::$session;
|
|
}
|
|
|
|
$this->setLastError(-1);
|
|
self::$session = parent::retrieve($test, $sessionId);
|
|
$this->lockSession(self::$session);
|
|
|
|
return self::$session;
|
|
}
|
|
|
|
/**
|
|
* @param AssessmentTest $test
|
|
* @param string $sessionId
|
|
* @return AssessmentTestSession
|
|
* @throws StorageException
|
|
*/
|
|
public function instantiate(AssessmentTest $test, $sessionId = '')
|
|
{
|
|
$session = parent::instantiate($test, $sessionId);
|
|
$this->lockSession($session);
|
|
return $session;
|
|
}
|
|
|
|
/**
|
|
* @param AssessmentTestSession $assessmentTestSession
|
|
* @throws StorageException
|
|
*/
|
|
public function persist(AssessmentTestSession $assessmentTestSession)
|
|
{
|
|
if ($assessmentTestSession->isReadOnly()) {
|
|
throw new StorageException(
|
|
'Readonly test session cannot be stored. Test session id: ' . $assessmentTestSession->getSessionId(),
|
|
StorageException::PERSITANCE
|
|
);
|
|
}
|
|
parent::persist($assessmentTestSession);
|
|
}
|
|
|
|
/**
|
|
* @param AssessmentTestSession $session
|
|
*/
|
|
private function lockSession(AssessmentTestSession $session)
|
|
{
|
|
if ($session->isLocked()) {
|
|
return;
|
|
}
|
|
|
|
$lock = $this->createLock('AssessmentTestSession_' . $session->getSessionId(), 30);
|
|
$lock->acquire(true);
|
|
$session->setReadOnly(false);
|
|
$session->setLock($lock);
|
|
}
|
|
|
|
protected function getRetrievalStream($sessionId)
|
|
{
|
|
|
|
$storageService = $this->getServiceLocator()->get(tao_models_classes_service_StateStorage::SERVICE_ID);
|
|
$userUri = $this->getUserUri();
|
|
|
|
if (is_null($userUri) === true) {
|
|
$msg = "Could not retrieve current user URI.";
|
|
throw new StorageException($msg, StorageException::RETRIEVAL);
|
|
}
|
|
|
|
$data = $storageService->get($userUri, $sessionId);
|
|
|
|
$stateEmpty = (empty($data) === true);
|
|
$stream = new MemoryStream(($stateEmpty === true) ? '' : $data);
|
|
$stream->open();
|
|
|
|
if ($stateEmpty === false) {
|
|
// Consume additional error (short signed integer).
|
|
$this->setLastError($stream->read(2));
|
|
}
|
|
|
|
$stream->close();
|
|
return $stream;
|
|
}
|
|
|
|
protected function persistStream(AssessmentTestSession $assessmentTestSession, MemoryStream $stream)
|
|
{
|
|
/** @var tao_models_classes_service_StateStorage $storageService */
|
|
$storageService = $this->getServiceLocator()->get(tao_models_classes_service_StateStorage::SERVICE_ID);;
|
|
$userUri = $this->getUserUri();
|
|
|
|
if (is_null($userUri) === true) {
|
|
$msg = "Could not retrieve current user URI.";
|
|
throw new StorageException($msg, StorageException::RETRIEVAL);
|
|
}
|
|
|
|
$data = $this->getLastError() . $stream->getBinary();
|
|
if (!$storageService->set($userUri, $assessmentTestSession->getSessionId(), $data)) {
|
|
throw new StorageException('Can\'t write into storage at ' . static::class);
|
|
}
|
|
}
|
|
|
|
public function exists($sessionId)
|
|
{
|
|
$storageService = $this->getServiceLocator()->get(tao_models_classes_service_StateStorage::SERVICE_ID);
|
|
$userUri = $this->getUserUri();
|
|
|
|
if (is_null($userUri) === true) {
|
|
$msg = "Could not retrieve current user URI.";
|
|
throw new StorageException($msg, StorageException::RETRIEVAL);
|
|
}
|
|
|
|
return $storageService->has($userUri, $sessionId);
|
|
}
|
|
|
|
/**
|
|
* @param string $sessionId
|
|
* @return bool
|
|
*/
|
|
public function delete($sessionId)
|
|
{
|
|
/** @var StateStorage $storageService */
|
|
$storageService = ServiceManager::getServiceManager()->get(StateStorage::SERVICE_ID);
|
|
|
|
return $storageService->del($this->getUserUri(), $sessionId);
|
|
}
|
|
|
|
protected function createBinaryStreamAccess(IStream $stream)
|
|
{
|
|
return new QtiBinaryStreamAccess(
|
|
$stream,
|
|
$this->getServiceLocator()->get(QtiFlysystemFileManager::SERVICE_ID)
|
|
);
|
|
}
|
|
|
|
public function getServiceLocator()
|
|
{
|
|
if ($this->serviceLocator === null) {
|
|
return ServiceManager::getServiceManager();
|
|
}
|
|
return $this->serviceLocator;
|
|
}
|
|
|
|
/**
|
|
* @param string $sessionId
|
|
* @return bool
|
|
*/
|
|
private function sessionExists(string $sessionId): bool
|
|
{
|
|
return self::$session && self::$session->getSessionId() === $sessionId;
|
|
}
|
|
|
|
/**
|
|
* @param $forReadingOnly
|
|
* @return bool
|
|
*/
|
|
private function assessModeChangedToWrite($forReadingOnly): bool
|
|
{
|
|
return !$forReadingOnly && self::$session->isReadOnly();
|
|
}
|
|
}
|