tao-test/app/taoQtiTestPreviewer/controller/Previewer.php

306 lines
9.7 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) 2018-2020 (original work) Open Assessment Technologies SA ;
*/
declare(strict_types=1);
namespace oat\taoQtiTestPreviewer\controller;
use Exception;
use common_exception_Error;
use oat\tao\helpers\Base64;
use tao_helpers_Http as HttpHelper;
use oat\taoItems\model\pack\Packer;
use common_Exception as CommonException;
use taoItems_models_classes_ItemsService;
use oat\generis\model\OntologyAwareTrait;
use tao_actions_ServiceModule as ServiceModule;
use oat\taoItems\model\media\ItemMediaResolver;
use oat\taoQtiTestPreviewer\models\ItemPreviewer;
use oat\tao\model\media\sourceStrategy\HttpSource;
use oat\tao\model\routing\AnnotationReader\security;
use common_exception_BadRequest as BadRequestException;
use taoQtiTest_helpers_TestRunnerUtils as TestRunnerUtils;
use oat\taoQtiTestPreviewer\models\PreviewLanguageService;
use common_exception_Unauthorized as UnauthorizedException;
use common_exception_NotImplemented as NotImplementedException;
use common_exception_MissingParameter as MissingParameterException;
use common_exception_NoImplementation as NoImplementationException;
use common_exception_UserReadableException as UserReadableException;
use tao_models_classes_FileNotFoundException as FileNotFoundException;
/**
* Class Previewer
*
* @package oat\taoQtiTestPreviewer\controller
*/
class Previewer extends ServiceModule
{
use OntologyAwareTrait;
/**
* Previewer constructor.
*
* @security("hide")
*/
public function __construct()
{
parent::__construct();
// Prevent anything to be cached by the client.
TestRunnerUtils::noHttpClientCache();
}
/**
* Initializes the delivery session
*/
public function init(): void
{
$code = 200;
try {
$requestParams = $this->getPsrRequest()->getQueryParams();
$serviceCallId = $requestParams['serviceCallId'];
$response = [
'success' => $serviceCallId === 'previewer',
'itemIdentifier' => null,
'itemData' => null,
];
} catch (Exception $e) {
$response = $this->getErrorResponse($e);
$code = $this->getErrorCode($e);
}
$this->returnJson($response, $code);
}
/**
* Provides the definition data and the state for a particular item
*
* @param taoItems_models_classes_ItemsService $itemsService
*/
public function getItem(taoItems_models_classes_ItemsService $itemsService): void
{
$code = 200;
try {
$requestParams = $this->getPsrRequest()->getQueryParams();
$itemUri = $requestParams['itemUri'] ?? '';
$resultId = $requestParams['resultId'] ?? '';
$response = [
'baseUrl' => '',
'content' => [],
];
// Previewing a result.
if ($resultId !== '') {
if (!isset($requestParams['itemDefinition'])) {
throw new MissingParameterException('itemDefinition', $this->getRequestURI());
}
if (!isset($requestParams['deliveryUri'])) {
throw new MissingParameterException('deliveryUri', $this->getRequestURI());
}
$itemDefinition = $requestParams['itemDefinition'];
$delivery = $this->getResource($requestParams['deliveryUri']);
/** @var ItemPreviewer $itemPreviewer */
$itemPreviewer = $this->getServiceLocator()->get(ItemPreviewer::class);
$itemPreviewer->setServiceLocator($this->getServiceLocator());
/** @var PreviewLanguageService $previewLanguageService */
$previewLanguageService = $this->getServiceLocator()->get(PreviewLanguageService::class);
$previewLanguage = $previewLanguageService->getPreviewLanguage($delivery->getUri(), $resultId);
$response['content'] = $itemPreviewer
->setItemDefinition($itemDefinition)
->setUserLanguage($previewLanguage)
->setDelivery($delivery)
->loadCompiledItemData();
$response['baseUrl'] = $itemPreviewer->getBaseUrl();
} elseif ($itemUri) {
$item = $this->getResource($itemUri);
$lang = $this->getSession()->getDataLanguage();
if (!$itemsService->hasItemContent($item, $lang)) {
$this->returnJson($response, $code);
return;
}
$packer = new Packer($item, $lang);
$packer->setServiceLocator($this->getServiceLocator());
$itemPack = $packer->pack();
$response['content'] = $itemPack->JsonSerialize();
$response['baseUrl'] = _url('asset', null, null, [
'uri' => $itemUri,
'path' => '',
]);
} else {
throw new BadRequestException('Either itemUri or resultId needs to be provided.');
}
$response['success'] = true;
} catch (Exception $e) {
$response = $this->getErrorResponse($e);
$code = $this->getErrorCode($e);
}
$this->returnJson($response, $code);
}
/**
* Gets access to an asset
*
* @throws CommonException
* @throws FileNotFoundException
* @throws common_exception_Error
*/
public function asset(): void
{
$requestParams = $this->getPsrRequest()->getQueryParams();
$item = $this->getResource($requestParams['uri']);
$lang = $this->getSession()->getDataLanguage();
$resolver = new ItemMediaResolver($item, $lang);
$asset = $resolver->resolve($requestParams['path']);
$mediaSource = $asset->getMediaSource();
$mediaIdentifier = $asset->getMediaIdentifier();
if ($mediaSource instanceof HttpSource || Base64::isEncodedImage($mediaIdentifier)) {
throw new CommonException('Only tao files available for rendering through item preview');
}
$info = $mediaSource->getFileInfo($mediaIdentifier);
$stream = $mediaSource->getFileStream($mediaIdentifier);
HttpHelper::returnStream($stream, $info['mime']);
}
/**
* Stores the state object and the response set of a particular item
*/
public function submitItem(): void
{
$code = 200;
try {
$requestParams = $this->getPsrRequest()->getQueryParams();
$itemUri = $requestParams['itemUri'];
$jsonPayload = $this->getPayload();
$response = $this->getItemPreviewer()->processResponses($itemUri, $jsonPayload);
} catch (Exception $e) {
$response = $this->getErrorResponse($e);
$code = $this->getErrorCode($e);
}
$this->returnJson($response, $code);
}
/**
* Gets an error response array
*
* @param Exception $e
*
* @return array
*/
protected function getErrorResponse(Exception $e): array
{
$response = [
'success' => false,
'type' => 'error',
];
if ($e instanceof FileNotFoundException) {
$response['type'] = 'FileNotFound';
$response['message'] = __('File not found');
} elseif ($e instanceof UnauthorizedException) {
$response['code'] = 403;
$response['message'] = $e->getUserMessage();
} elseif ($e instanceof UserReadableException) {
$response['message'] = $e->getUserMessage();
} elseif ($e instanceof Exception) {
$response['type'] = 'exception';
$response['code'] = $e->getCode();
$response['message'] = $e->getMessage();
} else {
$response['message'] = __('An error occurred!');
}
return $response;
}
/**
* Gets an HTTP response code
*
* @param Exception $e
*
* @return int
*/
protected function getErrorCode(Exception $e): int
{
switch (true) {
case $e instanceof NotImplementedException:
case $e instanceof NoImplementationException:
case $e instanceof UnauthorizedException:
$code = 403;
break;
case $e instanceof FileNotFoundException:
$code = 404;
break;
default:
$code = 500;
break;
}
return $code;
}
/**
* @return ItemPreviewer
*/
private function getItemPreviewer(): ItemPreviewer
{
/** @var ItemPreviewer $itemPreviewer */
$itemPreviewer = $this->getServiceLocator()->get(ItemPreviewer::class);
return $itemPreviewer;
}
/**
* Gets payload from the request
*
* @return array|mixed|object|null
*/
private function getPayload()
{
$jsonPayload = $this->getPsrRequest()->getParsedBody();
return json_decode($jsonPayload['itemResponse'], true);
}
}