348 lines
12 KiB
PHP
348 lines
12 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-2020 (original work) Open Assessment Technologies SA;
|
||
|
*/
|
||
|
|
||
|
declare(strict_types=1);
|
||
|
|
||
|
namespace oat\taoMediaManager\model;
|
||
|
|
||
|
use common_exception_Error;
|
||
|
use common_exception_UserReadableException;
|
||
|
use core_kernel_classes_Class;
|
||
|
use core_kernel_classes_Resource;
|
||
|
use Exception;
|
||
|
use helpers_File;
|
||
|
use oat\oatbox\filesystem\File;
|
||
|
use common_report_Report as Report;
|
||
|
use oat\oatbox\log\LoggerAwareTrait;
|
||
|
use oat\oatbox\log\TaoLoggerAwareInterface;
|
||
|
use oat\oatbox\service\ConfigurableService;
|
||
|
use oat\oatbox\service\ServiceManager;
|
||
|
use oat\taoMediaManager\model\sharedStimulus\parser\SharedStimulusMediaExtractor;
|
||
|
use tao_helpers_File;
|
||
|
use tao_helpers_form_Form as Form;
|
||
|
use oat\tao\model\import\ImportHandlerHelperTrait;
|
||
|
use oat\tao\model\import\TaskParameterProviderInterface;
|
||
|
use qtism\data\QtiComponent;
|
||
|
use qtism\data\storage\xml\XmlDocument;
|
||
|
use qtism\data\storage\xml\XmlStorageException;
|
||
|
use tao_helpers_Uri;
|
||
|
use tao_models_classes_import_ImportHandler;
|
||
|
|
||
|
/**
|
||
|
* @access public
|
||
|
* @package taoMediaManager
|
||
|
*/
|
||
|
class SharedStimulusImporter extends ConfigurableService implements
|
||
|
tao_models_classes_import_ImportHandler,
|
||
|
TaskParameterProviderInterface,
|
||
|
TaoLoggerAwareInterface
|
||
|
{
|
||
|
use ImportHandlerHelperTrait {
|
||
|
getTaskParameters as getDefaultTaskParameters;
|
||
|
}
|
||
|
use LoggerAwareTrait;
|
||
|
|
||
|
/** @var SharedStimulusPackageImporter */
|
||
|
private $zipImporter = null;
|
||
|
|
||
|
/** @var string */
|
||
|
private $instanceUri;
|
||
|
|
||
|
public function setInstanceUri(string $instanceUri): self
|
||
|
{
|
||
|
$this->instanceUri = $instanceUri;
|
||
|
|
||
|
return $this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns a textual description of the import format
|
||
|
*
|
||
|
* @return string
|
||
|
*/
|
||
|
public function getLabel()
|
||
|
{
|
||
|
return __('Shared Stimulus');
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns a form in order to prepare the import
|
||
|
* if the import is from a file, the form should include the file element
|
||
|
*
|
||
|
* @return Form
|
||
|
*/
|
||
|
public function getForm()
|
||
|
{
|
||
|
return (new FileImportForm($this->instanceUri))
|
||
|
->getForm();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Starts the import based on the form
|
||
|
*
|
||
|
* @param core_kernel_classes_Class $class
|
||
|
* @param Form|array $form
|
||
|
* @param string|null $userId owner of the resource
|
||
|
*
|
||
|
* @return Report $report
|
||
|
*
|
||
|
* @throws common_exception_Error
|
||
|
*/
|
||
|
public function import($class, $form, $userId = null)
|
||
|
{
|
||
|
$uploadedFile = $this->fetchUploadedFile($form);
|
||
|
|
||
|
try {
|
||
|
$service = $this->getMediaService();
|
||
|
$classUri = $class->getUri();
|
||
|
|
||
|
$instanceUri = $form instanceof Form
|
||
|
? $form->getValue('instanceUri')
|
||
|
: (isset($form['instanceUri']) ? $form['instanceUri'] : null);
|
||
|
|
||
|
$fileInfo = $form instanceof Form ? $form->getValue('source') : $form['source'];
|
||
|
|
||
|
// importing new media
|
||
|
if (!$instanceUri || $instanceUri === $classUri) {
|
||
|
//if the file is a zip do a zip import
|
||
|
if (!helpers_File::isZipMimeType($fileInfo['type'])) {
|
||
|
try {
|
||
|
self::isValidSharedStimulus($uploadedFile);
|
||
|
|
||
|
$mediaResourceUri = $service->createMediaInstance(
|
||
|
$uploadedFile,
|
||
|
$classUri,
|
||
|
tao_helpers_Uri::decode($form instanceof Form ? $form->getValue('lang') : $form['lang']),
|
||
|
$fileInfo['name'],
|
||
|
MediaService::SHARED_STIMULUS_MIME_TYPE,
|
||
|
$userId
|
||
|
);
|
||
|
|
||
|
if (!$mediaResourceUri) {
|
||
|
$report = Report::createFailure(__('Fail to import Shared Stimulus'));
|
||
|
$report->setData(['uriResource' => '']);
|
||
|
} else {
|
||
|
$report = Report::createSuccess(__('Shared Stimulus imported successfully'));
|
||
|
$report->add(Report::createSuccess(
|
||
|
__('Imported %s', $fileInfo['name']),
|
||
|
['uriResource' => $mediaResourceUri] // 'uriResource' key is needed by javascript in tao/views/templates/form/import.tpl
|
||
|
));
|
||
|
}
|
||
|
} catch (XmlStorageException $e) {
|
||
|
// The shared stimulus is not qti compliant, display error
|
||
|
$report = Report::createFailure($e->getMessage());
|
||
|
$report->setData(['uriResource' => '']);
|
||
|
}
|
||
|
} else {
|
||
|
$report = $this->getZipImporter()->import($class, $form, $userId);
|
||
|
}
|
||
|
} else {
|
||
|
if (!helpers_File::isZipMimeType($fileInfo['type'])) {
|
||
|
self::isValidSharedStimulus($uploadedFile);
|
||
|
|
||
|
if (in_array($fileInfo['type'], ['application/xml', 'text/xml'])) {
|
||
|
$name = basename($fileInfo['name'], 'xml');
|
||
|
$name .= 'xhtml';
|
||
|
$filepath = tao_helpers_File::concat([dirname($fileInfo['name']), $name]);
|
||
|
$fileResource = fopen($filepath, 'w');
|
||
|
$uploadedFileResource = $uploadedFile->readStream();
|
||
|
stream_copy_to_stream($uploadedFileResource, $fileResource);
|
||
|
fclose($fileResource);
|
||
|
fclose($uploadedFileResource);
|
||
|
}
|
||
|
|
||
|
$instanceEdited = $service->editMediaInstance(
|
||
|
isset($filepath) ? $filepath : $uploadedFile,
|
||
|
$instanceUri,
|
||
|
tao_helpers_Uri::decode($form instanceof Form ? $form->getValue('lang') : $form['lang']),
|
||
|
$userId
|
||
|
);
|
||
|
|
||
|
if (!$instanceEdited) {
|
||
|
$report = Report::createFailure(__('Fail to edit shared stimulus'));
|
||
|
} else {
|
||
|
$report = Report::createSuccess(__('Shared Stimulus edited successfully'));
|
||
|
$report->add(
|
||
|
Report::createSuccess(
|
||
|
__('Edited %s', $fileInfo['name']),
|
||
|
[
|
||
|
'uriResource' => $instanceUri
|
||
|
]
|
||
|
)
|
||
|
);
|
||
|
}
|
||
|
|
||
|
$report->setData(['uriResource' => $instanceUri]);
|
||
|
} else {
|
||
|
$report = $this->getZipImporter()->edit(new core_kernel_classes_Resource($instanceUri), $form, $userId);
|
||
|
}
|
||
|
}
|
||
|
} catch (Exception $e) {
|
||
|
$message = $e instanceof common_exception_UserReadableException
|
||
|
? $e->getUserMessage()
|
||
|
: __('An error has occurred. Please contact your administrator.');
|
||
|
$report = Report::createFailure($message);
|
||
|
$report->setData(['uriResource' => '']);
|
||
|
$this->logError($e->getMessage());
|
||
|
}
|
||
|
|
||
|
$this->getUploadService()->remove($uploadedFile);
|
||
|
|
||
|
return $report;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param string|File $file
|
||
|
*
|
||
|
* @return XmlDocument
|
||
|
*
|
||
|
* @throws XmlStorageException
|
||
|
*/
|
||
|
public static function isValidSharedStimulus($file)
|
||
|
{
|
||
|
// No $version given = auto detect.
|
||
|
$xmlDocument = new XmlDocument();
|
||
|
|
||
|
// don't validate because of APIP
|
||
|
if ($file instanceof File) {
|
||
|
$xml = $file->read();
|
||
|
$xmlDocument->loadFromString($xml, false);
|
||
|
} elseif (is_file($file) && is_readable($file)) {
|
||
|
$xmlDocument->load($file, false);
|
||
|
$xml = file_get_contents($file);
|
||
|
}
|
||
|
|
||
|
// The shared stimulus is qti compliant, see if it is not an interaction, feedback or template
|
||
|
if (self::hasInteraction($xmlDocument->getDocumentComponent())) {
|
||
|
throw new XmlStorageException("The shared stimulus contains interactions QTI components.");
|
||
|
}
|
||
|
if (self::hasFeedback($xmlDocument->getDocumentComponent())) {
|
||
|
throw new XmlStorageException("The shared stimulus contains feedback QTI components.");
|
||
|
}
|
||
|
|
||
|
if (self::hasTemplate($xmlDocument->getDocumentComponent())) {
|
||
|
throw new XmlStorageException("The shared stimulus contains template QTI components.");
|
||
|
}
|
||
|
|
||
|
ServiceManager::getServiceManager()->get(SharedStimulusMediaExtractor::class)
|
||
|
->assertMediaFileExists($xml);
|
||
|
|
||
|
return $xmlDocument;
|
||
|
}
|
||
|
|
||
|
private static function hasInteraction(QtiComponent $domDocument): bool
|
||
|
{
|
||
|
$interactions = [
|
||
|
'endAttemptInteraction',
|
||
|
'inlineChoiceInteraction',
|
||
|
'textEntryInteraction',
|
||
|
'associateInteraction',
|
||
|
'choiceInteraction',
|
||
|
'drawingInteraction',
|
||
|
'extendedTextInteraction',
|
||
|
'gapMatchInteraction',
|
||
|
'graphicAssociateInteraction',
|
||
|
'graphicGapMatchInteraction',
|
||
|
'graphicOrderInteraction',
|
||
|
'hotspotInteraction',
|
||
|
'selectPointInteraction',
|
||
|
'hottextInteraction',
|
||
|
'matchInteraction',
|
||
|
'mediaInteraction',
|
||
|
'orderInteraction',
|
||
|
'sliderInteraction',
|
||
|
'uploadInteraction',
|
||
|
'customInteraction',
|
||
|
'positionObjectInteraction',
|
||
|
|
||
|
];
|
||
|
|
||
|
return self::hasComponents($domDocument, $interactions);
|
||
|
}
|
||
|
|
||
|
private static function hasFeedback(QtiComponent $domDocument): bool
|
||
|
{
|
||
|
$feedback = [
|
||
|
'feedbackBlock',
|
||
|
'feedbackInline'
|
||
|
];
|
||
|
|
||
|
return self::hasComponents($domDocument, $feedback);
|
||
|
}
|
||
|
|
||
|
private static function hasTemplate(QtiComponent $domDocument): bool
|
||
|
{
|
||
|
return self::hasComponents($domDocument, 'templateDeclaration');
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param string|string[] $className
|
||
|
*/
|
||
|
private static function hasComponents(QtiComponent $domDocument, $className): bool
|
||
|
{
|
||
|
return $domDocument->getComponentsByClassName($className)->count() > 0;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param SharedStimulusPackageImporter $zipImporter
|
||
|
* @return $this
|
||
|
*/
|
||
|
public function setZipImporter($zipImporter)
|
||
|
{
|
||
|
$this->zipImporter = $zipImporter;
|
||
|
|
||
|
return $this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get the zip importer for shared stimulus
|
||
|
*
|
||
|
* @return SharedStimulusPackageImporter
|
||
|
*/
|
||
|
protected function getZipImporter()
|
||
|
{
|
||
|
if (!$this->zipImporter) {
|
||
|
$this->zipImporter = new SharedStimulusPackageImporter();
|
||
|
$this->zipImporter->setServiceLocator($this->getServiceLocator());
|
||
|
}
|
||
|
|
||
|
return $this->zipImporter;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Defines the task parameters to be stored for later use.
|
||
|
*
|
||
|
* @param Form $form
|
||
|
* @return array
|
||
|
*/
|
||
|
public function getTaskParameters(Form $form)
|
||
|
{
|
||
|
return array_merge(
|
||
|
$form->getValues(),
|
||
|
$this->getDefaultTaskParameters($form)
|
||
|
);
|
||
|
}
|
||
|
|
||
|
private function getMediaService(): MediaService
|
||
|
{
|
||
|
return $this->getServiceLocator()->get(MediaService::class);
|
||
|
}
|
||
|
}
|