387 lines
14 KiB
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();
|
|
}
|
|
}
|