tao-test/app/taoQtiTestPreviewer/models/test/mapper/TestPreviewMapper.php

247 lines
8.4 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) 2020 (original work) Open Assessment Technologies SA ;
*/
declare(strict_types=1);
namespace oat\taoQtiTestPreviewer\models\test\mapper;
use common_Exception as Exception;
use oat\generis\model\OntologyAwareTrait;
use oat\oatbox\service\ConfigurableService;
use oat\taoQtiTestPreviewer\models\test\TestPreviewConfig;
use oat\taoQtiTestPreviewer\models\test\TestPreviewMap;
use qtism\data\AssessmentItemRef;
use qtism\data\AssessmentTest;
use qtism\data\ExtendedAssessmentItemRef;
use qtism\data\NavigationMode;
use qtism\runtime\tests\Route;
use qtism\runtime\tests\RouteItem;
use oat\taoQtiItem\model\qti\Service;
class TestPreviewMapper extends ConfigurableService implements TestPreviewMapperInterface
{
use OntologyAwareTrait;
/** @var Service */
private $service;
public function map(AssessmentTest $test, Route $route, TestPreviewConfig $config): TestPreviewMap
{
$map = [
'scope' => 'test',
'parts' => []
];
$routeItems = $route->getAllRouteItems();
$checkForInformationalItem = $config->get(TestPreviewConfig::CHECK_INFORMATIONAL);
$forceInformationalTitles = $config->get(TestPreviewConfig::REVIEW_FORCE_INFORMATION_TITLE);
$displaySubsectionTitle = $config->get(TestPreviewConfig::REVIEW_DISPLAY_SUBSECTION_TITLE) ?? true;
$map['title'] = $test->getTitle();
$map['identifier'] = $test->getIdentifier();
$map['className'] = $test->getQtiClassName();
$map['toolName'] = $test->getToolName();
$map['exclusivelyLinear'] = $test->isExclusivelyLinear();
$map['hasTimeLimits'] = $test->hasTimeLimits();
$offset = 0;
$offsetSection = 0;
$lastSection = null;
/** @var RouteItem $routeItem */
foreach ($routeItems as $routeItem) {
foreach ($this->getRouteItemAssessmentItemRefs($routeItem) as $itemRef) {
$occurrence = $routeItem->getOccurence();
$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;
}
$itemUri = $itemRef->getHref();
$itemInfos = [
'id' => $itemId,
'uri' => $itemUri,
'label' => $this->getItemLabel($itemUri),
'position' => $offset,
'occurrence' => $occurrence,
'remainingAttempts' => -1,
'answered' => 0,
'flagged' => false,
'viewed' => false,
'categories' => $itemRef->getCategories()->getArrayCopy(),
];
$isItemInformational = true;
if ($checkForInformationalItem) {
$isItemInformational = $this->isItemInformational($itemInfos['categories'], $itemRef);
$itemInfos['informational'] = $isItemInformational;
}
if ($itemRef->hasTimeLimits()) {
$itemInfos['timeConstraint'] = null; //@TODO Implement as feature
}
if (!isset($map['parts'][$partId])) {
$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'] = null; //@TODO Implement as feature
}
}
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'] = false; //@TODO Implement as feature
$map['parts'][$partId]['sections'][$sectionId]['position'] = $offset;
if ($section->hasTimeLimits()) {
$map['parts'][$partId]['sections'][$sectionId]['timeConstraint'] = null; //@TODO Implement as feature
}
}
$map['parts'][$partId]['sections'][$sectionId]['items'][$itemId] = $itemInfos;
$this->updateStats($map, $itemInfos);
$this->updateStats($map['parts'][$partId], $itemInfos);
$this->updateStats($map['parts'][$partId]['sections'][$sectionId], $itemInfos);
$offset++;
if (!$forceInformationalTitles || ($forceInformationalTitles && !$isItemInformational)) {
$offsetSection++;
}
}
}
return new TestPreviewMap($map);
}
private function updateStats(array &$target, array $itemInfos): void
{
if (!isset($target['stats'])) {
$target['stats'] = [
'questions' => 0,
'answered' => 0,
'flagged' => 0,
'viewed' => 0,
'total' => 0,
'questionsViewed' => 0,
];
}
if (empty($itemInfos['informational'])) {
$target['stats']['questions']++;
if (!empty($itemInfos['answered'])) {
$target['stats']['answered']++;
}
if (!empty($itemInfos['viewed'])) {
$target['stats']['questionsViewed']++;
}
}
if (!empty($itemInfos['flagged'])) {
$target['stats']['flagged']++;
}
if (!empty($itemInfos['viewed'])) {
$target['stats']['viewed']++;
}
$target['stats']['total']++;
}
/**
* @param RouteItem $routeItem
*
* @return AssessmentItemRef[]
*/
private function getRouteItemAssessmentItemRefs(RouteItem $routeItem): array
{
return [
$routeItem->getAssessmentItemRef()
];
}
/**
* @param string $itemUri
*
* @return string
*/
private function getItemLabel(string $itemUri): string
{
$resource = $this->getResource($itemUri);
$item = $this->getService()->getDataItemByRdfItem($resource);
return $item !== null
? $item->getAttributeValue('title')
: $resource->getLabel();
}
/**
* @return Service
*/
private function getService(): Service
{
if (!isset($this->service)) {
$this->service = $this->getServiceLocator()->get(Service::class);
}
return $this->service;
}
/**
* @param $categories
* @param AssessmentItemRef|ExtendedAssessmentItemRef $itemRef
* @return bool
*/
private function isItemInformational($categories, $itemRef): bool
{
$additionalCheck = false;
if (method_exists($itemRef, 'getResponseDeclarations') && !count($itemRef->getResponseDeclarations())) {
$additionalCheck = true;
}
return $additionalCheck || in_array('x-tao-itemusage-informational', $categories, true);
}
}