Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
46.46% covered (danger)
46.46%
46 / 99
33.33% covered (danger)
33.33%
7 / 21
CRAP
0.00% covered (danger)
0.00%
0 / 1
ItemPreviewer
46.46% covered (danger)
46.46%
46 / 99
33.33% covered (danger)
33.33%
7 / 21
298.92
0.00% covered (danger)
0.00%
0 / 1
 getFileStorage
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setUserLanguage
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setItemDefinition
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setDelivery
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 validateProperties
50.00% covered (danger)
50.00%
3 / 6
0.00% covered (danger)
0.00%
0 / 1
6.00
 loadCompiledItemData
62.50% covered (warning)
62.50%
10 / 16
0.00% covered (danger)
0.00%
0 / 1
3.47
 loadCompiledItemVariables
85.71% covered (warning)
85.71%
6 / 7
0.00% covered (danger)
0.00%
0 / 1
2.01
 getBaseUrl
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 processResponses
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
6
 getItemSessionService
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getVariableFiller
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 getQtiSmService
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getOutcomeResponseService
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getQtiXmlDoc
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
12
 getItemPublicDir
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 getItemPrivateDir
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 getItemUri
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 getItemPublicHref
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 getItemPrivateHref
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 loadItemHrefs
61.11% covered (warning)
61.11%
11 / 18
0.00% covered (danger)
0.00%
0 / 1
8.12
 getUpdateItemContentReferencesService
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
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
21namespace oat\taoQtiTestPreviewer\models;
22
23use common_Exception as CommonException;
24use common_exception_Error as ErrorException;
25use common_exception_InconsistentData as InconsistentDataException;
26use common_exception_NotFound as NotFoundException;
27use core_kernel_classes_Resource as Resource;
28use LogicException;
29use oat\generis\model\OntologyAwareTrait;
30use oat\oatbox\service\ConfigurableService;
31use oat\taoDelivery\model\container\delivery\AbstractContainer;
32use oat\taoDelivery\model\RuntimeService;
33use oat\taoItems\model\pack\ItemPack;
34use oat\taoItems\model\pack\Packer;
35use oat\taoQtiItem\helpers\QtiFile;
36use oat\taoQtiItem\model\qti\Service;
37use oat\taoQtiItem\model\QtiJsonItemCompiler;
38use oat\taoQtiTest\models\container\QtiTestDeliveryContainer;
39use oat\taoQtiTest\models\render\UpdateItemContentReferencesService;
40use qtism\common\datatypes\files\FileManagerException;
41use qtism\data\storage\StorageException;
42use qtism\data\storage\xml\XmlDocument;
43use RuntimeException;
44use tao_models_classes_service_FileStorage as FileStorage;
45use tao_models_classes_service_StorageDirectory as StorageDirectory;
46use taoQtiCommon_helpers_PciVariableFiller as PciVariableFiller;
47use taoQtiTest_models_classes_QtiTestCompiler as QtiTestCompiler;
48use taoQtiTest_models_classes_QtiTestService as QtiTestService;
49
50class ItemPreviewer extends ConfigurableService
51{
52    use OntologyAwareTrait;
53
54    /**
55     * @var string
56     */
57    private $userLanguage;
58
59    /**
60     * @var string
61     */
62    private $itemDefinition;
63
64    /**
65     * @var Resource
66     */
67    private $delivery;
68
69    /**
70     * @var string
71     */
72    private $itemUri;
73
74    /**
75     * @var StorageDirectory
76     */
77    private $itemPublicDir;
78
79    /**
80     * @var StorageDirectory
81     */
82    private $itemPrivateDir;
83
84    /**
85     * @var array
86     */
87    private $itemHrefs = [];
88
89    /**
90     * @return FileStorage
91     */
92    private function getFileStorage()
93    {
94        return $this->getServiceLocator()->get(FileStorage::SERVICE_ID);
95    }
96
97    /**
98     * @param string $userLanguage
99     * @return ItemPreviewer
100     */
101    public function setUserLanguage($userLanguage)
102    {
103        $this->userLanguage = $userLanguage;
104
105        return $this;
106    }
107
108    /**
109     * @param string $itemDefinition
110     * @return ItemPreviewer
111     */
112    public function setItemDefinition($itemDefinition)
113    {
114        $this->itemDefinition = $itemDefinition;
115
116        return $this;
117    }
118
119    /**
120     * @param Resource $delivery
121     * @return ItemPreviewer
122     * @throws NotFoundException
123     */
124    public function setDelivery($delivery)
125    {
126        if (!$delivery->exists()) {
127            throw new NotFoundException('Delivery "' . $delivery->getUri() . '" not found');
128        }
129
130        $this->delivery = $delivery;
131
132        return $this;
133    }
134
135    /**
136     * @throws LogicException
137     */
138    private function validateProperties()
139    {
140        if (
141            empty($this->userLanguage)
142            || empty($this->itemDefinition)
143            || empty($this->delivery)
144        ) {
145            throw new LogicException(
146                'UserLanguage, ItemDefinition and Delivery are mandatory for loading of compiled item data.'
147            );
148        }
149    }
150
151    /**
152     * @return array
153     *
154     * @throws CommonException
155     * @throws ErrorException
156     * @throws InconsistentDataException
157     * @throws NotFoundException
158     */
159    public function loadCompiledItemData()
160    {
161        $this->validateProperties();
162
163        $jsonFile = $this->getItemPrivateDir()->getFile(
164            $this->userLanguage . DIRECTORY_SEPARATOR . QtiJsonItemCompiler::ITEM_FILE_NAME
165        );
166        $xmlFile = $this->getItemPrivateDir()->getFile(
167            $this->userLanguage . DIRECTORY_SEPARATOR . Service::QTI_ITEM_FILE
168        );
169
170        if ($jsonFile->exists()) {
171            // new test runner is used
172            $itemData = json_decode($jsonFile->read(), true);
173        } elseif ($xmlFile->exists()) {
174            // old test runner is used
175            /** @var Packer $packer */
176            $packer = (new Packer(new Resource($this->getItemUri()), $this->userLanguage))
177                ->setServiceLocator($this->getServiceLocator());
178
179            /** @var ItemPack $itemPack */
180            $itemPack = $packer->pack();
181
182            $itemData = $itemPack->JsonSerialize();
183        } else {
184            throw new NotFoundException('Either item.json or qti.xml should exist');
185        }
186
187        return $this->getUpdateItemContentReferencesService()->__invoke($itemData);
188    }
189
190    /**
191     * @return mixed
192     * @throws InconsistentDataException
193     * @throws NotFoundException
194     */
195    public function loadCompiledItemVariables()
196    {
197        $this->validateProperties();
198
199        $variableElements = $this->getItemPrivateDir()->getFile(
200            $this->userLanguage . DIRECTORY_SEPARATOR . QtiJsonItemCompiler::VAR_ELT_FILE_NAME
201        );
202
203        if (!$variableElements->exists()) {
204            throw new NotFoundException('File variableElements.json should exist');
205        }
206
207        return json_decode($variableElements->read(), true);
208    }
209
210    /**
211     * @return string
212     * @throws CommonException
213     * @throws InconsistentDataException
214     */
215    public function getBaseUrl()
216    {
217        return $this->getItemPublicDir()->getPublicAccessUrl() . $this->userLanguage . '/';
218    }
219
220    /**
221     * Item's ResponseProcessing.
222     *
223     * @param string $itemUri
224     * @param array $jsonPayload
225     * @return array
226     * @throws FileManagerException
227     * @throws CommonException
228     */
229    public function processResponses($itemUri, $jsonPayload)
230    {
231        if (empty($itemUri)) {
232            throw new CommonException('Missing required itemUri');
233        }
234
235        $item = $this->getResource($itemUri);
236        $qtiXmlDoc = $this->getQtiXmlDoc($item);
237        $filler = $this->getVariableFiller($qtiXmlDoc);
238        $qtiSmService = $this->getQtiSmService();
239        $variables = $qtiSmService->getQtiSmVariables($filler, $jsonPayload);
240        $itemSession = $this->getItemSessionService()->getItemSession($qtiXmlDoc, $variables);
241        $itemSessionResult = $this->getOutcomeResponseService()->buildOutcomeResponse($itemSession);
242
243        // Return the item session state to the client-side.
244        return [
245            'success' => true,
246            'displayFeedback' => true,
247            'itemSession' => $itemSessionResult,
248        ];
249    }
250
251    /**
252     * @return ItemSessionService
253     */
254    private function getItemSessionService()
255    {
256        return $this->getServiceLocator()->get(ItemSessionService::class);
257    }
258
259    /**
260     * @param XmlDocument $qtiXmlDoc
261     * @return PciVariableFiller
262     */
263    private function getVariableFiller($qtiXmlDoc)
264    {
265        $docComponent = $qtiXmlDoc->getDocumentComponent();
266        return new PciVariableFiller($docComponent);
267    }
268
269    /**
270     * @return QtiSmService
271     */
272    private function getQtiSmService()
273    {
274        return $this->getServiceLocator()->get(QtiSmService::class);
275    }
276
277    /**
278     * @return OutcomeResponseService
279     */
280    private function getOutcomeResponseService()
281    {
282        return $this->getServiceLocator()->get(OutcomeResponseService::class);
283    }
284
285    /**
286     * @param Resource $item
287     * @return XmlDocument
288     * @throws CommonException
289     */
290    private function getQtiXmlDoc($item)
291    {
292        try {
293            $qtiXmlFileContent = QtiFile::getQtiFileContent($item);
294            $qtiXmlDoc = new XmlDocument();
295            $qtiXmlDoc->loadFromString($qtiXmlFileContent);
296        } catch (StorageException $e) {
297            $this->logError(($e->getPrevious() !== null) ? $e->getPrevious()->getMessage() : $e->getMessage());
298            throw new RuntimeException('An error occurred while loading QTI-XML file', 0, $e);
299        }
300
301        return $qtiXmlDoc;
302    }
303
304    /**
305     * @return StorageDirectory
306     * @throws InconsistentDataException
307     */
308    private function getItemPublicDir()
309    {
310        if ($this->itemPublicDir === null) {
311            $this->itemPublicDir = $this->getFileStorage()->getDirectoryById($this->getItemPublicHref());
312        }
313
314        return $this->itemPublicDir;
315    }
316
317    /**
318     * @return StorageDirectory
319     * @throws InconsistentDataException
320     */
321    private function getItemPrivateDir()
322    {
323        if ($this->itemPrivateDir === null) {
324            $this->itemPrivateDir = $this->getFileStorage()->getDirectoryById($this->getItemPrivateHref());
325        }
326
327        return $this->itemPrivateDir;
328    }
329
330    /**
331     * @return string
332     * @throws InconsistentDataException
333     */
334    public function getItemUri()
335    {
336        if (empty($this->itemHrefs)) {
337            $this->loadItemHrefs();
338        }
339
340        return $this->itemHrefs[0];
341    }
342
343    /**
344     * @return string
345     * @throws InconsistentDataException
346     */
347    private function getItemPublicHref()
348    {
349        if (empty($this->itemHrefs)) {
350            $this->loadItemHrefs();
351        }
352
353        return $this->itemHrefs[1];
354    }
355
356    /**
357     * @return string
358     * @throws InconsistentDataException
359     */
360    private function getItemPrivateHref()
361    {
362        if (empty($this->itemHrefs)) {
363            $this->loadItemHrefs();
364        }
365
366        return $this->itemHrefs[2];
367    }
368
369    /**
370     * @throws InconsistentDataException
371     */
372    private function loadItemHrefs()
373    {
374        $runtimeService = $this->getServiceLocator()->get(RuntimeService::SERVICE_ID);
375        /** @var AbstractContainer $deliveryContainer */
376        $deliveryContainer = $runtimeService->getDeliveryContainer($this->delivery->getUri());
377
378        $deliveryPrivateDir = null;
379        if ($deliveryContainer instanceof QtiTestDeliveryContainer) {
380            // in case of new test runner
381            $deliveryPrivateDir = $deliveryContainer->getRuntimeParams()['private'];
382        } else {
383            // in case of old test runner
384            $inParams = $deliveryContainer->getRuntimeParams()['in'];
385
386            foreach ($inParams as $param) {
387                if ($param['def'] === QtiTestService::INSTANCE_FORMAL_PARAM_TEST_COMPILATION) {
388                    $deliveryPrivateDir = explode('|', $param['const'])[0];
389                    break;
390                }
391            }
392        }
393
394        if (!$deliveryPrivateDir) {
395            throw new InconsistentDataException('Could not determine private dir of delivery');
396        }
397
398        $deliveryPrivateStorageDir = $this->getFileStorage()->getDirectoryById($deliveryPrivateDir);
399
400        $itemHrefIndexPath = QtiTestCompiler::buildHrefIndexPath($this->itemDefinition);
401
402        $itemHrefs = explode('|', $deliveryPrivateStorageDir->getFile($itemHrefIndexPath)->read());
403
404        if (count($itemHrefs) < 3) {
405            throw new InconsistentDataException('The itemRef is not formatted correctly');
406        }
407
408        $this->itemHrefs = $itemHrefs;
409    }
410
411    private function getUpdateItemContentReferencesService(): UpdateItemContentReferencesService
412    {
413        return $this->getServiceLocator()->getContainer()->get(UpdateItemContentReferencesService::class);
414    }
415}