701 lines
20 KiB
PHP
701 lines
20 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) 2013 (original work) Open Assessment Technologies SA (under the project TAO-PRODUCT);
|
|
*/
|
|
|
|
namespace oat\taoQtiItem\model\qti;
|
|
|
|
use oat\taoQtiItem\model\qti\container\FlowContainer;
|
|
use oat\taoQtiItem\model\qti\container\ContainerItemBody;
|
|
use oat\taoQtiItem\model\qti\interaction\Interaction;
|
|
use oat\taoQtiItem\model\qti\feedback\ModalFeedback;
|
|
use oat\taoQtiItem\model\qti\response\TemplatesDriven;
|
|
use oat\taoQtiItem\model\qti\exception\QtiModelException;
|
|
use \common_Serializable;
|
|
use \common_Logger;
|
|
use \common_ext_ExtensionsManager;
|
|
use \taoItems_models_classes_TemplateRenderer;
|
|
use \DOMDocument;
|
|
use oat\tao\helpers\Template;
|
|
|
|
/**
|
|
* The QTI_Item object represent the assessmentItem.
|
|
* It's the main QTI object, it contains all the other objects and
|
|
* is the main point to render a complete item.
|
|
*
|
|
* @access public
|
|
* @author Sam, <sam@taotesting.com>
|
|
* @package taoQTI
|
|
* @see http://www.imsglobal.org/question/qti_v2p0/imsqti_infov2p0.html#section10042
|
|
*/
|
|
class Item extends IdentifiedElement implements FlowContainer, IdentifiedElementContainer, common_Serializable
|
|
{
|
|
|
|
/**
|
|
* the QTI tag name as defined in QTI standard
|
|
*
|
|
* @access protected
|
|
* @var string
|
|
*/
|
|
protected static $qtiTagName = 'assessmentItem';
|
|
|
|
/**
|
|
* Item's body content
|
|
*
|
|
* @var \oat\taoQtiItem\model\qti\container\ContainerItemBody
|
|
*/
|
|
protected $body = null;
|
|
|
|
/**
|
|
* Item's response processing
|
|
*
|
|
* @access protected
|
|
* @var array
|
|
*/
|
|
protected $responses = [];
|
|
|
|
/**
|
|
* Item's response processing
|
|
*
|
|
* @access protected
|
|
* @var ResponseProcessing
|
|
*/
|
|
protected $responseProcessing = null;
|
|
|
|
/**
|
|
* Item's outcomes
|
|
*
|
|
* @access protected
|
|
* @var array
|
|
*/
|
|
protected $outcomes = [];
|
|
|
|
/**
|
|
* Item's stylesheets
|
|
*
|
|
* @access protected
|
|
* @var array
|
|
*/
|
|
protected $stylesheets = [];
|
|
|
|
/**
|
|
* Rubric blocks
|
|
*
|
|
* @access protected
|
|
* @var array
|
|
*/
|
|
protected $modalFeedbacks = [];
|
|
|
|
/**
|
|
* The namespaces defined in the original qti.xml file,
|
|
* others that the standard included ones
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $namespaces = [];
|
|
|
|
/**
|
|
* The schema locations defined in the original qti.xml file,
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $schemaLocations = [];
|
|
|
|
/**
|
|
* The information on apip accessibility.
|
|
* It is currently stored as an xml string.
|
|
* This opens opprtunity to manage APIP accessibility more thouroughly in the future
|
|
*
|
|
* @var type
|
|
*/
|
|
protected $apipAccessibility = '';
|
|
|
|
/**
|
|
* Short description of method __construct
|
|
*
|
|
* @access public
|
|
* @author Sam, <sam@taotesting.com>
|
|
* @param array $attributes
|
|
* @return mixed
|
|
*/
|
|
public function __construct($attributes = [])
|
|
{
|
|
// override the tool options !
|
|
$attributes['toolName'] = PRODUCT_NAME;
|
|
$attributes['toolVersion'] = \tao_models_classes_TaoService::singleton()->getPlatformVersion();
|
|
|
|
// create container
|
|
$this->body = new ContainerItemBody('', $this);
|
|
|
|
parent::__construct($attributes);
|
|
}
|
|
|
|
public function addNamespace($name, $uri)
|
|
{
|
|
$this->namespaces[$name] = $uri;
|
|
}
|
|
|
|
public function getNamespaces()
|
|
{
|
|
return $this->namespaces;
|
|
}
|
|
|
|
public function getNamespace($uri)
|
|
{
|
|
return array_search($uri, $this->namespaces);
|
|
}
|
|
|
|
public function addSchemaLocation($uri, $url)
|
|
{
|
|
$this->schemaLocations[$uri] = $url;
|
|
}
|
|
|
|
public function getSchemaLocations()
|
|
{
|
|
return $this->schemaLocations;
|
|
}
|
|
|
|
public function getSchemaLocation($uri)
|
|
{
|
|
return $this->schemaLocations[$uri];
|
|
}
|
|
|
|
public function setApipAccessibility($apipXml)
|
|
{
|
|
$this->apipAccessibility = $apipXml;
|
|
}
|
|
|
|
public function getApipAccessibility()
|
|
{
|
|
return $this->apipAccessibility;
|
|
}
|
|
|
|
protected function getUsedAttributes()
|
|
{
|
|
return [
|
|
'oat\\taoQtiItem\\model\\qti\\attribute\\Title',
|
|
'oat\\taoQtiItem\\model\\qti\\attribute\\Label',
|
|
'oat\\taoQtiItem\\model\\qti\\attribute\\Lang',
|
|
'oat\\taoQtiItem\\model\\qti\\attribute\\Adaptive',
|
|
'oat\\taoQtiItem\\model\\qti\\attribute\\TimeDependent',
|
|
'oat\\taoQtiItem\\model\\qti\\attribute\\ToolName',
|
|
'oat\\taoQtiItem\\model\\qti\\attribute\\ToolVersion'
|
|
];
|
|
}
|
|
|
|
public function getBody()
|
|
{
|
|
return $this->body;
|
|
}
|
|
|
|
/**
|
|
* Short description of method addInteraction
|
|
*
|
|
* @access public
|
|
* @author Sam, <sam@taotesting.com>
|
|
* @param Interaction $interaction
|
|
* @param $body
|
|
* @return bool
|
|
*/
|
|
public function addInteraction(Interaction $interaction, $body)
|
|
{
|
|
$returnValue = false;
|
|
|
|
if (!is_null($interaction)) {
|
|
$returnValue = $this->getBody()->setElement($interaction, $body);
|
|
}
|
|
|
|
return (bool) $returnValue;
|
|
}
|
|
|
|
/**
|
|
* Short description of method removeInteraction
|
|
*
|
|
* @access public
|
|
* @author Sam, <sam@taotesting.com>
|
|
* @param Interaction $interaction
|
|
* @return bool
|
|
*/
|
|
public function removeInteraction(Interaction $interaction)
|
|
{
|
|
$returnValue = false;
|
|
|
|
if (!is_null($interaction)) {
|
|
$returnValue = $this->getBody()->removeElement($interaction);
|
|
}
|
|
|
|
return (bool) $returnValue;
|
|
}
|
|
|
|
public function getInteractions()
|
|
{
|
|
return $this->getComposingElements('oat\taoQtiItem\model\qti\interaction\Interaction');
|
|
}
|
|
|
|
public function getObjects()
|
|
{
|
|
return $this->body->getElements(\oat\taoQtiItem\model\qti\QtiObject::class);
|
|
}
|
|
|
|
public function getRubricBlocks()
|
|
{
|
|
return $this->body->getElements('oat\\taoQtiItem\\model\\qti\\RubricBlock');
|
|
}
|
|
|
|
public function getRelatedItem()
|
|
{
|
|
return $this; // the related item of an item is itself!
|
|
}
|
|
|
|
/**
|
|
* Short description of method getResponseProcessing
|
|
*
|
|
* @access public
|
|
* @author Sam, <sam@taotesting.com>
|
|
* @return \oat\taoQtiItem\model\qti\response\ResponseProcessing
|
|
*/
|
|
public function getResponseProcessing()
|
|
{
|
|
return $this->responseProcessing;
|
|
}
|
|
|
|
/**
|
|
* Short description of method setResponseProcessing
|
|
*
|
|
* @access public
|
|
* @author Sam, <sam@taotesting.com>
|
|
* @param $rprocessing
|
|
* @return mixed
|
|
*/
|
|
public function setResponseProcessing($rprocessing)
|
|
{
|
|
$this->responseProcessing = $rprocessing;
|
|
}
|
|
|
|
/**
|
|
* Short description of method setOutcomes
|
|
*
|
|
* @access public
|
|
* @author Sam, <sam@taotesting.com>
|
|
* @param
|
|
* array outcomes
|
|
* @return mixed
|
|
* @throws InvalidArgumentException
|
|
*/
|
|
public function setOutcomes($outcomes)
|
|
{
|
|
$this->outcomes = [];
|
|
foreach ($outcomes as $outcome) {
|
|
if (!$outcome instanceof OutcomeDeclaration) {
|
|
throw new \InvalidArgumentException("wrong entry in outcomes list");
|
|
}
|
|
$this->addOutcome($outcome);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* add an outcome declaration to the item
|
|
*
|
|
* @param \oat\taoQtiItem\model\qti\OutcomeDeclaration $outcome
|
|
*/
|
|
public function addOutcome(OutcomeDeclaration $outcome)
|
|
{
|
|
$this->outcomes[$outcome->getSerial()] = $outcome;
|
|
$outcome->setRelatedItem($this);
|
|
}
|
|
|
|
/**
|
|
* Short description of method getOutcomes
|
|
*
|
|
* @access public
|
|
* @author Sam, <sam@taotesting.com>
|
|
* @return array
|
|
*/
|
|
public function getOutcomes()
|
|
{
|
|
return $this->outcomes;
|
|
}
|
|
|
|
/**
|
|
* Short description of method getOutcome
|
|
*
|
|
* @access public
|
|
* @author Sam, <sam@taotesting.com>
|
|
* @param
|
|
* string serial
|
|
* @return \oat\taoQtiItem\model\qti\OutcomeDeclaration
|
|
*/
|
|
public function getOutcome($serial)
|
|
{
|
|
$returnValue = null;
|
|
|
|
if (!empty($serial)) {
|
|
if (isset($this->outcomes[$serial])) {
|
|
$returnValue = $this->outcomes[$serial];
|
|
}
|
|
}
|
|
|
|
return $returnValue;
|
|
}
|
|
|
|
/**
|
|
* Short description of method removeOutcome
|
|
*
|
|
* @access public
|
|
* @author Sam, <sam@taotesting.com>
|
|
* @param OutcomeDeclaration $outcome
|
|
* @return bool
|
|
*/
|
|
public function removeOutcome(OutcomeDeclaration $outcome)
|
|
{
|
|
$returnValue = (bool) false;
|
|
|
|
if (!is_null($outcome)) {
|
|
if (isset($this->outcomes[$outcome->getSerial()])) {
|
|
unset($this->outcomes[$outcome->getSerial()]);
|
|
$returnValue = true;
|
|
}
|
|
} else {
|
|
common_Logger::w('Tried to remove null outcome');
|
|
}
|
|
|
|
if (!$returnValue) {
|
|
common_Logger::w('outcome not found ' . $outcome->getSerial());
|
|
}
|
|
|
|
return (bool) $returnValue;
|
|
}
|
|
|
|
public function addResponse(ResponseDeclaration $response)
|
|
{
|
|
$this->responses[$response->getSerial()] = $response;
|
|
$response->setRelatedItem($this);
|
|
}
|
|
|
|
public function getResponses()
|
|
{
|
|
return $this->responses;
|
|
}
|
|
|
|
public function addModalFeedback(ModalFeedback $modalFeedback)
|
|
{
|
|
$this->modalFeedbacks[$modalFeedback->getSerial()] = $modalFeedback;
|
|
$modalFeedback->setRelatedItem($this);
|
|
}
|
|
|
|
public function removeModalFeedback(ModalFeedback $modalFeedback)
|
|
{
|
|
unset($this->modalFeedbacks[$modalFeedback->getSerial()]);
|
|
}
|
|
|
|
/**
|
|
* Get the modal feedbacks of the item
|
|
*
|
|
* @access public
|
|
* @author Sam, <sam@taotesting.com>
|
|
* @return array
|
|
*/
|
|
public function getModalFeedbacks()
|
|
{
|
|
return $this->modalFeedbacks;
|
|
}
|
|
|
|
public function getModalFeedback($serial)
|
|
{
|
|
$returnValue = null;
|
|
if (isset($this->modalFeedbacks[$serial])) {
|
|
$returnValue = $this->modalFeedbacks[$serial];
|
|
}
|
|
|
|
return $returnValue;
|
|
}
|
|
|
|
/**
|
|
* Get the stylesheets of the item
|
|
*
|
|
* @access public
|
|
* @author Sam, <sam@taotesting.com>
|
|
* @return array
|
|
*/
|
|
public function getStylesheets()
|
|
{
|
|
return (array) $this->stylesheets;
|
|
}
|
|
|
|
public function addStylesheet(Stylesheet $stylesheet)
|
|
{
|
|
// @todo : validate style sheet before adding:
|
|
$this->stylesheets[$stylesheet->getSerial()] = $stylesheet;
|
|
$stylesheet->setRelatedItem($this);
|
|
}
|
|
|
|
public function removeStylesheet(Stylesheet $stylesheet)
|
|
{
|
|
unset($this->stylesheets[$stylesheet->getSerial()]);
|
|
}
|
|
|
|
public function removeResponse($response)
|
|
{
|
|
|
|
$serial = '';
|
|
if ($response instanceof ResponseDeclaration) {
|
|
$serial = $response->getSerial();
|
|
} elseif (is_string($response)) {
|
|
$serial = $response;
|
|
} else {
|
|
throw new \InvalidArgumentException('the argument must be an instance of taoQTI_models_classes_QTI_ResponseDeclaration or a string serial');
|
|
}
|
|
|
|
if (!empty($serial)) {
|
|
unset($this->responses[$serial]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get recursively all included identified QTI elements in the object (identifier => Object)
|
|
*
|
|
* @return array
|
|
*/
|
|
public function getIdentifiedElements()
|
|
{
|
|
$returnValue = $this->getBody()->getIdentifiedElements();
|
|
$returnValue->addMultiple($this->getOutcomes());
|
|
$returnValue->addMultiple($this->getResponses());
|
|
$returnValue->addMultiple($this->getModalFeedbacks());
|
|
|
|
return $returnValue;
|
|
}
|
|
|
|
/**
|
|
* Short description of method toXHTML
|
|
*
|
|
* @access public
|
|
* @author Sam, <sam@taotesting.com>
|
|
* @param array $options
|
|
* @param array $filtered
|
|
* @return string
|
|
*/
|
|
public function toXHTML($options = [], &$filtered = [])
|
|
{
|
|
|
|
$template = static::getTemplatePath() . '/xhtml.item.tpl.php';
|
|
|
|
// get the variables to use in the template
|
|
$variables = $this->getAttributeValues();
|
|
$variables['stylesheets'] = [];
|
|
foreach ($this->getStylesheets() as $stylesheet) {
|
|
$variables['stylesheets'][] = $stylesheet->getAttributeValues();
|
|
}
|
|
//additional css:
|
|
if (isset($options['css'])) {
|
|
foreach ($options['css'] as $css) {
|
|
$variables['stylesheets'][] = ['href' => $css, 'media' => 'all'];
|
|
}
|
|
}
|
|
|
|
//additional js:
|
|
$variables['javascripts'] = [];
|
|
$variables['js_variables'] = [];
|
|
if (isset($options['js'])) {
|
|
foreach ($options['js'] as $js) {
|
|
$variables['javascripts'][] = ['src' => $js];
|
|
}
|
|
}
|
|
if (isset($options['js_var'])) {
|
|
foreach ($options['js_var'] as $name => $value) {
|
|
$variables['js_variables'][$name] = $value;
|
|
}
|
|
}
|
|
|
|
//user scripts
|
|
$variables['user_scripts'] = $this->getUserScripts();
|
|
|
|
$dataForDelivery = $this->getDataForDelivery();
|
|
$variables['itemData'] = $dataForDelivery['core'];
|
|
|
|
//copy all variable data into filtered array
|
|
foreach ($dataForDelivery['variable'] as $serial => $data) {
|
|
$filtered[$serial] = $data;
|
|
}
|
|
|
|
$variables['contentVariableElements'] = isset($options['contentVariableElements']) && is_array($options['contentVariableElements']) ? $options['contentVariableElements'] : [];
|
|
$variables['tao_lib_path'] = isset($options['path']) && isset($options['path']['tao']) ? $options['path']['tao'] : '';
|
|
$variables['taoQtiItem_lib_path'] = isset($options['path']) && isset($options['path']['taoQtiItem']) ? $options['path']['taoQtiItem'] : '';
|
|
$variables['client_config_url'] = isset($options['client_config_url']) ? $options['client_config_url'] : '';
|
|
|
|
$tplRenderer = new taoItems_models_classes_TemplateRenderer($template, $variables);
|
|
|
|
$returnValue = $tplRenderer->render();
|
|
|
|
return (string) $returnValue;
|
|
}
|
|
|
|
protected function getUserScripts()
|
|
{
|
|
$userScripts = [];
|
|
|
|
$userScriptConfig = \common_ext_ExtensionsManager::singleton()->getExtensionById('taoQtiItem')->getConfig('userScripts');
|
|
if (is_array($userScriptConfig)) {
|
|
foreach ($userScriptConfig as $data) {
|
|
$userScripts[] = Template::js($data['src'], $data['extension']);
|
|
}
|
|
}
|
|
return $userScripts;
|
|
}
|
|
|
|
public static function getTemplateQti()
|
|
{
|
|
return static::getTemplatePath() . '/qti.item.tpl.php';
|
|
}
|
|
|
|
protected function getTemplateQtiVariables()
|
|
{
|
|
$variables = parent::getTemplateQtiVariables();
|
|
|
|
$variables['stylesheets'] = '';
|
|
foreach ($this->stylesheets as $stylesheet) {
|
|
$variables['stylesheets'] .= $stylesheet->toQTI();
|
|
}
|
|
|
|
$variables['responses'] = '';
|
|
foreach ($this->responses as $response) {
|
|
$variables['responses'] .= $response->toQTI();
|
|
}
|
|
|
|
$variables['outcomes'] = '';
|
|
foreach ($this->outcomes as $outcome) {
|
|
$variables['outcomes'] .= $outcome->toQTI();
|
|
}
|
|
|
|
$variables['feedbacks'] = '';
|
|
foreach ($this->modalFeedbacks as $feedback) {
|
|
$variables['feedbacks'] .= $feedback->toQTI();
|
|
}
|
|
|
|
$variables['namespaces'] = $this->getNamespaces();
|
|
$schemaLocations = '';
|
|
foreach ($this->getSchemaLocations() as $uri => $url) {
|
|
$schemaLocations .= $uri . ' ' . $url . ' ';
|
|
}
|
|
$variables['schemaLocations'] = trim($schemaLocations);
|
|
$nsXsi = $this->getNamespace('http://www.w3.org/2001/XMLSchema-instance');
|
|
$variables['xsi'] = $nsXsi ? $nsXsi . ':' : 'xsi:';
|
|
|
|
// render the responseProcessing
|
|
$renderedResponseProcessing = '';
|
|
$responseProcessing = $this->getResponseProcessing();
|
|
if (isset($responseProcessing)) {
|
|
if ($responseProcessing instanceof TemplatesDriven) {
|
|
$renderedResponseProcessing = $responseProcessing->buildQTI();
|
|
} else {
|
|
$renderedResponseProcessing = $responseProcessing->toQTI();
|
|
}
|
|
}
|
|
|
|
// move item CSS class to itemBody
|
|
$variables['class'] = $this->getAttributeValue('class');
|
|
unset($variables['attributes']['class']);
|
|
|
|
$variables['renderedResponseProcessing'] = $renderedResponseProcessing;
|
|
|
|
$variables['apipAccessibility'] = $this->getApipAccessibility();
|
|
|
|
return $variables;
|
|
}
|
|
|
|
/**
|
|
* Short description of method toQTI
|
|
*
|
|
* @access public
|
|
* @author Sam, <sam@taotesting.com>
|
|
* @param boolean $validate (optional) Validate the XML output against QTI Specification (XML Schema). Default is false.
|
|
* @return string
|
|
* @throws exception\QtiModelException
|
|
*/
|
|
public function toXML($validate = false)
|
|
{
|
|
|
|
$returnValue = '';
|
|
|
|
$qti = $this->toQTI();
|
|
|
|
// render and clean the xml
|
|
$dom = new DOMDocument('1.0', 'UTF-8');
|
|
|
|
$domDocumentConfig = \common_ext_ExtensionsManager::singleton()->getExtensionById('taoQtiItem')->getConfig('XMLParser');
|
|
|
|
if (is_array($domDocumentConfig) && !empty($domDocumentConfig)) {
|
|
foreach ($domDocumentConfig as $param => $value) {
|
|
if (property_exists($dom, $param)) {
|
|
$dom->$param = $value;
|
|
}
|
|
}
|
|
} else {
|
|
$dom->formatOutput = true;
|
|
$dom->preserveWhiteSpace = false;
|
|
$dom->validateOnParse = false;
|
|
}
|
|
|
|
if ($dom->loadXML($qti)) {
|
|
$returnValue = $dom->saveXML();
|
|
|
|
//in debug mode, systematically check if the save QTI is standard compliant
|
|
if ($validate) {
|
|
$parserValidator = new Parser($returnValue);
|
|
$parserValidator->validate();
|
|
if (!$parserValidator->isValid()) {
|
|
common_Logger::w('Invalid QTI output: ' . PHP_EOL . ' ' . $parserValidator->displayErrors());
|
|
}
|
|
}
|
|
} else {
|
|
$parserValidator = new Parser($qti);
|
|
$parserValidator->validate();
|
|
if (!$parserValidator->isValid()) {
|
|
throw new QtiModelException('Wrong QTI item output format');
|
|
}
|
|
}
|
|
|
|
return (string) $returnValue;
|
|
}
|
|
|
|
/**
|
|
* Serialize item object into json format, handy to be used in js
|
|
*/
|
|
public function toArray($filterVariableContent = false, &$filtered = [])
|
|
{
|
|
$data = parent::toArray($filterVariableContent, $filtered);
|
|
$data['namespaces'] = $this->getNamespaces();
|
|
$data['schemaLocations'] = $this->getSchemaLocations();
|
|
$data['stylesheets'] = $this->getArraySerializedElementCollection($this->getStylesheets(), $filterVariableContent, $filtered);
|
|
$data['outcomes'] = $this->getArraySerializedElementCollection($this->getOutcomes(), $filterVariableContent, $filtered);
|
|
$data['responses'] = $this->getArraySerializedElementCollection($this->getResponses(), $filterVariableContent, $filtered);
|
|
$data['feedbacks'] = $this->getArraySerializedElementCollection($this->getModalFeedbacks(), $filterVariableContent, $filtered);
|
|
$data['responseProcessing'] = $this->responseProcessing->toArray($filterVariableContent, $filtered);
|
|
$data['apipAccessibility'] = $this->getApipAccessibility();
|
|
return $data;
|
|
}
|
|
|
|
public function getDataForDelivery()
|
|
{
|
|
|
|
$filtered = [];
|
|
$itemData = $this->toArray(true, $filtered);
|
|
unset($itemData['responseProcessing']);
|
|
|
|
return ['core' => $itemData, 'variable' => $filtered];
|
|
}
|
|
|
|
/**
|
|
* @return mixed
|
|
*/
|
|
public function toForm()
|
|
{
|
|
|
|
$formContainer = new AssessmentItem($this);
|
|
$returnValue = $formContainer->getForm();
|
|
|
|
return $returnValue;
|
|
}
|
|
}
|