tao-test/app/taoProctoring/model/monitorCache/implementation/DeliveryMonitoringData.php

364 lines
12 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) 2015 (original work) Open Assessment Technologies SA;
*
*
*/
namespace oat\taoProctoring\model\monitorCache\implementation;
use oat\taoDelivery\model\execution\DeliveryExecutionContext;
use oat\taoDelivery\model\execution\DeliveryExecutionContextInterface;
use oat\taoDelivery\model\execution\DeliveryExecutionInterface;
use oat\taoProctoring\model\execution\DeliveryExecutionManagerService;
use oat\taoProctoring\model\implementation\TestSessionService;
use oat\taoProctoring\model\monitorCache\DeliveryMonitoringData as DeliveryMonitoringDataInterface;
use oat\taoProctoring\model\execution\DeliveryExecution as ProctoredDeliveryExecution;
use oat\taoProctoring\model\TestSessionConnectivityStatusService;
use oat\taoQtiTest\models\runner\session\TestSession;
use oat\taoQtiTest\models\runner\time\QtiTimerFactory;
use oat\taoTests\models\runner\time\TimePoint;
use qtism\runtime\tests\AssessmentTestSession;
use oat\taoDelivery\model\execution\DeliveryExecution;
use oat\taoProctoring\model\monitorCache\DeliveryMonitoringService;
use Zend\ServiceManager\ServiceLocatorAwareTrait;
use Zend\ServiceManager\ServiceLocatorAwareInterface;
/**
* class DeliveryMonitoringData
*
* Represents data model of delivery execution.
*
* @package oat\taoProctoring
* @author Aleh Hutnikau <hutnikau@1pt.com>
*/
class DeliveryMonitoringData implements DeliveryMonitoringDataInterface, ServiceLocatorAwareInterface
{
use ServiceLocatorAwareTrait;
/**
* @var array
*/
private $data = [];
/**
* @var DeliveryExecution
*/
private $deliveryExecution;
/**
* @var AssessmentTestSession
*/
private $testSession;
/**
* @var array
*/
private $errors = [];
/**
* @var array
*/
private $requiredFields = [
DeliveryMonitoringService::DELIVERY_EXECUTION_ID,
DeliveryMonitoringService::STATUS,
];
/**
* @param DeliveryExecutionInterface $deliveryExecution
* @param $data
* @throws \common_exception_NotFound
*/
public function __construct(DeliveryExecutionInterface $deliveryExecution, array $data)
{
$this->deliveryExecution = $deliveryExecution;
$this->data = $data;
}
/**
* (non-PHPdoc)
* @see \oat\taoProctoring\model\monitorCache\DeliveryMonitoringData::update()
*/
public function update($key, $value)
{
$this->addValue($key, $value, true);
}
/**
* Add data
* @param string $key
* @param string $value
* @param boolean $overwrite
*/
public function addValue($key, $value, $overwrite = false)
{
if (!isset($this->data[$key]) || $overwrite) {
$this->data[$key] = (string) $value;
}
}
/**
* Save delivery execution
* @param DeliveryExecution $deliveryExecution
*/
public function setDeliveryExecution(DeliveryExecution $deliveryExecution)
{
$this->deliveryExecution = $deliveryExecution;
}
/**
* @return DeliveryExecutionInterface
*/
public function getDeliveryExecution()
{
return $this->deliveryExecution;
}
/**
* {@inheritdoc}
*/
public function setDeliveryExecutionContext(DeliveryExecutionContextInterface $context)
{
$this->update(self::PARAM_EXECUTION_CONTEXT, json_encode($context));
}
/**
* {@inheritdoc}
*/
public function getDeliveryExecutionContext()
{
try {
if (isset($this->data[self::PARAM_EXECUTION_CONTEXT])) {
return DeliveryExecutionContext::createFromArray(json_decode($this->data[self::PARAM_EXECUTION_CONTEXT], true));
}
} catch (\Exception $e) {}
return null;
}
/**
* Validate data
* @return bool whether data is valid and can be saved.
*/
public function validate()
{
$result = true;
$this->errors = [];
$data = $this->get();
foreach ($this->requiredFields as $requiredField) {
if (!isset($data[$requiredField])) {
$result = false;
$this->errors[$requiredField] = 'cannot be empty';
}
}
foreach ($data as $fieldName => $fieldValue) {
if (!array_key_exists($fieldName, $this->errors) && $fieldValue !== null && !is_string($fieldValue)) {
$this->errors[$fieldName] = 'should be a string';
}
}
return $result;
}
/**
* Get list of errors.
* @return array
*/
public function getErrors()
{
return $this->errors;
}
/**
* Get delivery execution data
* @param bool $refresh
* @return array
*/
public function get($refresh = false)
{
if ($refresh) {
$this->updateData();
}
return $this->data;
}
/**
* Set test session
* @param AssessmentTestSession $testSession
*/
public function setTestSession(AssessmentTestSession $testSession)
{
$this->testSession = $testSession;
}
/**
* @param array $keys
*/
public function updateData(array $keys = null)
{
if ($keys === null) {
$keys = [
DeliveryMonitoringService::STATUS,
DeliveryMonitoringService::REMAINING_TIME,
DeliveryMonitoringService::EXTRA_TIME,
DeliveryMonitoringService::EXTENDED_TIME
];
}
foreach ($keys as $key) {
$methodName = 'update' . str_replace(' ', '', ucwords(str_replace('_', ' ', $key)));
if (method_exists($this, $methodName)) {
$this->{$methodName}();
}
}
}
/**
* Update extra time allowed for the delivery execution
*/
private function updateLastTestTakerActivity()
{
$this->addValue(DeliveryMonitoringService::LAST_TEST_TAKER_ACTIVITY, microtime(true), true);
}
/**
* Update connectivity status (online|offline)
*/
private function updateLastConnect()
{
$status = $this->deliveryExecution->getState()->getUri();
/** @var TestSessionConnectivityStatusService $testSessionConnectivityStatusService */
$testSessionConnectivityStatusService = $this->getServiceLocator()->get(TestSessionConnectivityStatusService::SERVICE_ID);
if ($testSessionConnectivityStatusService->hasOnlineMode() && ProctoredDeliveryExecution::STATE_ACTIVE == $status) {
$lastConnectivity = $testSessionConnectivityStatusService->getLastOnline($this->deliveryExecution->getIdentifier());
}else{
// to ensure that during sorting by connectivity all similar statuses grouped together
$lastConnectivity = (~PHP_INT_MAX) + substr(abs(crc32($status)), 0, 3);
}
$this->addValue(DeliveryMonitoringService::CONNECTIVITY, $lastConnectivity, true);
}
/**
* Update test session state
*/
private function updateStatus()
{
$status = $this->deliveryExecution->getState()->getUri();
$this->addValue(DeliveryMonitoringService::STATUS, $status, true);
if ($status == ProctoredDeliveryExecution::STATE_PAUSED) {
$this->addValue(DeliveryMonitoringService::LAST_PAUSE_TIMESTAMP, microtime(true), true);
}
}
/**
* Update remaining time of delivery execution
*/
private function updateRemainingTime()
{
$result = null;
$remaining = 0;
$hasTimer = false;
$session = $this->getTestSession();
if ($session !== null && $session->isRunning()) {
$remaining = PHP_INT_MAX;
$timeConstraints = $session->getTimeConstraints();
foreach ($timeConstraints as $tc) {
// Only consider time constraints in force.
$maximumRemainingTime = $tc->getMaximumRemainingTime();
if ($maximumRemainingTime !== false) {
$hasTimer = true;
$remaining = min($remaining, $maximumRemainingTime->getSeconds(true));
}
}
}
if ($hasTimer) {
$result = $remaining;
}
$this->addValue(DeliveryMonitoringService::REMAINING_TIME, $result, true);
}
/**
* Update diff between last_pause_timestamp and last_test_taker_activity
*/
private function updateDiffTimestamp()
{
$diffTimestamp = 0;
$lastTimeStamp = 0;
$lastActivity = 0;
if (isset($this->data[DeliveryMonitoringService::LAST_PAUSE_TIMESTAMP])) {
$lastTimeStamp = $this->data[DeliveryMonitoringService::LAST_PAUSE_TIMESTAMP];
}
if (isset($this->data[DeliveryMonitoringService::LAST_TEST_TAKER_ACTIVITY])) {
$lastActivity = $this->data[DeliveryMonitoringService::LAST_TEST_TAKER_ACTIVITY];
}
if ($lastTimeStamp - $lastActivity > 0) {
$diffTimestamp = isset($this->data[DeliveryMonitoringService::DIFF_TIMESTAMP]) ? $this->data[DeliveryMonitoringService::DIFF_TIMESTAMP] : 0;
$diffTimestamp += $lastTimeStamp - $lastActivity;
}
$this->addValue(DeliveryMonitoringService::DIFF_TIMESTAMP, $diffTimestamp, true);
}
/**
* Update extra time allowed for the delivery execution
*/
private function updateExtraTime()
{
$testSession = $this->getTestSession();
if ($testSession instanceof TestSession) {
$timer = $testSession->getTimer();
$timerTarget = $testSession->getTimerTarget();
} else {
$timerTarget = TimePoint::TARGET_SERVER;
$qtiTimerFactory = $this->getServiceLocator()->get(QtiTimerFactory::SERVICE_ID);
$timer = $qtiTimerFactory->getTimer($this->deliveryExecution->getIdentifier(), $this->deliveryExecution->getUserIdentifier());
}
/** @var DeliveryExecutionManagerService $deliveryExecutionManager */
$deliveryExecutionManager = $this->getServiceLocator()->get(DeliveryExecutionManagerService::SERVICE_ID);
$maxTimeSeconds = $deliveryExecutionManager->getTimeLimits($testSession);
$data = $this->get();
$oldConsumedExtraTime = isset($data[DeliveryMonitoringService::CONSUMED_EXTRA_TIME]) ? $data[DeliveryMonitoringService::CONSUMED_EXTRA_TIME] : 0;
$consumedExtraTime = max($oldConsumedExtraTime, $timer->getConsumedExtraTime(null, $maxTimeSeconds, $timerTarget));
$this->addValue(DeliveryMonitoringService::EXTRA_TIME, $timer->getExtraTime(), true);
$this->addValue(DeliveryMonitoringService::CONSUMED_EXTRA_TIME, $consumedExtraTime, true);
}
/**
* @return AssessmentTestSession
*/
private function getTestSession()
{
if ($this->testSession === null) {
$testSessionService = $this->getServiceLocator()->get(TestSessionService::SERVICE_ID);
$this->testSession = $testSessionService->getTestSession($this->deliveryExecution);
}
return $this->testSession;
}
}