Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 126
0.00% covered (danger)
0.00%
0 / 14
CRAP
0.00% covered (danger)
0.00%
0 / 1
PciManager
0.00% covered (danger)
0.00%
0 / 126
0.00% covered (danger)
0.00%
0 / 14
1640
0.00% covered (danger)
0.00%
0 / 1
 __call
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
 index
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getService
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 getRegisteredImplementations
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
12
 getPciModels
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
 verify
0.00% covered (danger)
0.00%
0 / 31
0.00% covered (danger)
0.00%
0 / 1
30
 createExistingTypePciObjectFromUploadedPackage
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
12
 add
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
42
 export
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
12
 getMinifiedModel
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
2
 getRequestPciDataObject
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
42
 unregister
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
6
 enable
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
2
 disable
0.00% covered (danger)
0.00%
0 / 7
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) 2014-2022 (original work) Open Assessment Technologies SA;
19 *
20 */
21
22namespace oat\qtiItemPci\controller;
23
24use common_Exception;
25use common_exception_Error;
26use oat\qtiItemPci\model\portableElement\dataObject\PciDataObject;
27use oat\tao\model\http\formatter\ResponseFormatter;
28use oat\taoQtiItem\model\portableElement\exception\PortableElementException;
29use oat\taoQtiItem\model\portableElement\exception\PortableElementInvalidModelException;
30use oat\taoQtiItem\model\portableElement\exception\PortableElementNotFoundException;
31use oat\taoQtiItem\model\portableElement\exception\PortableElementParserException;
32use oat\taoQtiItem\model\portableElement\element\PortableElementObject;
33use oat\taoQtiItem\model\portableElement\exception\PortableElementVersionIncompatibilityException;
34use oat\taoQtiItem\model\portableElement\model\PortableModelRegistry;
35use oat\taoQtiItem\model\portableElement\storage\PortableElementRegistry;
36use oat\taoQtiItem\model\portableElement\PortableElementService;
37use tao_helpers_Http;
38use oat\taoQtiItem\model\qti\interaction\CustomInteraction;
39
40/**
41 * Actions for pci portable custom elements management
42 * Class PciManager
43 * @author Bartlomiej Marszal
44 */
45class PciManager extends \tao_actions_CommonModule
46{
47    /**
48     * @var PortableElementRegistry
49     */
50    protected $registry;
51
52    /**
53     * @var PortableElementService
54     */
55    protected $service;
56
57    /**
58     * @param $method
59     * @param $arguments
60     * @security("hide");
61     */
62    public function __call($method, $arguments)
63    {
64        try {
65            $this->$method($arguments);
66        } catch (PortableElementNotFoundException $e) {
67            $this->returnJson($e->getMessage(), 404);
68        } catch (PortableElementException $e) {
69            $this->returnJson($e->getMessage(), 500);
70        }
71    }
72
73    public function index()
74    {
75        $this->setView('pci-manager/index.tpl');
76    }
77
78    protected function getService()
79    {
80        if (!$this->service) {
81            $this->service = new PortableElementService();
82            $this->service->setServiceLocator($this->getServiceManager());
83        }
84        return $this->service;
85    }
86
87    /**
88     * Returns the list of registered custom interactions and their data
89     */
90    public function getRegisteredImplementations()
91    {
92        $returnValue = [];
93
94        $pciModels = $this->getPciModels();
95        foreach ($pciModels as $pciModel) {
96            $all = $pciModel->getRegistry()->getLatest();
97            foreach ($all as $portableElement) {
98                $returnValue[$portableElement->getTypeIdentifier()] = $this->getMinifiedModel($portableElement);
99            }
100        }
101        uasort($returnValue, function ($a, $b) {
102            return $a['runtimeOnly'] > $b['runtimeOnly'];
103        });
104        $this->returnJson($returnValue);
105    }
106
107    /**
108     * Return the list of registered PCI php subclasses
109     * @return array
110     */
111    protected function getPciModels()
112    {
113        $pciModels = [];
114        foreach (PortableModelRegistry::getRegistry()->getModels() as $model) {
115            if (is_subclass_of($model->getQtiElementClassName(), CustomInteraction::class)) {
116                $pciModels[] = $model;
117            }
118        }
119        return $pciModels;
120    }
121
122    /**
123     * Service to check if the uploaded file archive is a valid and non-existing one
124     *
125     * JSON structure:
126     * {
127     *     "valid" : true/false (if is a valid package)
128     *     "exists" : true/false (if the package is valid, check if the typeIdentifier is already used in the registry)
129     * }
130     *
131     * @throws common_Exception
132     */
133    public function verify(ResponseFormatter $responseFormatter): void
134    {
135        $result = [
136            'valid' => false,
137            'exists' => false
138        ];
139
140        $formatter = $responseFormatter->withJsonHeader();
141
142        try {
143            $pciObject = $this->createExistingTypePciObjectFromUploadedPackage();
144            $result['valid'] = true;
145        } catch (PortableElementException $e) {
146            $result['messages'] = $e->getReportMessages() ?: [['message' => $e->getMessage()]];
147            $this->setResponse($formatter->withBody($result)->format($this->getPsrResponse()));
148
149            return;
150        }
151
152        $all = $pciObject->getModel()->getRegistry()->getLatestCreators();
153        if (isset($all[$pciObject->getTypeIdentifier()])) {
154            $result['exists'] = true;
155            $currentVersion = $all[$pciObject->getTypeIdentifier()]->getVersion();
156            if (version_compare($pciObject->getVersion(), $currentVersion, '<')) {
157                $result['valid'] = false;
158                $result['messages'][] = [
159                    'message' =>
160                    __(
161                        'A newer version of the pci "%s" already exists (current version: %s, target version: %s)',
162                        $pciObject->getTypeIdentifier(),
163                        $currentVersion,
164                        $pciObject->getVersion()
165                    )
166                ];
167                $this->setResponse($formatter->withBody($result)->format($this->getPsrResponse()));
168
169                return;
170            }
171        }
172
173        $this->setResponse($formatter->withBody(
174            array_merge($result, $this->getMinifiedModel($pciObject))
175        )->format($this->getPsrResponse()));
176    }
177
178    /**
179     * @throws PortableElementException
180     * @throws common_Exception
181     */
182    private function createExistingTypePciObjectFromUploadedPackage(): PortableElementObject
183    {
184        $file = tao_helpers_Http::getUploadedFile('content');
185
186        $errorMessage = 'Unable to find a valid PCI manifest';
187
188        foreach ($this->getPciModels() as $pciModel) {
189            try {
190                return $this->getService()->getValidPortableElementFromZipSource($pciModel->getId(), $file['tmp_name']);
191            } catch (PortableElementParserException $e) {
192                $errorMessage = $e->getMessage();
193            }
194        }
195
196        throw new PortableElementInvalidModelException($errorMessage);
197    }
198
199    /**
200     * Add a new custom interaction from the uploaded zip package
201     */
202    public function add()
203    {
204        //as upload may be called multiple times, we remove the session lock as soon as possible
205        session_write_close();
206
207        try {
208            $file = tao_helpers_Http::getUploadedFile('content');
209        } catch (common_exception_Error $e) {
210            throw new PortableElementParserException('Unable to handle uploaded package.', 0, $e);
211        }
212
213        $pciModels = $this->getPciModels();
214        foreach ($pciModels as $pciModel) {
215            try {
216                $portableElement = $this->getService()->import($pciModel->getId(), $file['tmp_name']);
217                $this->returnJson($this->getMinifiedModel($portableElement));
218                if (!is_null($portableElement)) {
219                    break;//stop at the first one
220                }
221            } catch (PortableElementInvalidModelException $e) {
222            } catch (PortableElementParserException $e) {
223            }
224        }
225    }
226
227    /**
228     * Export PCI zip package with all runtime, creator & manifest files
229     */
230    public function export()
231    {
232        //as upload may be called multiple times, we remove the session lock as soon as possible
233        session_write_close();
234        $requestPayload = $this->getPsrRequest()->getQueryParams();
235        try {
236            if (!isset($requestPayload['typeIdentifier'], $requestPayload['pciIdentifier'])) {
237                throw new PortableElementException('PCI parameter missing.');
238            }
239            $path = $this->getService()->export($requestPayload['pciIdentifier'], $requestPayload['typeIdentifier']);
240            tao_helpers_Http::returnFile($path);
241        } catch (common_Exception $e) {
242            $this->returnJson(['error' => $e->getMessage()]);
243        }
244    }
245
246    protected function getMinifiedModel(PortableElementObject $object)
247    {
248        $data = $object->toArray(['typeIdentifier', 'label']);
249        $data['runtimeOnly'] = empty($object->getCreator());
250        $data['version'] = $object->getVersion();
251        $data['enabled'] = $object->isEnabled();
252        $data['model'] = $object->getModelLabel();
253        $data['pci_identifier'] = $object->getModelId();
254        return $data;
255    }
256
257    /**
258     * @return PciDataObject
259     * @throws PortableElementException
260     */
261    protected function getRequestPciDataObject()
262    {
263        if (!$this->hasRequestParameter('typeIdentifier')) {
264            throw new PortableElementException('Type identifier parameter missing.');
265        }
266        $typeIdentifier = $this->getRequestParameter('typeIdentifier');
267
268        $pciModels = $this->getPciModels();
269        $pciDataObjects = [];
270        foreach ($pciModels as $pciModel) {
271            try {
272                $pciDataObject = $pciModel->getRegistry()->getLatestVersion($typeIdentifier);
273                if (!is_null($pciDataObject)) {
274                    $pciDataObjects[$typeIdentifier] = $pciDataObject;
275                }
276            } catch (PortableElementNotFoundException $e) {
277                continue;
278            }
279        }
280
281        if (!empty($pciDataObjects)) {
282            return $pciDataObjects[$typeIdentifier];
283        }
284
285        throw new PortableElementException('Element not found');
286    }
287
288
289    /**
290     * @throws PortableElementException
291     * @throws PortableElementVersionIncompatibilityException
292     * @throws common_Exception
293     * @throws common_exception_Error
294     * @throws \HttpRequestException
295     */
296    public function unregister()
297    {
298        try {
299            $pci = $this->getRequestPciDataObject();
300            $registry = $pci->getModel()->getRegistry();
301            $registry->removeAllVersions($pci->getTypeIdentifier());
302        } catch (\Exception $e) {
303            throw new PortableElementException('Could not unregister element');
304        }
305
306        $this->returnJson([
307            'success' => true
308        ]);
309    }
310
311    /**
312     * Enable pci object
313     * @throws PortableElementException
314     */
315    public function enable()
316    {
317        $pci = $this->getRequestPciDataObject();
318        $pci->enable();
319        $pci->getModel()->getRegistry()->update($pci);
320        $this->returnJson([
321            'success' => true,
322            'interactionHook' => $this->getMinifiedModel($pci)
323        ]);
324    }
325
326    /**
327     * disable pci data object
328     * @throws PortableElementException
329     */
330    public function disable()
331    {
332        $pci = $this->getRequestPciDataObject();
333        $pci->disable();
334        $pci->getModel()->getRegistry()->update($pci);
335        $this->returnJson([
336            'success' => true,
337            'interactionHook' => $this->getMinifiedModel($pci)
338        ]);
339    }
340}