Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
35.11% covered (danger)
35.11%
33 / 94
23.53% covered (danger)
23.53%
4 / 17
CRAP
0.00% covered (danger)
0.00%
0 / 1
Service
35.11% covered (danger)
35.11%
33 / 94
23.53% covered (danger)
23.53%
4 / 17
477.25
0.00% covered (danger)
0.00%
0 / 1
 getDataItemByRdfItem
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
90
 getXmlByRdfItem
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 saveDataItemToRdfItem
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
20
 saveXmlItemToRdfItem
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 catchItemCreatedEvent
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 loadItemFromFile
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
42
 renderQTIItem
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 getVariableElements
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 getMetadataRegistry
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 hasItemModel
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 deleteContentByRdfItem
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 backupContentByRdfItem
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
3
 restoreContentByRdfItem
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
3
 singleton
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getItemService
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getNewSerializedItemContentDirectory
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
1
 getXmlToItemParser
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) 2013-2014 (original work) Open Assessment Technologies SA (under the project TAO-PRODUCT);
19 *
20 */
21
22namespace oat\taoQtiItem\model\qti;
23
24use common_exception_Error;
25use common_exception_NotFound;
26use oat\oatbox\filesystem\File;
27use oat\oatbox\filesystem\FilesystemException;
28use oat\taoQtiItem\model\qti\parser\XmlToItemParser;
29use tao_helpers_Uri;
30use common_exception_FileSystemError;
31use oat\generis\model\fileReference\FileReferenceSerializer;
32use oat\generis\model\OntologyAwareTrait;
33use oat\oatbox\event\EventManagerAwareTrait;
34use oat\oatbox\service\ConfigurableService;
35use oat\oatbox\service\ServiceManager;
36use oat\taoItems\model\event\ItemCreatedEvent;
37use oat\taoItems\model\event\ItemUpdatedEvent;
38use oat\taoQtiItem\helpers\Authoring;
39use oat\taoQtiItem\model\ItemModel;
40use oat\taoQtiItem\model\qti\exception\XIncludeException;
41use oat\taoQtiItem\model\qti\metadata\MetadataRegistry;
42use oat\taoQtiItem\model\qti\exception\ParsingException;
43use core_kernel_classes_Resource;
44use taoItems_models_classes_ItemsService;
45use common_Logger;
46use common_Exception;
47use Exception;
48use oat\taoItems\model\media\ItemMediaResolver;
49
50/**
51 * The QTI_Service gives you a central access to the managment methods of the
52 * objects
53 *
54 * @author Somsack Sipasseuth <sam@taotesting.com>
55 * @author Jérôme Bogaerts <jerome@taotesting.com>
56 */
57class Service extends ConfigurableService
58{
59    use EventManagerAwareTrait;
60    use OntologyAwareTrait;
61
62    public const QTI_ITEM_FILE = 'qti.xml';
63
64    /**
65     * Load a QTI_Item from an, RDF Item using the itemContent property of the
66     * Item as the QTI xml
67     *
68     * @access public
69     * @author Somsack Sipasseuth, <somsack.sipasseuth@tudor.lu>
70     * @param  Resource item
71     * @throws \common_Exception If $item is not representing an item with a QTI item model.
72     * @return Item An item as a business object.
73     */
74    public function getDataItemByRdfItem(core_kernel_classes_Resource $item, $langCode = '', $resolveXInclude = false)
75    {
76        $returnValue = null;
77
78        try {
79            //Parse it and build the QTI_Data_Item
80            $file = $this->getXmlByRdfItem($item, $langCode);
81            $qtiParser = new Parser($file);
82            $returnValue = $qtiParser->load();
83
84            if (is_null($returnValue) && !empty($qtiParser->getErrors())) {
85                common_Logger::w($qtiParser->displayErrors(false));
86            }
87
88            if ($resolveXInclude && !empty($langCode)) {
89                try {
90                    //loadxinclude
91                    $resolver = new ItemMediaResolver($item, $langCode);
92                    $xincludeLoader = new XIncludeLoader($returnValue, $resolver);
93                    $xincludeLoader->load(true);
94                } catch (XIncludeException $exception) {
95                    common_Logger::e($exception->getMessage());
96                }
97            }
98
99            if (!$returnValue->getAttributeValue('xml:lang')) {
100                $returnValue->setAttribute('xml:lang', \common_session_SessionManager::getSession()->getDataLanguage());
101            }
102        } catch (FilesystemException $e) {
103            // fail silently, since file might not have been created yet
104            // $returnValue is then NULL.
105            common_Logger::d('item(' . $item->getUri() . ') is empty, newly created?');
106        } catch (common_Exception $e) {
107            common_Logger::d('item(' . $item->getUri() . ') is not existing');
108        }
109
110        return $returnValue;
111    }
112
113    /**
114     * Load the XML of the QTI item
115     *
116     * @param core_kernel_classes_Resource $item
117     * @param string $language
118     * @return false|string
119     * @throws common_Exception
120     */
121    public function getXmlByRdfItem(core_kernel_classes_Resource $item, $language = '')
122    {
123        //check if the item is QTI item
124        if (! $this->getItemService()->hasItemModel($item, [ItemModel::MODEL_URI])) {
125            throw new common_Exception('Non QTI item(' . $item->getUri() . ') opened via QTI Service');
126        }
127
128        $file = $this->getItemService()->getItemDirectory($item, $language)->getFile(self::QTI_ITEM_FILE);
129        return $file->read();
130    }
131
132    /**
133     * Save a QTI_Item into an RDF Item, by exporting the QTI_Item to QTI xml
134     * and saving it in the itemContent property of the RDF Item
135     *
136     * @param \oat\taoQtiItem\model\qti\Item $qtiItem
137     * @param core_kernel_classes_Resource $rdfItem
138     * @return bool
139     * @throws common_exception_Error
140     * @throws common_exception_NotFound
141     * @throws common_Exception
142     * @throws exception\QtiModelException
143     */
144    public function saveDataItemToRdfItem(Item $qtiItem, core_kernel_classes_Resource $rdfItem)
145    {
146        $label = mb_substr($rdfItem->getLabel(), 0, 256, 'UTF-8');
147        //set the current data lang in the item content to keep the integrity
148        if ($qtiItem->hasAttribute('xml:lang') && !empty($qtiItem->getAttributeValue('xml:lang'))) {
149            $lang = $qtiItem->getAttributeValue('xml:lang');
150        } else {
151            $lang = \common_session_SessionManager::getSession()->getDataLanguage();
152        }
153        $qtiItem->setAttribute('xml:lang', $lang);
154        $qtiItem->setAttribute('label', $label);
155
156        $directory = taoItems_models_classes_ItemsService::singleton()->getItemDirectory($rdfItem);
157        $success = $directory->getFile(self::QTI_ITEM_FILE)->put($qtiItem->toXML());
158
159        if ($success) {
160            $this->getEventManager()->trigger(new ItemUpdatedEvent($rdfItem->getUri()));
161        }
162
163        return $success;
164    }
165
166    /**
167     * @param string|File $xml
168     * @param core_kernel_classes_Resource $rdfItem
169     *
170     * @return bool
171     *
172     * @throws common_exception_Error
173     * @throws common_exception_NotFound
174     * @throws common_Exception
175     */
176    public function saveXmlItemToRdfItem($xml, core_kernel_classes_Resource $rdfItem)
177    {
178        $sanitized = Authoring::sanitizeQtiXml($xml);
179
180        $qtiItem = $this->getXmlToItemParser()->parse($sanitized);
181
182        return $this->saveDataItemToRdfItem($qtiItem, $rdfItem);
183    }
184
185    /**
186     * @param ItemCreatedEvent $event
187     */
188    public function catchItemCreatedEvent(ItemCreatedEvent $event)
189    {
190        if ($event->getItemContent() !== null) {
191            $this->saveXmlItemToRdfItem($event->getItemContent(), $this->getResource($event->getItemUri()));
192        }
193    }
194
195    /**
196     * Load a QTI item from a qti file in parameter.
197     *
198     * @param $file
199     * @return null|Item
200     * @throws Exception
201     * @throws ParsingException
202     */
203    public function loadItemFromFile($file)
204    {
205        $returnValue = null;
206
207        if (is_string($file) && !empty($file)) {
208            //validate the file to import
209            try {
210                $qtiParser = new Parser($file);
211                $qtiParser->validate();
212
213                if (!$qtiParser->isValid()) {
214                    throw new ParsingException($qtiParser->displayErrors());
215                }
216
217                $returnValue = $qtiParser->load();
218            } catch (ParsingException $pe) {
219                throw new ParsingException($pe->getMessage());
220            } catch (Exception $e) {
221                throw new Exception("Unable to load file {$file} caused  by {$e->getMessage()}");
222            }
223        }
224
225        return $returnValue;
226    }
227
228    /**
229     * Build the XHTML/CSS/JS from a QTI_Item to be rendered.
230     *
231     * @param Item $item
232     * @param string $language
233     * @return string
234     */
235    public function renderQTIItem(Item $item, $language = 'en-US')
236    {
237        if (! is_null($item)) {
238            return $item->toXHTML(['lang' => $language]);
239        }
240        return '';
241    }
242
243    public function getVariableElements(Item $item)
244    {
245        $allData = $item->getDataForDelivery();
246        return $allData['variable'];
247    }
248
249    /**
250     * Obtain a reference on the Metadata Injector/Extractor Registry.
251     *
252     * @return \oat\taoQtiItem\model\qti\metadata\MetadataRegistry
253     */
254    public function getMetadataRegistry()
255    {
256        return new MetadataRegistry();
257    }
258
259    public function hasItemModel(core_kernel_classes_Resource $item, $models)
260    {
261        return taoItems_models_classes_ItemsService::singleton()->hasItemModel($item, $models);
262    }
263
264    /**
265     * Delete the contents of the item, but not the resource representing it.
266     *
267     * @param core_kernel_classes_Resource $item
268     * @return bool
269     */
270    public function deleteContentByRdfItem(core_kernel_classes_Resource $item)
271    {
272        return taoItems_models_classes_ItemsService::singleton()->deleteItemContent($item);
273    }
274
275    /**
276     * @param core_kernel_classes_Resource $item
277     * @return array
278     * @throws common_exception_FileSystemError
279     */
280    public function backupContentByRdfItem(core_kernel_classes_Resource $item): array
281    {
282        try {
283            $itemContentProperty = $this->getItemService()->getItemContentProperty();
284
285            $oldItemContentPropertyValues = [];
286            $newItemContentDirectoryName = tao_helpers_Uri::getUniqueId($item->getUri()) . '.' . uniqid();
287            $propertyLanguages = $item->getUsedLanguages($itemContentProperty);
288            foreach ($propertyLanguages as $language) {
289                $oldItemContentPropertyValues[$language] = (string) $item
290                    ->getPropertyValuesByLg($itemContentProperty, $language)
291                    ->get(0);
292                $serial = $this->getNewSerializedItemContentDirectory($newItemContentDirectoryName, $language);
293
294                $item->editPropertyValueByLg($itemContentProperty, $serial, $language);
295            }
296
297            return $oldItemContentPropertyValues;
298        } catch (Exception $e) {
299            $this->logError('Item content backup failed: ' . $e->getMessage());
300            throw new common_Exception("QTI Item backup failed. Item uri - " . $item->getUri());
301        }
302    }
303
304    /**
305     * @param core_kernel_classes_Resource $item
306     * @param array $backUpNames
307     * @throws common_exception_FileSystemError
308     */
309    public function restoreContentByRdfItem(core_kernel_classes_Resource $item, array $backUpNames): void
310    {
311        try {
312            $itemContentProperty = $this->getItemService()->getItemContentProperty();
313            foreach ($backUpNames as $language => $itemContentPropertyValue) {
314                $item->editPropertyValueByLg($itemContentProperty, $itemContentPropertyValue, $language);
315            }
316        } catch (Exception $e) {
317            $this->logError('Rollback item error: ' . $e->getMessage());
318            throw new common_Exception(
319                sprintf(
320                    'Cannot rollback item. Item uri - %s :: Backup folders - %s ',
321                    $item->getUri(),
322                    json_encode($backUpNames)
323                )
324            );
325        }
326    }
327
328    /**
329     * @deprecated use ServiceManager::get(\oat\taoQtiItem\model\qti\Service::class)
330     * @return self
331     */
332    public static function singleton()
333    {
334        return ServiceManager::getServiceManager()->get(self::class);
335    }
336
337    private function getItemService(): taoItems_models_classes_ItemsService
338    {
339        return $this->getServiceLocator()->get(taoItems_models_classes_ItemsService::class);
340    }
341
342    /**
343     * @param string $newItemContentDirectoryName
344     * @param $language
345     * @return mixed
346     * @throws \common_ext_ExtensionException
347     */
348    private function getNewSerializedItemContentDirectory(string $newItemContentDirectoryName, $language): string
349    {
350        $newItemContentDirectoryPath = $this->getItemService()->composeItemDirectoryPath(
351            $newItemContentDirectoryName,
352            $language
353        );
354        $newDirectory = $this->getItemService()->getDefaultItemDirectory()->getDirectory($newItemContentDirectoryPath);
355
356        return $this->getServiceLocator()->get(FileReferenceSerializer::SERVICE_ID)->serialize($newDirectory);
357    }
358
359    private function getXmlToItemParser(): XmlToItemParser
360    {
361        return $this->getServiceLocator()->get(XmlToItemParser::class);
362    }
363}