*/ 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; } }