models/classes/runner/map/QtiRunnerMap.php
public function getScopedMap(RunnerServiceContext $context, RunnerConfig $config, $scope = RunnerMap::SCOPE_SECTION)
    {
        if (!($context instanceof QtiRunnerServiceContext)) {
            throw new \common_exception_InvalidArgumentType(
                'QtiRunnerMap',
                'getMap',
                0,
                'oat\taoQtiTest\models\runner\QtiRunnerServiceContext',
                $context
            );
        }

        $map = [
            'scope' => $scope,
            'parts' => []
        ];

        // get config for the sequence number option
        $reviewConfig = $config->getConfigValue('review');
        $checkInformational = $config->getConfigValue('checkInformational');
        $forceTitles = !empty($reviewConfig['forceTitle']);
        $forceInformationalTitles = !empty($reviewConfig['forceInformationalTitle']);
        $useTitle = !empty($reviewConfig['useTitle']);
        $uniqueTitle = isset($reviewConfig['itemTitle']) ? $reviewConfig['itemTitle'] : '%d';
        $uniqueInformationalTitle = isset($reviewConfig['informationalItemTitle']) ? $reviewConfig['informationalItemTitle'] : 'Instructions';
        $displaySubsectionTitle = isset($reviewConfig['displaySubsectionTitle']) ? (bool) $reviewConfig['displaySubsectionTitle'] : true;

        /* @var AssessmentTestSession $session */
        $session = $context->getTestSession();
        $extendedStorage = $this->getServiceLocator()->get(ExtendedStateService::SERVICE_ID);
        if ($session->isRunning() !== false) {
            $route         = $session->getRoute();
            $store         = $session->getAssessmentItemSessionStore();

            switch($scope){
                case RunnerMap::SCOPE_SECTION :
                    $routeItems = $route->getRouteItemsByAssessmentSection($session->getCurrentAssessmentSection());
                    break;
                case RunnerMap::SCOPE_PART :
                    $routeItems = $route->getRouteItemsByTestPart($session->getCurrentTestPart());
                    break;
                case RunnerMap::SCOPE_TEST:
                default:
                    $routeItems = $route->getAllRouteItems();
                    break;
            }

            $offset = $this->getOffsetPosition($context, $routeItems[0]);
            $offsetSection = 0;
            $lastSection   = null;

            // fallback index in case of the delivery was compiled without the index of item href
            $this->itemHrefIndex = [];
            $shouldBuildItemHrefIndex = !$this->hasItemHrefIndexFile($context, $session->getCurrentAssessmentItemRef()->getIdentifier());
            \common_Logger::t('Store index ' . ($shouldBuildItemHrefIndex ? 'must be built' : 'is part of the package'));

            /** @var \qtism\runtime\tests\RouteItem $routeItem */
            foreach ($routeItems as $routeItem) {

                $catSession = false;
                $itemRefs = $this->getRouteItemAssessmentItemRefs($context, $routeItem, $catSession);
                $previouslySeenItems = ($catSession) ? $context->getPreviouslySeenCatItemIds($routeItem) : [];

                foreach ($itemRefs as $itemRef) {
                    $occurrence = ($catSession !== false) ? 0 : $routeItem->getOccurence();

                    // get the jump definition
                    $itemSession = ($catSession !== false) ? false : $store->getAssessmentItemSession($itemRef, $occurrence);

                    // load item infos
                    $isItemInformational = ($itemSession) ? TestRunnerUtils::isItemInformational($routeItem, $itemSession) : false;
                    $testPart = $routeItem->getTestPart();
                    $partId = $testPart->getIdentifier();
                    $navigationMode = $testPart->getNavigationMode();

                    if ($displaySubsectionTitle) {
                        $section = $routeItem->getAssessmentSection();
                    } else {
                        $sections = $routeItem->getAssessmentSections()->getArrayCopy();
                        $section = $sections[0];
                    }
                    $sectionId = $section->getIdentifier();
                    $itemId = $itemRef->getIdentifier();

                    if ($lastSection != $sectionId) {
                        $offsetSection = 0;
                        $lastSection = $sectionId;
                    }

                    if ($forceInformationalTitles && $isItemInformational) {
                        $itemUri = strstr($itemRef->getHref(), '|', true);
                        $label = $uniqueInformationalTitle === false
                            ? $this->getItemLabel($context, $itemUri, $useTitle)
                            : $uniqueInformationalTitle;

                    } elseif ($forceTitles) {
                        $label = __($uniqueTitle, $offsetSection + 1);

                    } else {
                        $itemUri = strstr($itemRef->getHref(), '|', true);
                        $label = $this->getItemLabel($context, $itemUri, $useTitle);
                    }

                    // fallback in case of the delivery was compiled without the index of item href
                    if ($shouldBuildItemHrefIndex) {
                        $this->itemHrefIndex[$itemId] = $itemRef->getHref();
                    }

                    $itemInfos = [
                        'id' => $itemId,
                        'label' => $label,
                        'position' => $offset,
                        'occurrence' => $occurrence,
                        'remainingAttempts' => ($itemSession) ? $itemSession->getRemainingAttempts() : -1,
                        'answered' => ($itemSession) ? TestRunnerUtils::isItemCompleted($routeItem, $itemSession) : in_array($itemId, $previouslySeenItems),
                        'flagged' => $extendedStorage->getItemFlag($session->getSessionId(), $itemId),
                        'viewed' => ($itemSession) ? $itemSession->isPresented() : in_array($itemId, $previouslySeenItems),
                        'categories' => $itemRef->getCategories()->getArrayCopy()
                    ];

                    if ($checkInformational) {
                        $itemInfos['informational'] = $isItemInformational;
                    }

                    if($itemRef->hasTimeLimits()){
                        $itemInfos['timeConstraint'] = $this->getTimeConstraint($session, $itemRef, $navigationMode);
                    }

                    if (!isset($map['parts'][$partId]) && $scope != RunnerMap::SCOPE_SECTION) {
                        $map['parts'][$partId]['id'] = $partId;
                        $map['parts'][$partId]['label'] = $partId;
                        $map['parts'][$partId]['position'] = $offset;
                        $map['parts'][$partId]['isLinear'] = $navigationMode == NavigationMode::LINEAR;

                        if($testPart->hasTimeLimits()){
                            $map['parts'][$partId]['timeConstraint'] =  $this->getTimeConstraint($session, $testPart, $navigationMode);
                        }
                    }

                    if (!isset($map['parts'][$partId]['sections'][$sectionId])) {
                        $map['parts'][$partId]['sections'][$sectionId]['id'] = $sectionId;
                        $map['parts'][$partId]['sections'][$sectionId]['label'] = $section->getTitle();
                        $map['parts'][$partId]['sections'][$sectionId]['isCatAdaptive'] = CatUtils::isAssessmentSectionAdaptive($section);
                        $map['parts'][$partId]['sections'][$sectionId]['position'] = $offset;

                        if($section->hasTimeLimits()){
                            $map['parts'][$partId]['sections'][$sectionId]['timeConstraint'] = $this->getTimeConstraint($session, $section, $navigationMode);
                        }
                    }

                    $map['parts'][$partId]['sections'][$sectionId]['items'][$itemId] = $itemInfos;

                    // update the stats
                    if($scope == RunnerMap::SCOPE_TEST) {
                        $this->updateStats($map, $itemInfos);
                        $this->updateStats($map['parts'][$partId], $itemInfos);
                        $this->updateStats($map['parts'][$partId]['sections'][$sectionId], $itemInfos);
                    }
                    if($scope == RunnerMap::SCOPE_PART) {
                        $this->updateStats($map['parts'][$partId], $itemInfos);
                        $this->updateStats($map['parts'][$partId]['sections'][$sectionId], $itemInfos);
                    }
                    if($scope == RunnerMap::SCOPE_SECTION) {
                        $this->updateStats($map['parts'][$partId]['sections'][$sectionId], $itemInfos);
                    }

                    $offset ++;
                    if (!$forceInformationalTitles || ($forceInformationalTitles && !$isItemInformational)) {
                        $offsetSection++;
                    };
                }
            }
            // fallback in case of the delivery was compiled without the index of item href
            if ($shouldBuildItemHrefIndex) {
                \common_Logger::t('Store index of item href into the test state storage');
                $storage = $this->getServiceLocator()->get(ExtendedStateService::SERVICE_ID);
                $storage->storeItemHrefIndex($context->getTestExecutionUri(), $this->itemHrefIndex);
            }
        }

        return $map;
    }
