From 90c37dbbade47b6c6baca483b9d5e67d89ba7c2a Mon Sep 17 00:00:00 2001 From: = Date: Tue, 27 Oct 2015 17:00:26 +0100 Subject: [PATCH] Template Defaults/Template Processing at test level enhancement. --- src/qtism/data/AssessmentItem.php | 2 +- src/qtism/data/ExtendedAssessmentItemRef.php | 2 +- .../runtime/tests/AssessmentItemSession.php | 54 ++++++-- .../runtime/tests/AssessmentTestSession.php | 129 +++++++++++------- src/qtism/runtime/tests/Route.php | 2 +- .../binary/TemporaryQtiBinaryStorageTest.php | 51 +++---- .../AssessmentTestSessionTemplatesTest.php | 12 +- .../templates/template_test_simple.xml | 18 ++- 8 files changed, 174 insertions(+), 96 deletions(-) diff --git a/src/qtism/data/AssessmentItem.php b/src/qtism/data/AssessmentItem.php index 487639774..bae7f98fb 100644 --- a/src/qtism/data/AssessmentItem.php +++ b/src/qtism/data/AssessmentItem.php @@ -752,7 +752,7 @@ public function getComponents() ); if ($this->hasTemplateProcessing() === true) { - $comp[] = $this->getResponseProcessing(); + $comp[] = $this->getTemplateProcessing(); } $comp = array_merge($comp, $this->getStylesheets()->getArrayCopy()); diff --git a/src/qtism/data/ExtendedAssessmentItemRef.php b/src/qtism/data/ExtendedAssessmentItemRef.php index b9be27552..a83db28a0 100644 --- a/src/qtism/data/ExtendedAssessmentItemRef.php +++ b/src/qtism/data/ExtendedAssessmentItemRef.php @@ -513,7 +513,7 @@ public function getComponents() ); if ($this->hasTemplateProcessing() === true) { - $components[] = $this->getResponseProcessing(); + $components[] = $this->getTemplateProcessing(); } if ($this->hasResponseProcessing() === true) { diff --git a/src/qtism/runtime/tests/AssessmentItemSession.php b/src/qtism/runtime/tests/AssessmentItemSession.php index d69964a3b..3514774d7 100644 --- a/src/qtism/runtime/tests/AssessmentItemSession.php +++ b/src/qtism/runtime/tests/AssessmentItemSession.php @@ -235,6 +235,13 @@ class AssessmentItemSession extends State * @var \qtism\data\state\ShufflingCollection */ private $shufflingStates; + + /** + * Whether or not the template processing must occur automatically. + * + * @var boolean + */ + private $autoTemplateProcessing = true; /** * Create a new AssessmentItemSession object. @@ -247,11 +254,12 @@ class AssessmentItemSession extends State * @param \qtism\data\IAssessmentItem $assessmentItem The description of the item that the session handles. * @param integer $navigationMode (optional) A value from the NavigationMode enumeration. * @param integer $submissionMode (optional) A value from the SubmissionMode enumeration. + * @param boolean $autoTemplateProcessing (optional) Whether or not template processing must occur automatically. Default is true. * @throws \InvalidArgumentException If $navigationMode or $submission is not a value from the NavigationMode/SubmissionMode enumeration. * @see \qtism\runtime\tests\AssessmentItemSession::setItemSessionControl() The setItemSessionControl() method. * @see \qtism\runtime\tests\AssessmentItemSession::setTimeLimits() The setTimeLimits() method. */ - public function __construct(IAssessmentItem $assessmentItem, $navigationMode = NavigationMode::LINEAR, $submissionMode = SubmissionMode::INDIVIDUAL) + public function __construct(IAssessmentItem $assessmentItem, $navigationMode = NavigationMode::LINEAR, $submissionMode = SubmissionMode::INDIVIDUAL, $autoTemplateProcessing = true) { parent::__construct(); @@ -259,6 +267,7 @@ public function __construct(IAssessmentItem $assessmentItem, $navigationMode = N $this->setItemSessionControl(new ItemSessionControl()); $this->setNavigationMode($navigationMode); $this->setSubmissionMode($submissionMode); + $this->setAutoTemplateProcessing($autoTemplateProcessing); // -- Create the built-in response variables. $this->setVariable(new ResponseVariable('numAttempts', Cardinality::SINGLE, BaseType::INTEGER, new QtiInteger(0))); @@ -532,9 +541,28 @@ public function setShufflingStates(ShufflingCollection $shufflingStates) * * @param \qtism\data\state\ShufflingCollection $shufflingStates */ - public function getShufflingStates() { + public function getShufflingStates() + { return $this->shufflingStates; } + + /** + * Set whether or not template processing must occur automatically. + * + * @param boolean $autoTemplateProcessing + */ + public function setAutoTemplateProcessing($autoTemplateProcessing) + { + $this->autoTemplateProcessing = $autoTemplateProcessing; + } + + /** + * Know whether or not template processing must occur automatically. + */ + public function mustAutoTemplateProcessing() + { + return $this->autoTemplateProcessing; + } /** * Set the current time of the running assessment item session. @@ -606,15 +634,18 @@ public function beginItemSession() } } - // Apply templateProcessing. - $templateProcessing = $this->templateProcessing(); + // Apply templateProcessing if needed. + $templateProcessing = false; + if ($this->mustAutoTemplateProcessing() === true) { + $templateProcessing = $this->templateProcessing(); - foreach ($data as $identifier => $variable) { - if (in_array($identifier, $filter) === false) { - - // Outcome variables are applied their default value if any. - if ($variable instanceof OutcomeVariable || ($variable instanceof TemplateVariable && $templateProcessing === false)) { - $variable->applyDefaultValue(); + foreach ($data as $identifier => $variable) { + if (in_array($identifier, $filter) === false) { + + // Outcome variables are applied their default value if any. + if ($variable instanceof OutcomeVariable || ($variable instanceof TemplateVariable && $templateProcessing === false)) { + $variable->applyDefaultValue(); + } } } } @@ -1287,7 +1318,7 @@ private function mustModalFeedback() * @throws \qtism\runtime\rules\RuleProcessingException * @return boolean Whether or not the template processing occured. */ - protected function templateProcessing() + public function templateProcessing() { $assessmentItem = $this->getAssessmentItem(); if (($templateProcessing = $assessmentItem->getTemplateProcessing()) !== null) { @@ -1296,6 +1327,7 @@ protected function templateProcessing() return true; } else { + return false; } } diff --git a/src/qtism/runtime/tests/AssessmentTestSession.php b/src/qtism/runtime/tests/AssessmentTestSession.php index b999bf7a0..2b0cb6bd4 100644 --- a/src/qtism/runtime/tests/AssessmentTestSession.php +++ b/src/qtism/runtime/tests/AssessmentTestSession.php @@ -642,6 +642,11 @@ public function beginAttempt() // Time limits are OK! Let's try to begin the attempt. $routeItem = $this->getCurrentRouteItem(); $session = $this->getCurrentAssessmentItemSession(); + + if ($routeItem->getTestPart()->getNavigationMode() === NavigationMode::LINEAR && $session['numAttempts']->getValue() === 0) { + $this->applyTemplateDefaults($session); + $session->templateProcessing(); + } try { if ($this->getCurrentSubmissionMode() === SubmissionMode::INDIVIDUAL) { @@ -1812,17 +1817,29 @@ public function offsetExists($offset) * @param integer $navigationMode * @param integer $submissionMode * @return \qtism\runtime\tests\AssessmentItemSession - * @throws \qtism\runtime\expressions\ExpressionProcessingException|\qtism\runtime\expressions\operators\OperatorProcessingException If something wrong happens when initializing templateDefaults. */ protected function createAssessmentItemSession(IAssessmentItem $assessmentItem, $navigationMode, $submissionMode) { - $session = $this->getSessionManager()->createAssessmentItemSession($assessmentItem, $navigationMode, $submissionMode); + $session = $this->getSessionManager()->createAssessmentItemSession($assessmentItem, $navigationMode, $submissionMode, false); + + return $session; + } + + /** + * Apply the templateDefault values to the item $session. + * + * param \qtism\runtime\tests\AssessmentItemSession $session + * @throws \qtism\runtime\expressions\ExpressionProcessingException|\qtism\runtime\expressions\operators\OperatorProcessingException If something wrong happens when initializing templateDefaults. + */ + protected function applyTemplateDefaults(AssessmentItemSession $session) + { $templateDefaults = $session->getAssessmentItem()->getTemplateDefaults(); if (count($templateDefaults) > 0) { // Some templateVariable default values must have to be changed... foreach ($session->getAssessmentItem()->getTemplateDefaults() as $templateDefault) { + $identifier = $templateDefault->getTemplateIdentifier(); $expression = $templateDefault->getExpression(); $variable = $session->getVariable($identifier); @@ -1835,8 +1852,6 @@ protected function createAssessmentItemSession(IAssessmentItem $assessmentItem, } } } - - return $session; } /** @@ -1877,62 +1892,74 @@ protected function initializeTestDurations() protected function selectEligibleItems() { $route = $this->getRoute(); - $oldPosition = $route->getPosition(); - $adaptive = $this->isAdaptive(); - - // In this loop, we select at least the first routeItem we find as eligible. - while ($route->valid() === true) { - - $routeItem = $route->current(); - $itemRef = $routeItem->getAssessmentItemRef(); - $occurence = $routeItem->getOccurence(); + + if ($route->valid() === true) { - $session = $this->getItemSession($itemRef, $occurence); + $oldPosition = $route->getPosition(); + $adaptive = $this->isAdaptive(); + $initialTestPart = $route->current()->getTestPart(); + $isInitalRouteItemFirstOfTestPart = $route->isFirstOfTestPart(); - // Does such a session exist for item + occurence? - if ($session === false) { - - // Instantiate the item session... - $testPart = $routeItem->getTestPart(); - $navigationMode = $testPart->getNavigationMode(); - $submissionMode = $testPart->getSubmissionMode(); - - $session = $this->createAssessmentItemSession($itemRef, $navigationMode, $submissionMode); + // In this loop, we select at least the first routeItem we find as eligible. + while ($route->valid() === true) { - // Determine the item session control. - if (($control = $routeItem->getItemSessionControl()) !== null) { - $session->setItemSessionControl($control->getItemSessionControl()); - } - - // Determine the time limits. - if ($itemRef->hasTimeLimits() === true) { - $session->setTimeLimits($itemRef->getTimeLimits()); - } - - $this->addItemSession($session, $occurence); + $routeItem = $route->current(); + $itemRef = $routeItem->getAssessmentItemRef(); + $occurence = $routeItem->getOccurence(); - // If we know "what time it is", we transmit - // that information to the eligible item. - if ($this->hasTimeReference() === true) { - $session->setTime($this->getTimeReference()); + $session = $this->getItemSession($itemRef, $occurence); + + // Does such a session exist for item + occurence? + if ($session === false) { + + // Instantiate the item session... + $testPart = $routeItem->getTestPart(); + $navigationMode = $testPart->getNavigationMode(); + $submissionMode = $testPart->getSubmissionMode(); + + $session = $this->createAssessmentItemSession($itemRef, $navigationMode, $submissionMode); + + // Determine the item session control. + if (($control = $routeItem->getItemSessionControl()) !== null) { + $session->setItemSessionControl($control->getItemSessionControl()); + } + + // Determine the time limits. + if ($itemRef->hasTimeLimits() === true) { + $session->setTimeLimits($itemRef->getTimeLimits()); + } + + $this->addItemSession($session, $occurence); + + // If we know "what time it is", we transmit + // that information to the eligible item. + if ($this->hasTimeReference() === true) { + $session->setTime($this->getTimeReference()); + } + + $session->beginItemSession(); + + // Deal with template defaults and template processing for non linear case. + if ($testPart === $initialTestPart && $initialTestPart->getNavigationMode() === NavigationMode::NONLINEAR && $isInitalRouteItemFirstOfTestPart === true) { + $this->applyTemplateDefaults($session); + $session->templateProcessing(); + } } - $session->beginItemSession(); + if ($adaptive === true) { + // We cannot foresee more items to be selected for presentation + // because the rest of the sequence is linear and might contain + // branching rules or preconditions. + break; + } else { + // We continue to search for route items that are selectable for + // presentation to the candidate. + $route->next(); + } } - if ($adaptive === true) { - // We cannot foresee more items to be selected for presentation - // because the rest of the sequence is linear and might contain - // branching rules or preconditions. - break; - } else { - // We continue to search for route items that are selectable for - // presentation to the candidate. - $route->next(); - } + $route->setPosition($oldPosition); } - - $route->setPosition($oldPosition); } /** diff --git a/src/qtism/runtime/tests/Route.php b/src/qtism/runtime/tests/Route.php index 150e61be6..74299956b 100644 --- a/src/qtism/runtime/tests/Route.php +++ b/src/qtism/runtime/tests/Route.php @@ -914,7 +914,7 @@ public function isFirstOfTestPart() } $previousPosition = $this->getPosition() - 1; - if ($previousPosition === 0) { + if ($previousPosition === -1) { // This is the very first RouteItem of the whole Route. return true; } elseif ($this->getRouteItemAt($previousPosition)->getTestPart() !== $this->current()->getTestPart()) { diff --git a/test/qtismtest/runtime/storage/binary/TemporaryQtiBinaryStorageTest.php b/test/qtismtest/runtime/storage/binary/TemporaryQtiBinaryStorageTest.php index 94c5fc819..4346c0daa 100644 --- a/test/qtismtest/runtime/storage/binary/TemporaryQtiBinaryStorageTest.php +++ b/test/qtismtest/runtime/storage/binary/TemporaryQtiBinaryStorageTest.php @@ -827,24 +827,25 @@ public function testTemplateDefault1() { $QTPL1Session = $QTPL1Sessions[0]; // The the session is correctly instantiated, with the s in force. + // In linear mode, the templateDefaults are applied just before the first attempt. $this->assertEquals(AssessmentItemSessionState::INITIAL, $QTPL1Session->getState()); - $this->assertEquals(1.0, $QTPL1Session->getVariable('GOODSCORE')->getDefaultValue()->getValue()); - $this->assertEquals(0.0, $QTPL1Session->getVariable('WRONGSCORE')->getDefaultValue()->getValue()); - $this->assertEquals(1.0, $session['QTPL1.GOODSCORE']->getValue()); - $this->assertEquals(1.0, $QTPL1Session['GOODSCORE']->getValue()); - $this->assertEquals(0.0, $session['QTPL1.WRONGSCORE']->getValue()); - $this->assertEquals(0.0, $QTPL1Session['WRONGSCORE']->getValue()); + $this->assertNull($QTPL1Session->getVariable('GOODSCORE')->getDefaultValue()); + $this->assertNull($QTPL1Session->getVariable('WRONGSCORE')->getDefaultValue()); + $this->assertNull($session['QTPL1.GOODSCORE']); + $this->assertNull($QTPL1Session['GOODSCORE']); + $this->assertNull($session['QTPL1.WRONGSCORE']); + $this->assertNull($QTPL1Session['WRONGSCORE']); $QTPL2Sessions = $session->getAssessmentItemSessions('QTPL2'); $QTPL2Session = $QTPL2Sessions[0]; $this->assertEquals(AssessmentItemSessionState::INITIAL, $QTPL2Session->getState()); - $this->assertEquals(2.0, $QTPL2Session->getVariable('GOODSCORE')->getDefaultValue()->getValue()); - $this->assertEquals(-1.0, $QTPL2Session->getVariable('WRONGSCORE')->getDefaultValue()->getValue()); - $this->assertEquals(2.0, $session['QTPL2.GOODSCORE']->getValue()); - $this->assertEquals(2.0, $QTPL2Session['GOODSCORE']->getValue()); - $this->assertEquals(-1.0, $session['QTPL2.WRONGSCORE']->getValue()); - $this->assertEquals(-1.0, $QTPL2Session['WRONGSCORE']->getValue()); + $this->assertNull($QTPL2Session->getVariable('GOODSCORE')->getDefaultValue()); + $this->assertNull($QTPL2Session->getVariable('WRONGSCORE')->getDefaultValue()); + $this->assertNull($session['QTPL2.GOODSCORE']); + $this->assertNull($QTPL2Session['GOODSCORE']); + $this->assertNull($session['QTPL2.WRONGSCORE']); + $this->assertNull($QTPL2Session['WRONGSCORE']); // Now let's make sure the persistence works correctly when s are in force... // We do this by testing again that default values are correctly initialized within their respective @@ -854,23 +855,23 @@ public function testTemplateDefault1() { $session = $storage->retrieve($test, $sessionId); $this->assertEquals(AssessmentItemSessionState::INITIAL, $QTPL1Session->getState()); - $this->assertEquals(1.0, $QTPL1Session->getVariable('GOODSCORE')->getDefaultValue()->getValue()); - $this->assertEquals(0.0, $QTPL1Session->getVariable('WRONGSCORE')->getDefaultValue()->getValue()); - $this->assertEquals(1.0, $session['QTPL1.GOODSCORE']->getValue()); - $this->assertEquals(1.0, $QTPL1Session['GOODSCORE']->getValue()); - $this->assertEquals(0.0, $session['QTPL1.WRONGSCORE']->getValue()); - $this->assertEquals(0.0, $QTPL1Session['WRONGSCORE']->getValue()); + $this->assertNull($QTPL1Session->getVariable('GOODSCORE')->getDefaultValue()); + $this->assertNull($QTPL1Session->getVariable('WRONGSCORE')->getDefaultValue()); + $this->assertNull($session['QTPL1.GOODSCORE']); + $this->assertNull($QTPL1Session['GOODSCORE']); + $this->assertNull($session['QTPL1.WRONGSCORE']); + $this->assertNull($QTPL1Session['WRONGSCORE']); $QTPL2Sessions = $session->getAssessmentItemSessions('QTPL2'); $QTPL2Session = $QTPL2Sessions[0]; $this->assertEquals(AssessmentItemSessionState::INITIAL, $QTPL2Session->getState()); - $this->assertEquals(2.0, $QTPL2Session->getVariable('GOODSCORE')->getDefaultValue()->getValue()); - $this->assertEquals(-1.0, $QTPL2Session->getVariable('WRONGSCORE')->getDefaultValue()->getValue()); - $this->assertEquals(2.0, $session['QTPL2.GOODSCORE']->getValue()); - $this->assertEquals(2.0, $QTPL2Session['GOODSCORE']->getValue()); - $this->assertEquals(-1.0, $session['QTPL2.WRONGSCORE']->getValue()); - $this->assertEquals(-1.0, $QTPL2Session['WRONGSCORE']->getValue()); + $this->assertNull($QTPL2Session->getVariable('GOODSCORE')->getDefaultValue()); + $this->assertNull($QTPL2Session->getVariable('WRONGSCORE')->getDefaultValue()); + $this->assertNull($session['QTPL2.GOODSCORE']); + $this->assertNull($QTPL2Session['GOODSCORE']); + $this->assertNull($session['QTPL2.WRONGSCORE']); + $this->assertNull($QTPL2Session['WRONGSCORE']); // It seems to be ok! Let's take the test! $session->beginAttempt(); @@ -949,4 +950,4 @@ public function testTemplateDefault1() { $this->assertEquals(AssessmentItemSessionState::CLOSED, $QTPL1Session->getState()); $this->assertEquals(AssessmentItemSessionState::CLOSED, $QTPL2Session->getState()); } -} \ No newline at end of file +} diff --git a/test/qtismtest/runtime/tests/AssessmentTestSessionTemplatesTest.php b/test/qtismtest/runtime/tests/AssessmentTestSessionTemplatesTest.php index 5f05ed11c..9f25c8f07 100644 --- a/test/qtismtest/runtime/tests/AssessmentTestSessionTemplatesTest.php +++ b/test/qtismtest/runtime/tests/AssessmentTestSessionTemplatesTest.php @@ -16,10 +16,12 @@ public function testSimpleTemplating() { $session = self::instantiate(self::samplesDir() . 'custom/runtime/templates/template_test_simple.xml'); $session->beginTestSession(); // We are in linear mode with no branching/preconditions, so the sessions are alive... - $this->assertEquals(1.0, $session['QTPL1.GOODSCORE']->getValue()); - $this->assertEquals(0.0, $session['QTPL1.WRONGSCORE']->getValue()); - $this->assertEquals(2.0, $session['QTPL2.GOODSCORE']->getValue()); - $this->assertEquals(-1.0, $session['QTPL2.WRONGSCORE']->getValue()); + // But the templateDefaults/templateProcessings will only occur at the beginning of the + // very first attempt. + $this->assertNull($session['QTPL1.GOODSCORE']); + $this->assertNull($session['QTPL1.WRONGSCORE']); + $this->assertNull($session['QTPL2.GOODSCORE']); + $this->assertNull($session['QTPL2.WRONGSCORE']); // QTPL1 - correct response. $session->beginAttempt(); @@ -41,4 +43,4 @@ public function testSimpleTemplating() { $this->assertEquals(1.0, $session['QTPL1.SCORE']->getValue()); $this->assertEquals(2.0, $session['QTPL2.SCORE']->getValue()); } -} \ No newline at end of file +} diff --git a/test/samples/custom/runtime/templates/template_test_simple.xml b/test/samples/custom/runtime/templates/template_test_simple.xml index 3460994fd..32c0e7512 100644 --- a/test/samples/custom/runtime/templates/template_test_simple.xml +++ b/test/samples/custom/runtime/templates/template_test_simple.xml @@ -20,6 +20,14 @@ + + + + + + + + @@ -55,6 +63,14 @@ + + + + + + + + @@ -76,4 +92,4 @@ - \ No newline at end of file +