*/ namespace oat\taoQtiTest\models\runner\time; use oat\oatbox\service\ServiceManager; use oat\taoTests\models\runner\time\TimePoint; use qtism\common\datatypes\QtiDuration; use qtism\data\NavigationMode; use qtism\data\QtiComponent; use qtism\runtime\tests\TimeConstraint; use taoQtiTest_helpers_TestRunnerUtils as TestRunnerUtils; /** * Class QtiTimeConstraint * * Represents a time constraint during an AssessmentTestSession. * * @package oat\taoQtiTest\models\runner\time */ class QtiTimeConstraint extends TimeConstraint implements \JsonSerializable { /** * @var QtiTimer */ protected $timer; /** * Allow to take care of extra time * @var boolean */ protected $applyExtraTime; /** * @var integer */ protected $timerTarget; /** * @return QtiTimer */ public function getTimer() { return $this->timer; } /** * @param QtiTimer $timer * @return QtiTimeConstraint */ public function setTimer($timer) { $this->timer = $timer; return $this; } /** * @return boolean */ public function getApplyExtraTime() { return $this->applyExtraTime; } /** * @param boolean $applyExtraTime * @return QtiTimeConstraint */ public function setApplyExtraTime($applyExtraTime) { $this->applyExtraTime = $applyExtraTime; return $this; } /** * @param integer $timerTarget * @return QtiTimeConstraint */ public function setTimerTarget($timerTarget) { $this->timerTarget = $timerTarget; return $this; } /** * Create a new TimeConstraint object. * * @param QtiComponent $source The TestPart or SectionPart the constraint applies on. * @param QtiDuration $duration The already spent duration by the candidate on $source. * @param int|NavigationMode $navigationMode The current navigation mode. * @param boolean $considerMinTime Whether or not to consider minimum time limits. * @param boolean $applyExtraTime Allow to take care of extra time * @param integer $timerTarget client/server */ public function __construct( QtiComponent $source, QtiDuration $duration, $navigationMode = NavigationMode::LINEAR, $considerMinTime = true, $applyExtraTime = false, $timerTarget = TimePoint::TARGET_SERVER ) { $this->setSource($source); $this->setDuration($duration); $this->setNavigationMode($navigationMode); $this->setApplyExtraTime($applyExtraTime); $this->setTimerTarget($timerTarget); } /** * Get the remaining duration from a source (min or max time, usually) * @param QtiDuration $duration the source duration * @return Duration|false A Duration object (or false of not available) that represents the remaining time */ protected function getRemainingTimeFrom(QtiDuration $duration) { if (!is_null($duration)) { $remaining = clone $duration; $remaining->sub($this->getDuration()); return ($remaining->isNegative() === true) ? new QtiDuration('PT0S') : $remaining; } return false; } /** * Get the time remaining to be spent by the candidate on the source of the time * constraint. Please note that this method will never return negative durations. * * @return Duration A Duration object or null if there is no maxTime constraint running for the source of the time constraint. */ public function getMaximumRemainingTime() { if (($maxTime = $this->getAdjustedMaxTime()) !== null) { $maximumTime = clone $maxTime; if ($this->getApplyExtraTime() && $this->timer) { // take care of the already consumed extra time under the current constraint // and append the full remaining extra time // the total must correspond to the already elapsed time plus the remaining time $maximumTime->add(new QtiDuration('PT' . $this->timer->getExtraTime() . 'S')); } return $this->getRemainingTimeFrom($maximumTime); } return false; } /** * Get the time remaining the candidate has to spend by the candidate on the source of the time * constraint. Please note that this method will never return negative durations. * * @return Duration A Duration object or null if there is no minTime constraint running for the source of the time constraint. */ public function getMinimumRemainingTime() { if (($timeLimits = $this->getSource()->getTimeLimits()) !== null && ($minTime = $timeLimits->getMinTime()) !== null) { return $this->getRemainingTimeFrom($minTime); } return false; } /** * Calculates maximum time with applied adjustments * @return QtiDuration|null */ public function getAdjustedMaxTime() { $timeLimits = $this->getSource()->getTimeLimits(); if ($timeLimits === null) { return null; } $maxTime = $timeLimits->getMaxTime(); if ($maxTime === null) { return null; } $maximumTime = clone $maxTime; if ($this->timer) { $adjustmentSeconds = $this->timer->getAdjustmentMap()->get($this->getSource()->getIdentifier()); if ($adjustmentSeconds > 0) { $maximumTime->add(new QtiDuration('PT' . $adjustmentSeconds . 'S')); } else { $maximumTime->sub(new QtiDuration('PT' . abs($adjustmentSeconds) . 'S')); } } return $maximumTime; } /** * Converts a duration to milliseconds * @param QtiDuration|null $duration the duration to convert * @return int|false the duration in ms or false if none */ private function durationToMs($duration) { if (!is_null($duration) && $duration instanceof QtiDuration) { return TestRunnerUtils::getDurationWithMicroseconds($duration); } return false; } /** * Serialize the constraint the expected way by the TestContext and the TestMap * @return array */ public function jsonSerialize() { $source = $this->getSource(); $timeLimits = $source->getTimeLimits(); if (!is_null($timeLimits)) { $identifier = $source->getIdentifier(); $maxTime = $this->getAdjustedMaxTime(); $minTime = $timeLimits->getMinTime(); $maxTimeRemaining = $this->getMaximumRemainingTime(); $minTimeRemaining = $this->getMinimumRemainingTime(); if ($maxTimeRemaining !== false || $minTimeRemaining !== false) { $label = method_exists($source, 'getTitle') ? $source->getTitle() : $identifier; $extraTime = []; if ($this->getTimer() !== null && $maxTime !== null) { $timer = $this->getTimer(); $maxTimeSeconds = $maxTime->getSeconds(true); $extraTime = [ 'total' => $timer->getExtraTime(), 'consumed' => $timer->getConsumedExtraTime($identifier, $maxTimeSeconds, $this->timerTarget), 'remaining' => $timer->getRemainingExtraTime($identifier, $maxTimeSeconds, $this->timerTarget), ]; } /** @var TimerLabelFormatterService $labelFormatter */ $labelFormatter = ServiceManager::getServiceManager()->get(TimerLabelFormatterService::SERVICE_ID); return [ 'label' => $labelFormatter->format($label), 'source' => $identifier, 'qtiClassName' => $source->getQtiClassName(), 'extraTime' => $extraTime, 'allowLateSubmission' => $this->allowLateSubmission(), 'minTime' => $this->durationToMs($minTime), 'minTimeRemaining' => $this->durationToMs($minTimeRemaining), 'maxTime' => $this->durationToMs($maxTime), 'maxTimeRemaining' => $this->durationToMs($maxTimeRemaining), ]; } } return null; } }