Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
6.48% covered (danger)
6.48%
7 / 108
10.00% covered (danger)
10.00%
1 / 10
CRAP
0.00% covered (danger)
0.00%
0 / 1
QtiJsonItemCompiler
6.48% covered (danger)
6.48%
7 / 108
10.00% covered (danger)
10.00%
1 / 10
455.66
0.00% covered (danger)
0.00%
0 / 1
 compileJson
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 deployQtiItem
0.00% covered (danger)
0.00%
0 / 50
0.00% covered (danger)
0.00%
0 / 1
20
 createQtiItem
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
 parseAndReplaceAssetByPlaceholder
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
12
 convertXmlAttributes
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
6
 getItemPortableElements
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
2
 getMetadataProperties
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
 getQtiItemAssetCompiler
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getXIncludeXmlInjector
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getItemAssetXmlReplacer
0.00% covered (danger)
0.00%
0 / 1
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) 2016-2020 (original work) Open Assessment Technologies SA (under the project TAO-PRODUCT);
19 *
20 */
21
22declare(strict_types=1);
23
24namespace oat\taoQtiItem\model;
25
26use common_exception_Error;
27use common_report_Report;
28use core_kernel_classes_Resource;
29use DOMDocument;
30use Exception;
31use oat\oatbox\filesystem\Directory;
32use oat\taoItems\model\media\ItemMediaResolver;
33use oat\taoQtiItem\model\compile\QtiAssetCompiler\QtiItemAssetCompiler;
34use oat\taoQtiItem\model\compile\QtiAssetCompiler\QtiItemAssetXmlReplacer;
35use oat\taoQtiItem\model\compile\QtiAssetCompiler\XIncludeXmlInjector;
36use oat\taoQtiItem\model\pack\QtiItemPacker;
37use oat\taoQtiItem\model\portableElement\PortableElementService;
38use oat\taoQtiItem\model\qti\Element;
39use oat\taoQtiItem\model\qti\exception\XIncludeException;
40use oat\taoQtiItem\model\qti\Item;
41use oat\taoQtiItem\model\qti\Parser;
42use oat\taoQtiItem\model\qti\Service;
43use tao_helpers_Xml;
44use tao_models_classes_service_StorageDirectory;
45use taoItems_models_classes_CompilationFailedException;
46use Throwable;
47
48/**
49 * The QTI Json Item Compiler
50 *
51 * @access public
52 * @author Antoine Robin
53 * @package taoItems
54 */
55class QtiJsonItemCompiler extends QtiItemCompiler
56{
57    public const ITEM_FILE_NAME = 'item.json';
58    public const VAR_ELT_FILE_NAME = 'variableElements.json';
59    public const METADATA_FILE_NAME = 'metadataElements.json';
60    public const PORTABLE_ELEMENT_FILE_NAME = 'portableElements.json';
61
62    /**
63     * @var string json from the item packed
64     */
65    private $itemJson;
66
67    /**
68     * Generate JSON version of item
69     * @return common_report_Report
70     * @throws taoItems_models_classes_CompilationFailedException
71     */
72    public function compileJson()
73    {
74        $report = $this->internalCompile();
75        if ($report->getType() == common_report_Report::TYPE_SUCCESS) {
76            // replace instances with strign identifiers
77            list($item, $publicDirectory, $privateDirectory) = $report->getData();
78            $report->setData([$item->getUri(), $publicDirectory->getId(), $privateDirectory->getId()]);
79        }
80        return $report;
81    }
82
83    /**
84     * Deploy all the required files into the provided directories
85     *
86     * @param core_kernel_classes_Resource $item
87     * @param string $language
88     * @param tao_models_classes_service_StorageDirectory $publicDirectory
89     * @param tao_models_classes_service_StorageDirectory $privateDirectory
90     * @return common_report_Report
91     */
92    protected function deployQtiItem(
93        core_kernel_classes_Resource $item,
94        $language,
95        tao_models_classes_service_StorageDirectory $publicDirectory,
96        tao_models_classes_service_StorageDirectory $privateDirectory
97    ) {
98        $qtiService = Service::singleton();
99
100
101        try {
102            $qtiItem = $this->createQtiItem($item, $language);
103            $qtiItem->validateOutcomes();
104            $resolver = new ItemMediaResolver($item, $language);
105            $publicLangDirectory = $publicDirectory->getDirectory($language);
106
107            // retrieve the media assets
108            $packedAssets = $this->parseAndReplaceAssetByPlaceholder($qtiItem, $resolver, $publicLangDirectory);
109
110            $this->compileItemIndex($item->getUri(), $qtiItem, $language);
111
112            //store variable qti elements data into the private directory
113            $variableElements = $qtiService->getVariableElements($qtiItem);
114            $privateDirectory->write(
115                $language . DIRECTORY_SEPARATOR . self::VAR_ELT_FILE_NAME,
116                json_encode($variableElements)
117            );
118
119            //create the item.json file in private directory
120            $itemPacker = new QtiItemPacker();
121            $itemPack = $itemPacker->packQtiItem($item, $language, $qtiItem, $publicDirectory);
122            $this->itemJson = $itemPack->JsonSerialize();
123            //get the filtered data to avoid cheat
124            $data = $qtiItem->getDataForDelivery();
125            $data = $this->convertXmlAttributes($data);
126            $this->itemJson['data'] = $data['core'];
127            $metadata = $this->getMetadataProperties();
128
129            $privateDirectory->write(
130                $language . DIRECTORY_SEPARATOR . self::ITEM_FILE_NAME,
131                json_encode($this->itemJson)
132            );
133            $privateDirectory->write(
134                $language . DIRECTORY_SEPARATOR . self::METADATA_FILE_NAME,
135                json_encode($metadata)
136            );
137            $privateDirectory->write(
138                $language . DIRECTORY_SEPARATOR . self::PORTABLE_ELEMENT_FILE_NAME,
139                json_encode($this->getItemPortableElements($qtiItem))
140            );
141
142            return new common_report_Report(
143                common_report_Report::TYPE_SUCCESS,
144                __('Successfully compiled "%s"', $language)
145            );
146        } catch (\tao_models_classes_FileNotFoundException $e) {
147            return new common_report_Report(
148                common_report_Report::TYPE_ERROR,
149                __('Unable to retrieve asset "%s"', $e->getFilePath())
150            );
151        } catch (XIncludeException $e) {
152            return new common_report_Report(
153                common_report_Report::TYPE_ERROR,
154                $e->getUserMessage()
155            );
156        } catch (Exception $e) {
157            return new common_report_Report(
158                common_report_Report::TYPE_ERROR,
159                $e->getMessage()
160            );
161        }
162    }
163
164    private function createQtiItem(core_kernel_classes_Resource $item, $lang): Item
165    {
166        $qtiItem = $this->getServiceLocator()->get(Service::class)->getDataItemByRdfItem($item, $lang);
167
168        if (is_null($qtiItem)) {
169            throw new taoItems_models_classes_CompilationFailedException(
170                __('Unable to retrieve item : "%s"', $item->getLabel())
171            );
172        }
173
174        return $qtiItem;
175    }
176
177    private function parseAndReplaceAssetByPlaceholder(
178        Item &$qtiItem,
179        ItemMediaResolver $resolver,
180        Directory $publicLangDirectory
181    ) {
182        $packedAssets = $this->getQtiItemAssetCompiler()->extractAndCopyAssetFiles(
183            $qtiItem,
184            $publicLangDirectory,
185            $resolver
186        );
187
188        $dom = new DOMDocument('1.0', 'UTF-8');
189
190        try {
191            if ($dom->loadXML($qtiItem->toXML()) === false) {
192                throw new \InvalidArgumentException();
193            }
194        } catch (Throwable $e) {
195            throw new taoItems_models_classes_CompilationFailedException(
196                sprintf('Unable to load XML for item %s', $qtiItem->getIdentifier())
197            );
198        }
199
200        $this->getXIncludeXmlInjector()->injectSharedStimulus($dom, $packedAssets);
201        $this->getItemAssetXmlReplacer()->replaceAssetNodeValue($dom, $packedAssets);
202
203        $qtiParser = new Parser($dom->saveXML());
204        $qtiItem = $qtiParser->load();
205
206        return $packedAssets;
207    }
208
209    /**
210     * Convert internal parameters to json if needed
211     * @param $data
212     * @return mixed
213     * @throws common_exception_Error
214     */
215    protected function convertXmlAttributes($data)
216    {
217        if (
218            is_array($data)
219            && array_key_exists('core', $data)
220            && is_array($data['core'])
221            && array_key_exists('apipAccessibility', $data['core'])
222            && $data['core']['apipAccessibility']
223        ) {
224            $data['core']['apipAccessibility'] = tao_helpers_Xml::to_array($data['core']['apipAccessibility']);
225        }
226
227        return $data;
228    }
229
230    /**
231     * Get the portable elements data in use in the item
232     * @param Element $qtiItem
233     * @return array
234     */
235    private function getItemPortableElements(Element $qtiItem)
236    {
237        $portableElementService = new PortableElementService();
238        $portableElementService->setServiceLocator($this->getServiceLocator());
239        return [
240            'pci' => $portableElementService->getPortableElementByClass(
241                PortableElementService::PORTABLE_CLASS_INTERACTION,
242                $qtiItem,
243                true
244            ),
245            'pic' => $portableElementService->getPortableElementByClass(
246                PortableElementService::PORTABLE_CLASS_INFOCONTROL,
247                $qtiItem,
248                true
249            )
250        ];
251    }
252
253    /**
254     * Get the item properties as compiled metadata
255     * @return array
256     */
257    private function getMetadataProperties()
258    {
259        $triples = $this->getResource()->getRdfTriples();
260        $properties = [];
261        foreach ($triples as $triple) {
262            $properties[$triple->predicate] = $triple->object;
263        }
264        //we also include a shortcut to the item URI
265        $properties['@uri'] = $this->getResource()->getUri();
266        return $properties;
267    }
268
269    private function getQtiItemAssetCompiler(): QtiItemAssetCompiler
270    {
271        return $this->getServiceLocator()->get(QtiItemAssetCompiler::class);
272    }
273
274    private function getXIncludeXmlInjector(): XIncludeXmlInjector
275    {
276        return $this->getServiceLocator()->get(XIncludeXmlInjector::class);
277    }
278
279    private function getItemAssetXmlReplacer(): QtiItemAssetXmlReplacer
280    {
281        return $this->getServiceLocator()->get(QtiItemAssetXmlReplacer::class);
282    }
283}