Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 113
0.00% covered (danger)
0.00%
0 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
Previewer
0.00% covered (danger)
0.00%
0 / 113
0.00% covered (danger)
0.00%
0 / 9
812
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 init
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
6
 getItem
0.00% covered (danger)
0.00%
0 / 45
0.00% covered (danger)
0.00%
0 / 1
56
 asset
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
12
 submitItem
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
6
 getErrorResponse
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
30
 getErrorCode
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
42
 getItemPreviewer
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 getPayload
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3/**
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; under version 2
7 * of the License (non-upgradable).
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
17 *
18 * Copyright (c) 2018-2020 (original work) Open Assessment Technologies SA ;
19 */
20
21declare(strict_types=1);
22
23namespace oat\taoQtiTestPreviewer\controller;
24
25use Exception;
26use common_exception_Error;
27use oat\tao\helpers\Base64;
28use tao_helpers_Http as HttpHelper;
29use oat\taoItems\model\pack\Packer;
30use common_Exception as CommonException;
31use taoItems_models_classes_ItemsService;
32use oat\generis\model\OntologyAwareTrait;
33use tao_actions_ServiceModule as ServiceModule;
34use oat\taoItems\model\media\ItemMediaResolver;
35use oat\taoQtiTestPreviewer\models\ItemPreviewer;
36use oat\tao\model\media\sourceStrategy\HttpSource;
37use oat\tao\model\routing\AnnotationReader\security;
38use common_exception_BadRequest as BadRequestException;
39use taoQtiTest_helpers_TestRunnerUtils as TestRunnerUtils;
40use oat\taoQtiTestPreviewer\models\PreviewLanguageService;
41use common_exception_Unauthorized as UnauthorizedException;
42use common_exception_NotImplemented as NotImplementedException;
43use common_exception_MissingParameter as MissingParameterException;
44use common_exception_NoImplementation as NoImplementationException;
45use common_exception_UserReadableException as UserReadableException;
46use tao_models_classes_FileNotFoundException as FileNotFoundException;
47
48/**
49 * Class Previewer
50 *
51 * @package oat\taoQtiTestPreviewer\controller
52 */
53class Previewer extends ServiceModule
54{
55    use OntologyAwareTrait;
56
57    /**
58     * Previewer constructor.
59     *
60     * @security("hide")
61     */
62    public function __construct()
63    {
64        parent::__construct();
65
66        // Prevent anything to be cached by the client.
67        TestRunnerUtils::noHttpClientCache();
68    }
69
70    /**
71     * Initializes the delivery session
72     */
73    public function init(): void
74    {
75        $code = 200;
76
77        try {
78            $requestParams = $this->getPsrRequest()->getQueryParams();
79            $serviceCallId = $requestParams['serviceCallId'];
80
81            $response = [
82                'success' => $serviceCallId === 'previewer',
83                'itemIdentifier' => null,
84                'itemData' => null,
85            ];
86        } catch (Exception $e) {
87            $response = $this->getErrorResponse($e);
88            $code = $this->getErrorCode($e);
89        }
90
91        $this->returnJson($response, $code);
92    }
93
94    /**
95     * Provides the definition data and the state for a particular item
96     *
97     * @param taoItems_models_classes_ItemsService $itemsService
98     */
99    public function getItem(taoItems_models_classes_ItemsService $itemsService): void
100    {
101        $code = 200;
102
103        try {
104            $requestParams = $this->getPsrRequest()->getQueryParams();
105
106            $itemUri = $requestParams['itemUri'] ?? '';
107            $resultId = $requestParams['resultId'] ?? '';
108
109            $response = [
110                'baseUrl' => '',
111                'content' => [],
112            ];
113
114            // Previewing a result.
115            if ($resultId !== '') {
116                if (!isset($requestParams['itemDefinition'])) {
117                    throw new MissingParameterException('itemDefinition', $this->getRequestURI());
118                }
119
120                if (!isset($requestParams['deliveryUri'])) {
121                    throw new MissingParameterException('deliveryUri', $this->getRequestURI());
122                }
123
124                $itemDefinition = $requestParams['itemDefinition'];
125                $delivery = $this->getResource($requestParams['deliveryUri']);
126
127                /** @var ItemPreviewer $itemPreviewer */
128                $itemPreviewer = $this->getServiceLocator()->get(ItemPreviewer::class);
129                $itemPreviewer->setServiceLocator($this->getServiceLocator());
130
131                /** @var PreviewLanguageService $previewLanguageService */
132                $previewLanguageService = $this->getServiceLocator()->get(PreviewLanguageService::class);
133                $previewLanguage = $previewLanguageService->getPreviewLanguage($delivery->getUri(), $resultId);
134
135                $response['content'] = $itemPreviewer
136                    ->setItemDefinition($itemDefinition)
137                    ->setUserLanguage($previewLanguage)
138                    ->setDelivery($delivery)
139                    ->loadCompiledItemData();
140
141                $response['baseUrl'] = $itemPreviewer->getBaseUrl();
142            } elseif ($itemUri) {
143                $item = $this->getResource($itemUri);
144                $lang = $this->getSession()->getDataLanguage();
145
146                if (!$itemsService->hasItemContent($item, $lang)) {
147                    $this->returnJson($response, $code);
148                    return;
149                }
150
151                $packer = new Packer($item, $lang, true);
152                $packer->setServiceLocator($this->getServiceLocator());
153
154                $itemPack = $packer->pack();
155                $response['content'] = $itemPack->JsonSerialize();
156                $response['baseUrl'] = _url('asset', null, null, [
157                    'uri' => $itemUri,
158                    'path' => '',
159                ]);
160            } else {
161                throw new BadRequestException('Either itemUri or resultId needs to be provided.');
162            }
163
164            $response['success'] = true;
165        } catch (Exception $e) {
166            $response = $this->getErrorResponse($e);
167            $code = $this->getErrorCode($e);
168        }
169
170        $this->returnJson($response, $code);
171    }
172
173    /**
174     * Gets access to an asset
175     *
176     * @throws CommonException
177     * @throws FileNotFoundException
178     * @throws common_exception_Error
179     */
180    public function asset(): void
181    {
182        $requestParams = $this->getPsrRequest()->getQueryParams();
183
184        $item = $this->getResource($requestParams['uri']);
185        $lang = $this->getSession()->getDataLanguage();
186        $resolver = new ItemMediaResolver($item, $lang);
187
188        $asset = $resolver->resolve($requestParams['path']);
189        $mediaSource = $asset->getMediaSource();
190        $mediaIdentifier = $asset->getMediaIdentifier();
191
192        if ($mediaSource instanceof HttpSource || Base64::isEncodedImage($mediaIdentifier)) {
193            throw new CommonException('Only tao files available for rendering through item preview');
194        }
195
196        $info = $mediaSource->getFileInfo($mediaIdentifier);
197        $stream = $mediaSource->getFileStream($mediaIdentifier);
198
199        HttpHelper::returnStream($stream, $info['mime']);
200    }
201
202    /**
203     * Stores the state object and the response set of a particular item
204     */
205    public function submitItem(): void
206    {
207        $code = 200;
208
209        try {
210            $requestParams = $this->getPsrRequest()->getQueryParams();
211            $itemUri = $requestParams['itemUri'];
212            $jsonPayload = $this->getPayload();
213            $response = $this->getItemPreviewer()->processResponses($itemUri, $jsonPayload);
214        } catch (Exception $e) {
215            $response = $this->getErrorResponse($e);
216            $code = $this->getErrorCode($e);
217        }
218
219        $this->returnJson($response, $code);
220    }
221
222    /**
223     * Gets an error response array
224     *
225     * @param Exception $e
226     *
227     * @return array
228     */
229    protected function getErrorResponse(Exception $e): array
230    {
231        $response = [
232            'success' => false,
233            'type' => 'error',
234        ];
235
236        if ($e instanceof FileNotFoundException) {
237            $response['type'] = 'FileNotFound';
238            $response['message'] = __('File not found');
239        } elseif ($e instanceof UnauthorizedException) {
240            $response['code'] = 403;
241            $response['message'] = $e->getUserMessage();
242        } elseif ($e instanceof UserReadableException) {
243            $response['message'] = $e->getUserMessage();
244        } elseif ($e instanceof Exception) {
245            $response['type'] = 'exception';
246            $response['code'] = $e->getCode();
247            $response['message'] = $e->getMessage();
248        } else {
249            $response['message'] = __('An error occurred!');
250        }
251
252        return $response;
253    }
254
255    /**
256     * Gets an HTTP response code
257     *
258     * @param Exception $e
259     *
260     * @return int
261     */
262    protected function getErrorCode(Exception $e): int
263    {
264        switch (true) {
265            case $e instanceof NotImplementedException:
266            case $e instanceof NoImplementationException:
267            case $e instanceof UnauthorizedException:
268                $code = 403;
269                break;
270
271            case $e instanceof FileNotFoundException:
272                $code = 404;
273                break;
274
275            default:
276                $code = 500;
277                break;
278        }
279
280        return $code;
281    }
282
283    /**
284     * @return ItemPreviewer
285     */
286    private function getItemPreviewer(): ItemPreviewer
287    {
288        /** @var ItemPreviewer $itemPreviewer */
289        $itemPreviewer = $this->getServiceLocator()->get(ItemPreviewer::class);
290
291        return $itemPreviewer;
292    }
293
294    /**
295     * Gets payload from the request
296     *
297     * @return array|mixed|object|null
298     */
299    private function getPayload()
300    {
301        $jsonPayload = $this->getPsrRequest()->getParsedBody();
302
303        return json_decode($jsonPayload['itemResponse'], true);
304    }
305}