Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 105
0.00% covered (danger)
0.00%
0 / 17
CRAP
0.00% covered (danger)
0.00%
0 / 1
PortableElementService
0.00% covered (danger)
0.00%
0 / 105
0.00% covered (danger)
0.00%
0 / 17
1122
0.00% covered (danger)
0.00%
0 / 1
 getPortableModelRegistry
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 validate
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 registerModel
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 unregisterModel
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 export
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
6
 import
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 getValidPortableElementFromZipSource
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 getDirectoryParsers
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
12
 getValidPortableElementFromDirectorySource
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
30
 getPortableElementByIdentifier
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 getLatestCompatibleVersionElementById
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 registerFromDirectorySource
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
 retrieve
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 getFileStream
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getPortableObjectFromInstance
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
 getPortableElementByClass
0.00% covered (danger)
0.00%
0 / 21
0.00% covered (danger)
0.00%
0 / 1
30
 setBaseUrlToPortableData
0.00% covered (danger)
0.00%
0 / 4
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 (original work) Open Assessment Technologies SA;
19 *
20 */
21
22namespace oat\taoQtiItem\model\portableElement;
23
24use oat\taoQtiItem\model\portableElement\element\PortableElementObject;
25use oat\taoQtiItem\model\portableElement\exception\PortableElementInconsistencyModelException;
26use oat\taoQtiItem\model\portableElement\exception\PortableElementInvalidModelException;
27use oat\taoQtiItem\model\portableElement\exception\PortableElementNotFoundException;
28use oat\taoQtiItem\model\portableElement\exception\PortableElementParserException;
29use oat\taoQtiItem\model\portableElement\exception\PortableElementVersionIncompatibilityException;
30use oat\taoQtiItem\model\portableElement\model\PortableModelRegistry;
31use oat\taoQtiItem\model\portableElement\parser\element\PortableElementDirectoryParser;
32use oat\taoQtiItem\model\portableElement\parser\element\PortableElementPackageParser;
33use oat\taoQtiItem\model\portableElement\storage\PortableElementRegistry;
34use oat\taoQtiItem\model\portableElement\validator\Validator;
35use oat\taoQtiItem\model\qti\Element;
36use oat\taoQtiItem\model\qti\interaction\CustomInteraction;
37use oat\taoQtiItem\model\qti\InfoControl;
38use Zend\ServiceManager\ServiceLocatorAwareInterface;
39use Zend\ServiceManager\ServiceLocatorAwareTrait;
40
41class PortableElementService implements ServiceLocatorAwareInterface
42{
43    use ServiceLocatorAwareTrait;
44
45    public const PORTABLE_CLASS_INTERACTION = CustomInteraction::class;
46    public const PORTABLE_CLASS_INFOCONTROL = InfoControl::class;
47
48    protected function getPortableModelRegistry()
49    {
50        return PortableModelRegistry::getRegistry();
51    }
52
53    /**
54     * Validate a model using associated validator
55     *
56     * @param PortableElementObject $object
57     * @param null $source Directory of portable element, if not null it will be checked
58     * @param array $validationGroup Fields to be checked, empty=$validator->getConstraints()
59     * @return bool
60     * @throws PortableElementInconsistencyModelException
61     */
62    public function validate(PortableElementObject $object, $source = null, $validationGroup = [])
63    {
64        $validator = $object->getModel()->getValidator();
65        Validator::validate($object, $validator, $validationGroup);
66        if ($source) {
67            $validator->validateAssets($object, $source);
68        }
69    }
70
71    /**
72     * Register a $model with $source into registryEntries & filesystem
73     *
74     * @param PortableElementObject $object
75     * @param $source
76     * @return bool
77     * @throws PortableElementInvalidModelException
78     * @throws PortableElementVersionIncompatibilityException
79     */
80    public function registerModel(PortableElementObject $object, $source)
81    {
82        $validationGroup = ['typeIdentifier', 'version', 'runtime'];
83        $this->validate($object, $source, $validationGroup);
84
85        $registry = $object->getModel()->getRegistry();
86
87        //enable portable element immediately when registering it
88        $object->enable();
89
90        $registry->register($object, $source);
91
92        return true;
93    }
94
95    /**
96     * Unregister the portable element
97     *
98     * @param PortableElementObject $object
99     * @return bool
100     * @throws PortableElementVersionIncompatibilityException
101     */
102    public function unregisterModel(PortableElementObject $object)
103    {
104        $registry = $object->getModel()->getRegistry();
105        $registry->delete($object);
106        return true;
107    }
108
109    /**
110     * Export a model with files into a ZIP
111     *
112     * @param $type
113     * @param $identifier
114     * @param null $version
115     * @return string
116     * @throws PortableElementNotFoundException
117     * @throws \common_Exception
118     * @throws PortableElementInconsistencyModelException
119     */
120    public function export($type, $identifier, $version = null)
121    {
122        $model = $this->getPortableModelRegistry()->getModel($type);
123        $object = $model->getRegistry()->fetch($identifier, $version);
124
125        if (is_null($object)) {
126            throw new PortableElementNotFoundException(
127                'Unable to find a PCI associated to identifier: ' . $identifier
128            );
129        }
130        $this->validate($object);
131        return $model->getRegistry()->export($object);
132    }
133
134    /**
135     * Import a Portable element from an uploaded zip file
136     *
137     * @param $type
138     * @param $zipFile
139     * @return mixed
140     * @throws PortableElementInconsistencyModelException
141     */
142    public function import($type, $zipFile)
143    {
144        /** @var PortableElementPackageParser $parser */
145        $parser = $this->getPortableModelRegistry()->getModel($type)->getPackageParser();
146        $source = $parser->extract($zipFile);
147        $object = $parser->getModel()->createDataObject($parser->getManifestContent($zipFile));
148
149        $this->registerModel($object, $source);
150
151        \tao_helpers_File::delTree($source);
152
153        return $object;
154    }
155
156    /**
157     * Extract a valid model from a zip
158     *
159     * @param $type
160     * @param $zipFile
161     * @return mixed
162     * @throws PortableElementInconsistencyModelException
163     */
164    public function getValidPortableElementFromZipSource($type, $zipFile)
165    {
166        /** @var PortableElementPackageParser $parser */
167        $parser = $this->getPortableModelRegistry()->getModel($type)->getPackageParser();
168        $source = $parser->extract($zipFile);
169        $object = $parser->getModel()->createDataObject($parser->getManifestContent($zipFile));
170        $this->validate($object, $source);
171
172        return $object;
173    }
174
175    /**
176     * Return all directory parsers from configuration
177     *
178     * @return PortableElementDirectoryParser[]
179     */
180    protected function getDirectoryParsers()
181    {
182        $parsers = [];
183        $models = $this->getPortableModelRegistry()->getModels();
184        foreach ($models as $key => $model) {
185            if ($model->getDirectoryParser() instanceof PortableElementDirectoryParser) {
186                $parsers[] = $model->getDirectoryParser();
187            } else {
188                \common_Logger::w('Invalid DirectoryParser for model ' . $key);
189            }
190        }
191        return $parsers;
192    }
193
194    /**
195     * Extract a valid model from a directory
196     *
197     * @param $directory
198     * @return null|PortableElementObject
199     * @throws PortableElementParserException
200     * @throws \common_Exception
201     */
202    public function getValidPortableElementFromDirectorySource($directory)
203    {
204        $parserMatched = null;
205        $parsers = $this->getDirectoryParsers();
206        /** @var PortableElementDirectoryParser $parser */
207        foreach ($parsers as $parser) {
208            if ($parser->hasValidPortableElement($directory)) {
209                $parserMatched = $parser;
210            }
211        }
212
213        if (is_null($parserMatched)) {
214            throw new PortableElementParserException(
215                'This zip source is not compatible with any portable element. Manifest and/or engine file are missing '
216                . ' or related extensions are not installed.'
217            );
218        }
219
220        $source = $parserMatched->extract($directory);
221        $object = $parserMatched->getModel()->createDataObject($parserMatched->getManifestContent($directory));
222
223        // Validate Portable Element  Model
224        try {
225            $this->validate($object, $source);
226        } catch (PortableElementInvalidModelException $e) {
227            \common_Logger::w($e->getMessage());
228            return null;
229        }
230
231        return $object;
232    }
233
234    /**
235     * Get model from identifier & version
236     *
237     * @param $type
238     * @param $identifier
239     * @param null $version
240     * @return null|PortableElementObject
241     * @throws PortableElementNotFoundException
242     * @throws PortableElementInconsistencyModelException
243     */
244    public function getPortableElementByIdentifier($type, $identifier, $version = null)
245    {
246        $model = $this->getPortableModelRegistry()->getModel($type);
247        $registry = $model->getRegistry();
248
249        if ($registry->has($identifier, $version)) {
250            return $registry->fetch($identifier, $version);
251        }
252        return null;
253    }
254
255    public function getLatestCompatibleVersionElementById(
256        string $modeId,
257        string $identifier,
258        string $targetVersion
259    ): ?PortableElementObject {
260        $model = $this->getPortableModelRegistry()->getModel($modeId);
261        /* @var $registry PortableElementRegistry */
262        $registry = $model->getRegistry();
263
264        return $registry->getLatestCompatibleVersion($identifier, $targetVersion);
265    }
266
267    /**
268     * Register a model from a directory based on manifest.json
269     *
270     * @param $directory
271     * @return bool
272     * @throws \common_Exception
273     */
274    public function registerFromDirectorySource($directory)
275    {
276        $object = $this->getValidPortableElementFromDirectorySource($directory);
277        if (is_null($object)) {
278            throw new PortableElementNotFoundException(
279                'No valid portable element model found in the directory ' . $directory
280            );
281        }
282
283        return $this->registerModel($object, $directory);
284    }
285
286    /**
287     * Fill all values of a model based on $object->getTypeIdentifier, $object->getVersion
288     *
289     * @param $type
290     * @param $identifier
291     * @param null $version
292     * @return PortableElementObject
293     * @throws PortableElementNotFoundException
294     * @throws PortableElementInconsistencyModelException
295     */
296    public function retrieve($type, $identifier, $version = null)
297    {
298        $model = $this->getPortableModelRegistry()->getModel($type);
299        return $model->getRegistry()->fetch($identifier, $version);
300    }
301
302    /**
303     * Return the stream of a file model
304     *
305     * @param PortableElementObject $object
306     * @param $file
307     * @return bool|false|resource
308     * @throws \tao_models_classes_FileNotFoundException
309     */
310    public function getFileStream(PortableElementObject $object, $file)
311    {
312        return $object->getModel()->getRegistry()->getFileStream($object, $file);
313    }
314
315    /**
316     * @param Element $element
317     * @return PortableElementObject|null
318     */
319    public function getPortableObjectFromInstance(Element $element)
320    {
321        foreach ($this->getPortableModelRegistry()->getModels() as $model) {
322            $portableElementClass = $model->getQtiElementClassName();
323            if ($element instanceof $portableElementClass) {
324                return $this->retrieve($model->getId(), $element->getTypeIdentifier());
325            }
326        }
327        return null;
328    }
329
330    /**
331     * Get the array of portable elements used in qti item object by its php class
332     * @param string $portableElementClass - PORTABLE_CLASS_INTERACTION or PORTABLE_CLASS_INFOCONTROL
333     * @param Element $qtiItem
334     * @return array
335     */
336    public function getPortableElementByClass($portableElementClass, Element $qtiItem, $useVersionAlias = false)
337    {
338        $portableElements = [];
339
340        $identifiers = array_map(function ($portableElement) {
341            return $portableElement->getTypeIdentifier();
342        }, $qtiItem->getComposingElements($portableElementClass));
343
344        foreach ($this->getPortableModelRegistry()->getModels() as $model) {
345            $phpClass = $model->getQtiElementClassName();
346            if (is_subclass_of($phpClass, $portableElementClass)) {
347                $portableElements = array_merge(
348                    $portableElements,
349                    array_filter(
350                        $model->getRegistry()->getLatestRuntimes($useVersionAlias),
351                        function ($data) use ($identifiers) {
352                            $portableElement = reset($data);
353
354                            if (
355                                !empty($portableElement)
356                                && in_array($portableElement['typeIdentifier'], $identifiers)
357                            ) {
358                                return true;
359                            }
360
361                            return false;
362                        }
363                    )
364                );
365            }
366        }
367
368        /**
369         * @deprecated do not use the returned baseUrl
370         */
371        return $portableElements;
372    }
373
374    /**
375     * Set the base url to a portable element data array
376     * @param $data
377     * @return mixed
378     */
379    public function setBaseUrlToPortableData(&$data)
380    {
381        $model = $this->getPortableModelRegistry()->getModel($data['model']);
382        $portableObject = $model->createDataObject($data);
383        $data['baseUrl'] = $model->getRegistry()->getBaseUrl($portableObject);
384        return $data;
385    }
386}