public function getScopedMap(RunnerServiceContext $context, RunnerConfig $config, $scope = RunnerMap::SCOPE_SECTION)
    {
        if (!($context instanceof QtiRunnerServiceContext)) {
            throw new \common_exception_InvalidArgumentType(
                'QtiRunnerMap',
                'getMap',
                0,
                'oat\taoQtiTest\models\runner\QtiRunnerServiceContext',
                $context
            );
        }

        $map = [
            'scope' => $scope,
            'parts' => []
        ];

        // get config for the sequence number option
        $reviewConfig = $config->getConfigValue('review');
        $checkInformational = $config->getConfigValue('checkInformational');
        $forceTitles = !empty($reviewConfig['forceTitle']);
        $forceInformationalTitles = !empty($reviewConfig['forceInformationalTitle']);
        $useTitle = !empty($reviewConfig['useTitle']);
        $uniqueTitle = isset($reviewConfig['itemTitle']) ? $reviewConfig['itemTitle'] : '%d';
        $uniqueInformationalTitle = isset($reviewConfig['informationalItemTitle']) ? $reviewConfig['informationalItemTitle'] : 'Instructions';
        $displaySubsectionTitle = isset($reviewConfig['displaySubsectionTitle']) ? (bool) $reviewConfig['displaySubsectionTitle'] : true;

        /* @var AssessmentTestSession $session */
        $session = $context->getTestSession();
        $extendedStorage = $this->getServiceLocator()->get(ExtendedStateService::SERVICE_ID);
        if ($session->isRunning() !== false) {
            $route         = $session->getRoute();
            $store         = $session->getAssessmentItemSessionStore();

            switch($scope){
                case RunnerMap::SCOPE_SECTION :
                    $routeItems = $route->getRouteItemsByAssessmentSection($session->getCurrentAssessmentSection());
                    break;
                case RunnerMap::SCOPE_PART :
                    $routeItems = $route->getRouteItemsByTestPart($session->getCurrentTestPart());
                    break;
                case RunnerMap::SCOPE_TEST:
                default:
                    $routeItems = $route->getAllRouteItems();
                    break;
            }

            $offset = $this->getOffsetPosition($context, $routeItems[0]);
            $offsetSection = 0;
            $lastSection   = null;

            // fallback index in case of the delivery was compiled without the index of item href
            $this->itemHrefIndex = [];
            $shouldBuildItemHrefIndex = !$this->hasItemHrefIndexFile($context, $session->getCurrentAssessmentItemRef()->getIdentifier());
            \common_Logger::t('Store index ' . ($shouldBuildItemHrefIndex ? 'must be built' : 'is part of the package'));

            /** @var \qtism\runtime\tests\RouteItem $routeItem */
            foreach ($routeItems as $routeItem) {

                $catSession = false;
                $itemRefs = $this->getRouteItemAssessmentItemRefs($context, $routeItem, $catSession);
                $previouslySeenItems = ($catSession) ? $context->getPreviouslySeenCatItemIds($routeItem) : [];

                foreach ($itemRefs as $itemRef) {
                    $occurrence = ($catSession !== false) ? 0 : $routeItem->getOccurence();

                    // get the jump definition
                    $itemSession = ($catSession !== false) ? false : $store->getAssessmentItemSession($itemRef, $occurrence);

                    // load item infos
                    $isItemInformational = ($itemSession) ? TestRunnerUtils::isItemInformational($routeItem, $itemSession) : false;
                    $testPart = $routeItem->getTestPart();
                    $partId = $testPart->getIdentifier();
                    $navigationMode = $testPart->getNavigationMode();

                    if ($displaySubsectionTitle) {
                        $section = $routeItem->getAssessmentSection();
                    } else {
                        $sections = $routeItem->getAssessmentSections()->getArrayCopy();
                        $section = $sections[0];
                    }
                    $sectionId = $section->getIdentifier();
                    $itemId = $itemRef->getIdentifier();

                    if ($lastSection != $sectionId) {
                        $offsetSection = 0;
                        $lastSection = $sectionId;
                    }

                    if ($forceInformationalTitles && $isItemInformational) {
                        $itemUri = strstr($itemRef->getHref(), '|', true);
                        $label = $uniqueInformationalTitle === false
                            ? $this->getItemLabel($context, $itemUri, $useTitle)
                            : $uniqueInformationalTitle;

                    } elseif ($forceTitles) {
                        $label = __($uniqueTitle, $offsetSection + 1);

                    } else {
                        $itemUri = strstr($itemRef->getHref(), '|', true);
                        $label = $this->getItemLabel($context, $itemUri, $useTitle);
                    }

                    // fallback in case of the delivery was compiled without the index of item href
                    if ($shouldBuildItemHrefIndex) {
                        $this->itemHrefIndex[$itemId] = $itemRef->getHref();
                    }

                    $itemInfos = [
                        'id' => $itemId,
                        'label' => $label,
                        'position' => $offset,
                        'occurrence' => $occurrence,
                        'remainingAttempts' => ($itemSession) ? $itemSession->getRemainingAttempts() : -1,
                        'answered' => ($itemSession) ? TestRunnerUtils::isItemCompleted($routeItem, $itemSession) : in_array($itemId, $previouslySeenItems),
                        'flagged' => $extendedStorage->getItemFlag($session->getSessionId(), $itemId),
                        'viewed' => ($itemSession) ? $itemSession->isPresented() : in_array($itemId, $previouslySeenItems),
                        'categories' => $itemRef->getCategories()->getArrayCopy()
                    ];

                    if ($checkInformational) {
                        $itemInfos['informational'] = $isItemInformational;
                    }

                    if($itemRef->hasTimeLimits()){
                        $itemInfos['timeConstraint'] = $this->getTimeConstraint($session, $itemRef, $navigationMode);
                    }

                    if (!isset($map['parts'][$partId]) && $scope != RunnerMap::SCOPE_SECTION) {
                        $map['parts'][$partId]['id'] = $partId;
                        $map['parts'][$partId]['label'] = $partId;
                        $map['parts'][$partId]['position'] = $offset;
                        $map['parts'][$partId]['isLinear'] = $navigationMode == NavigationMode::LINEAR;

                        if($testPart->hasTimeLimits()){
                            $map['parts'][$partId]['timeConstraint'] =  $this->getTimeConstraint($session, $testPart, $navigationMode);
                        }
                    }

                    if (!isset($map['parts'][$partId]['sections'][$sectionId])) {
                        $map['parts'][$partId]['sections'][$sectionId]['id'] = $sectionId;
                        $map['parts'][$partId]['sections'][$sectionId]['label'] = $section->getTitle();
                        $map['parts'][$partId]['sections'][$sectionId]['isCatAdaptive'] = CatUtils::isAssessmentSectionAdaptive($section);
                        $map['parts'][$partId]['sections'][$sectionId]['position'] = $offset;

                        if($section->hasTimeLimits()){
                            $map['parts'][$partId]['sections'][$sectionId]['timeConstraint'] = $this->getTimeConstraint($session, $section, $navigationMode);
                        }
                    }

                    $map['parts'][$partId]['sections'][$sectionId]['items'][$itemId] = $itemInfos;

                    // update the stats
                    if($scope == RunnerMap::SCOPE_TEST) {
                        $this->updateStats($map, $itemInfos);
                        $this->updateStats($map['parts'][$partId], $itemInfos);
                        $this->updateStats($map['parts'][$partId]['sections'][$sectionId], $itemInfos);
                    }
                    if($scope == RunnerMap::SCOPE_PART) {
                        $this->updateStats($map['parts'][$partId], $itemInfos);
                        $this->updateStats($map['parts'][$partId]['sections'][$sectionId], $itemInfos);
                    }
                    if($scope == RunnerMap::SCOPE_SECTION) {
                        $this->updateStats($map['parts'][$partId]['sections'][$sectionId], $itemInfos);
                    }

                    $offset ++;
                    if (!$forceInformationalTitles || ($forceInformationalTitles && !$isItemInformational)) {
                        $offsetSection++;
                    };
                }
            }
            // fallback in case of the delivery was compiled without the index of item href
            if ($shouldBuildItemHrefIndex) {
                \common_Logger::t('Store index of item href into the test state storage');
                $storage = $this->getServiceLocator()->get(ExtendedStateService::SERVICE_ID);
                $storage->storeItemHrefIndex($context->getTestExecutionUri(), $this->itemHrefIndex);
            }
        }

        return $map;
    }
Get the testMap for the current context but limited to the given scope
@param RunnerServiceContext $context The test context
@param RunnerConfig $config The runner config
@param string $scope the target scope, section by default
@return mixed
@throws \common_exception_InvalidArgumentType