tao-test/app/taoQtiTest/models/classes/export/class.QtiTestExporter.php

387 lines
14 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) 2014 (original work) Open Assessment Technologies SA (under the project TAO-PRODUCT);
*
*/
use qtism\data\storage\xml\XmlDocument;
use oat\oatbox\filesystem\Directory;
use oat\taoQtiItem\model\qti\metadata\exporter\MetadataExporter;
use oat\taoQtiItem\model\qti\metadata\MetadataService;
use oat\oatbox\service\ServiceManager;
use oat\taoQtiTest\models\export\preprocessor\AssessmentItemRefPreProcessor;
/**
* A specialization of QTI ItemExporter aiming at exporting IMS QTI Test definitions from the TAO
* platform to a ZIP archive.
*
* @author Jérôme Bogaerts <jerome@taotesting.com>
*
*/
class taoQtiTest_models_classes_export_QtiTestExporter extends taoItems_models_classes_ItemExporter
{
/**
* The QTISM XmlDocument representing the Test to be
* exported.
*
* @var XmlDocument
*/
private $testDocument;
/**
* A reference to the QTI Test Service.
*
* @var taoQtiTest_models_classes_QtiTestService
*/
private $testService;
/**
* An array of items related to the current Test Export. The array is associative. Its
* keys are actually the assessmentItemRef identifiers.
*
* @var array
*/
private $items;
/**
* A DOMDocument representing the IMS Manifest to be
* populated while exporting the Test.
*
* @var DOMDocument
*/
private $manifest = null;
/**
* @var MetadataExporter Service to export metadata to IMSManifest
*/
protected $metadataExporter;
/**
* Create a new instance of QtiTestExport.
*
* @param core_kernel_classes_Resource $test The Resource in the ontology representing the QTI Test to be exported.
* @param ZipArchive $zip An instance of ZipArchive were components of the QTI Test will be stored into.
* @param DOMDocument $manifest A DOMDocument representing the IMS Manifest to be populated during the Test Export.
*/
public function __construct(core_kernel_classes_Resource $test, ZipArchive $zip, DOMDocument $manifest)
{
parent::__construct($test, $zip);
$this->setTestService(taoQtiTest_models_classes_QtiTestService::singleton());
$this->setTestDocument($this->getTestService()->getDoc($test));
$this->setItems($this->getTestService()->getItems($test));
$this->setManifest($manifest);
}
/**
* Set the QTISM XmlDocument which holds the QTI Test definition to be exported.
*
* @param XmlDocument $testDocument
*/
protected function setTestDocument(XmlDocument $testDocument)
{
$this->testDocument = $testDocument;
}
/**
* Get the QTISM XmlDocument which holds the QTI Test definition to be exported.
*
* @return XmlDocument
*/
protected function getTestDocument()
{
return $this->testDocument;
}
/**
* Set a reference on the QTI Test Service.
*
* @param taoQtiTest_models_classes_QtiTestService $service
*/
protected function setTestService(taoQtiTest_models_classes_QtiTestService $service)
{
$this->testService = $service;
}
/**
* Get a reference on the QTI Test Service.
*
* @return taoQtiTest_models_classes_QtiTestService
*/
protected function getTestService()
{
return $this->testService;
}
/**
* Set the array of items that are involved in the QTI Test Definition to
* be exported.
*
* @param array $items An associative array where keys are assessmentItemRef identifiers and values are core_kernel_classes_Resource objects representing the items in the knowledge base.
*/
protected function setItems(array $items)
{
$this->items = $items;
}
/**
* Get the array of items that are involved in the QTI Test Definition
* to be exported.
*
* @return array An associative array where keys are assessmentItemRef identifiers and values are core_kernel_classes_Resource objects representing the items in the knowledge base.
*/
protected function getItems()
{
return $this->items;
}
/**
* Set the DOMDocument representing the IMS Manifest to be
* populated during Test Export.
*
* @param DOMDocument $manifest
*/
protected function setManifest(DOMDocument $manifest)
{
$this->manifest = $manifest;
}
/**
* Get the DOMDocument representing the IMS Manifest to
* be populated during Test Export.
*
* @return DOMDocument
*/
public function getManifest()
{
return $this->manifest;
}
public function preProcessing()
{
if ($this->getServiceManager()->has(AssessmentItemRefPreProcessor::SERVICE_ID)) {
/** @var AssessmentItemRefPreProcessor $preprocessor */
$preprocessor = $this->getServiceManager()->get(AssessmentItemRefPreProcessor::SERVICE_ID);
$items = $preprocessor->process($this->testDocument);
$this->setItems($items);
}
}
/**
* Export the test definition and all its dependencies (media, items, ...) into
* the related ZIP archive.
*
* @param array $options An array of options (not used by this implementation).
* @return common_report_Report
*/
public function export($options = [])
{
$this->preProcessing();
// 1. Export the items bound to the test.
$report = $this->exportItems();
$itemIdentifiers = $report->getData();
// 2. Export the test definition itself.
$this->exportTest($itemIdentifiers);
// 3. Export test metadata to manifest
$this->getMetadataExporter()->export($this->getItem(), $this->getManifest());
// 4. Persist manifest in archive.
$this->getZip()->addFromString('imsmanifest.xml', $this->getManifest()->saveXML());
return $report;
}
/**
* Export the dependent items into the ZIP archive.
*
* @return common_report_Report that contains An array of identifiers that were assigned to exported items into the IMS Manifest.
*/
protected function exportItems()
{
$report = common_report_Report::createSuccess(__('Export successful for the test "%s"', $this->getItem()->getLabel()));
$identifiers = [];
foreach ($this->getItems() as $refIdentifier => $item) {
$itemExporter = $this->createItemExporter($item);
if (!in_array($itemExporter->buildIdentifier(), $identifiers)) {
$identifiers[] = $itemExporter->buildIdentifier();
$subReport = $itemExporter->export();
}
// Modify the reference to the item in the test definition.
$newQtiItemXmlPath = '../../items/' . tao_helpers_Uri::getUniqueId($item->getUri()) . '/qti.xml';
$itemRef = $this->getTestDocument()->getDocumentComponent()->getComponentByIdentifier($refIdentifier);
$itemRef->setHref($newQtiItemXmlPath);
if (
$report->getType() !== common_report_Report::TYPE_ERROR &&
($subReport->containsError() || $subReport->getType() === common_report_Report::TYPE_ERROR)
) {
//only report erros otherwise the list of report can be very long
$report->setType(common_report_Report::TYPE_ERROR);
$report->setMessage(__('Export failed for the test "%s"', $this->getItem()->getLabel()));
$report->add($subReport);
}
}
$report->setData($identifiers);
return $report;
}
/**
* Export the Test definition itself and its related media.
*
* @param array $itemIdentifiers An array of identifiers that were assigned to exported items into the IMS Manifest.
*/
protected function exportTest(array $itemIdentifiers)
{
$testXmlDocument = $this->postProcessing($this->getTestDocument()->saveToString());
$newTestDir = 'tests/' . tao_helpers_Uri::getUniqueId($this->getItem()->getUri()) . '/';
$testRootDir = $this->getTestService()->getQtiTestDir($this->getItem());
$testHref = $newTestDir . 'test.xml';
common_Logger::t('TEST DEFINITION AT: ' . $testHref);
$this->getZip()->addFromString($testHref, $testXmlDocument);
$this->referenceTest($testHref, $itemIdentifiers);
$iterator = $testRootDir->getFlyIterator(Directory::ITERATOR_RECURSIVE | Directory::ITERATOR_FILE);
$indexFile = pathinfo(taoQtiTest_models_classes_QtiTestService::QTI_TEST_DEFINITION_INDEX, PATHINFO_BASENAME);
foreach ($iterator as $f) {
// Only add dependency files...
if ($f->getBasename() !== taoQtiTest_models_classes_QtiTestService::TAOQTITEST_FILENAME && $f->getBasename() !== $indexFile) {
// Add the file to the archive.
$fileHref = $newTestDir . $f->getBaseName();
common_Logger::t('AUXILIARY FILE AT: ' . $fileHref);
$this->getZip()->addFromString($fileHref, $f->read());
$this->referenceAuxiliaryFile($fileHref);
}
}
}
/**
* Reference the test into the IMS Manifest.
*
* @param string $href The path (base path is the ZIP archive) to the test definition.
* @param array $itemIdentifiers An array of identifiers that were assigned to exported items into the IMS Manifest.
*/
protected function referenceTest($href, array $itemIdentifiers)
{
$identifier = tao_helpers_Uri::getUniqueId($this->getItem()->getUri());
$manifest = $this->getManifest();
// Identifiy the target node.
$resources = $manifest->getElementsByTagName('resources');
$targetElt = $resources->item(0);
// Create the IMS Manifest <resource> element.
$resourceElt = $manifest->createElement('resource');
$resourceElt->setAttribute('identifier', $identifier);
$resourceElt->setAttribute('type', $this->getTestResourceType());
$resourceElt->setAttribute('href', $href);
$targetElt->appendChild($resourceElt);
// Append an IMS Manifest <file> element referencing the test definition.
$fileElt = $manifest->createElement('file');
$fileElt->setAttribute('href', $href);
$resourceElt->appendChild($fileElt);
foreach ($itemIdentifiers as $itemIdentifier) {
$this->referenceDependency($itemIdentifier);
}
}
/**
* Reference a test dependency (i.e. Items related to the test) in the IMS Manifest.
*
* @param string $identifierRef The identifier of the item resource in the IMS Manifest.
*/
protected function referenceDependency($identifierRef)
{
$xpath = new DOMXpath($this->getManifest());
$identifier = tao_helpers_Uri::getUniqueId($this->getItem()->getUri());
$manifest = $this->getManifest();
$search = $xpath->query("//resource[@identifier='${identifier}']");
$resourceElt = $search->item(0);
// Append IMS Manifest <dependency> elements referencing $identifierRef.
$dependencyElt = $manifest->createElement('dependency');
$dependencyElt->setAttribute('identifierref', $identifierRef);
$resourceElt->appendChild($dependencyElt);
}
/**
* Reference a Test Auxiliary File (e.g. media, stylesheet, ...) in the IMS Manifest.
*
* @param string $href The path (base path is the ZIP archive) to the auxiliary file in the ZIP archive.
*/
protected function referenceAuxiliaryFile($href)
{
$manifest = $this->getManifest();
$testIdentifier = tao_helpers_Uri::getUniqueId($this->getItem()->getUri());
$xpath = new DOMXPath($manifest);
// Find the first <dependency> element.
$dependencies = $xpath->query("//resource[@identifier='${testIdentifier}']/dependency");
$firstDependencyElt = $dependencies->item(0);
// Create an IMS Manifest <file> element.
$fileElt = $manifest->createElement('file');
$fileElt->setAttribute('href', ltrim($href, '/'));
$firstDependencyElt->parentNode->insertBefore($fileElt, $firstDependencyElt);
}
protected function createItemExporter(core_kernel_classes_Resource $item)
{
return new taoQtiTest_models_classes_export_QtiItemExporter($item, $this->getZip(), $this->getManifest());
}
protected function getTestResourceType()
{
return 'imsqti_test_xmlv2p1';
}
protected function postProcessing($testXmlDocument)
{
return $testXmlDocument;
}
/**
* Get the service to export Metadata
*
* @return MetadataExporter
*/
protected function getMetadataExporter()
{
if (! $this->metadataExporter) {
$this->metadataExporter = $this->getServiceManager()->get(MetadataService::SERVICE_ID)->getExporter();
}
return $this->metadataExporter;
}
protected function getServiceManager()
{
return ServiceManager::getServiceManager();
}
}