Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 133 |
|
0.00% |
0 / 11 |
CRAP | |
0.00% |
0 / 1 |
RestQtiItem | |
0.00% |
0 / 133 |
|
0.00% |
0 / 11 |
1190 | |
0.00% |
0 / 1 |
getAcceptableMimeTypes | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
2 | |||
getDestinationClass | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
index | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
import | |
0.00% |
0 / 41 |
|
0.00% |
0 / 1 |
72 | |||
getTaskName | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
importDeferred | |
0.00% |
0 / 21 |
|
0.00% |
0 / 1 |
20 | |||
addExtraReturnData | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
20 | |||
getUploadedPackage | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
12 | |||
createQtiItem | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
12 | |||
export | |
0.00% |
0 / 20 |
|
0.00% |
0 / 1 |
30 | |||
createClass | |
0.00% |
0 / 14 |
|
0.00% |
0 / 1 |
12 |
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) 2017-2025 (original work) Open Assessment Technologies SA; |
19 | * |
20 | */ |
21 | |
22 | namespace oat\taoQtiItem\controller; |
23 | |
24 | use oat\tao\model\TaoOntology; |
25 | use oat\tao\model\taskQueue\TaskLog\Entity\EntityInterface; |
26 | use oat\tao\model\taskQueue\TaskLogInterface; |
27 | use Request; |
28 | use oat\taoQtiItem\model\qti\ImportService; |
29 | use oat\taoQtiItem\model\ItemModel; |
30 | use oat\generis\model\OntologyAwareTrait; |
31 | use oat\oatbox\reporting\ReportInterface; |
32 | use oat\taoQtiItem\model\qti\exception\ExtractException; |
33 | use oat\taoQtiItem\model\qti\exception\ParsingException; |
34 | use oat\taoQtiItem\model\Export\QTIPackedItemExporter; |
35 | use oat\taoQtiItem\model\tasks\ImportQtiItem; |
36 | use helpers_TimeOutHelper; |
37 | use tao_helpers_Http; |
38 | use tao_helpers_File; |
39 | use ZipArchive; |
40 | use tao_helpers_Export; |
41 | use common_report_Report; |
42 | use core_kernel_classes_Class; |
43 | use common_exception_NotImplemented; |
44 | use taoItems_models_classes_ItemsService; |
45 | use common_Exception; |
46 | use common_exception_BadRequest; |
47 | use common_exception_ClassAlreadyExists; |
48 | use common_exception_NotFound; |
49 | use Exception; |
50 | use common_exception_MissingParameter; |
51 | |
52 | /** |
53 | * End point of Rest item API |
54 | * |
55 | * @author Absar Gilani, <absar.gilani6@gmail.com> |
56 | * @author Gyula Szucs <gyula@taotesting.com> |
57 | */ |
58 | class RestQtiItem extends AbstractRestQti |
59 | { |
60 | use OntologyAwareTrait; |
61 | |
62 | public const RESTITEM_PACKAGE_NAME = 'content'; |
63 | |
64 | /** |
65 | * @inherit |
66 | */ |
67 | protected function getAcceptableMimeTypes(): array |
68 | { |
69 | return |
70 | [ |
71 | "application/json", |
72 | "text/xml", |
73 | "application/xml", |
74 | "application/rdf+xml" , |
75 | "application/zip", |
76 | ]; |
77 | } |
78 | |
79 | /** |
80 | * Class items will be created in |
81 | */ |
82 | protected function getDestinationClass(): core_kernel_classes_Class |
83 | { |
84 | return $this->getClassFromRequest($this->getClass(TaoOntology::CLASS_URI_ITEM)); |
85 | } |
86 | |
87 | /** |
88 | * Only import method is available, so index return failure response |
89 | */ |
90 | public function index(): void |
91 | { |
92 | $this->returnFailure(new common_exception_NotImplemented('This API does not support this call.')); |
93 | } |
94 | |
95 | /** |
96 | * Import file entry point by using $this->service |
97 | * Check POST method & get valid uploaded file |
98 | */ |
99 | public function import(): void |
100 | { |
101 | try { |
102 | if ($this->getRequestMethod() != Request::HTTP_POST) { |
103 | throw new common_exception_NotImplemented('Only post method is accepted to import Qti package.'); |
104 | } |
105 | |
106 | // Get valid package parameter |
107 | $package = $this->getUploadedPackage(); |
108 | |
109 | // Call service to import package |
110 | helpers_TimeOutHelper::setTimeOutLimit(helpers_TimeOutHelper::LONG); |
111 | $report = ImportService::singleton()->importQTIPACKFile( |
112 | $package, |
113 | $this->getDestinationClass(), |
114 | true, |
115 | true, |
116 | true, |
117 | $this->isMetadataGuardiansEnabled(), |
118 | $this->isMetadataValidatorsEnabled(), |
119 | $this->isItemMustExistEnabled(), |
120 | $this->isItemMustBeOverwrittenEnabled(), |
121 | $this->isMetadataRequired() |
122 | ); |
123 | helpers_TimeOutHelper::reset(); |
124 | |
125 | tao_helpers_File::remove($package); |
126 | if ($report->getType() !== ReportInterface::TYPE_SUCCESS) { |
127 | $message = __("An unexpected error occurred during the import of the IMS QTI Item Package. "); |
128 | //get message of first error report |
129 | if (!empty($report->getErrors())) { |
130 | $message .= $report->getErrors()[0]->getMessage(); |
131 | } |
132 | $this->returnFailure(new common_Exception($message)); |
133 | } else { |
134 | $itemIds = []; |
135 | /** @var common_report_Report $subReport */ |
136 | foreach ($report as $subReport) { |
137 | $itemIds[] = $subReport->getData()->getUri(); |
138 | } |
139 | $this->setSuccessJsonResponse(['items' => $itemIds]); |
140 | } |
141 | } catch (ExtractException $e) { |
142 | $this->returnFailure( |
143 | new common_Exception( |
144 | __('The ZIP archive containing the IMS QTI Item cannot be extracted.') |
145 | ) |
146 | ); |
147 | } catch (ParsingException $e) { |
148 | $this->returnFailure( |
149 | new common_Exception( |
150 | __('The ZIP archive does not contain an imsmanifest.xml file or is an invalid ZIP archive.') |
151 | ) |
152 | ); |
153 | } catch (Exception $e) { |
154 | $this->returnFailure($e); |
155 | } |
156 | } |
157 | |
158 | /** |
159 | * @inheritdoc |
160 | */ |
161 | protected function getTaskName(): string |
162 | { |
163 | return ImportQtiItem::class; |
164 | } |
165 | |
166 | /** |
167 | * Import item package through the task queue. |
168 | */ |
169 | public function importDeferred(): void |
170 | { |
171 | try { |
172 | if ($this->getRequestMethod() != Request::HTTP_POST) { |
173 | throw new common_exception_NotImplemented('Only post method is accepted to import Qti package.'); |
174 | } |
175 | |
176 | $task = ImportQtiItem::createTask( |
177 | $this->getUploadedPackage(), |
178 | $this->getDestinationClass(), |
179 | $this->getServiceLocator(), |
180 | $this->isMetadataGuardiansEnabled(), |
181 | $this->isMetadataValidatorsEnabled(), |
182 | $this->isItemMustExistEnabled(), |
183 | $this->isItemMustBeOverwrittenEnabled(), |
184 | $this->isMetadataRequired() |
185 | ); |
186 | |
187 | $result = [ |
188 | 'reference_id' => $task->getId() |
189 | ]; |
190 | |
191 | /** @var TaskLogInterface $taskLog */ |
192 | $taskLog = $this->getServiceManager()->get(TaskLogInterface::SERVICE_ID); |
193 | |
194 | if ($report = $taskLog->getReport($task->getId())) { |
195 | $result['report'] = $report->toArray(); |
196 | } |
197 | |
198 | $this->setSuccessJsonResponse($result); |
199 | } catch (Exception $e) { |
200 | $this->returnFailure($e); |
201 | } |
202 | } |
203 | |
204 | /** |
205 | * Add extra values to the JSON returned. |
206 | * |
207 | * @param EntityInterface $taskLogEntity |
208 | */ |
209 | protected function addExtraReturnData(EntityInterface $taskLogEntity): array |
210 | { |
211 | $data = []; |
212 | |
213 | if ($taskLogEntity->getReport()) { |
214 | $plainReport = $this->getPlainReport($taskLogEntity->getReport()); |
215 | |
216 | //the third report is report of import test |
217 | $itemsReport = array_slice($plainReport, 2); |
218 | foreach ($itemsReport as $itemReport) { |
219 | if (isset($itemReport->getData()['uriResource'])) { |
220 | $data['itemIds'][] = $itemReport->getData()['uriResource']; |
221 | } |
222 | } |
223 | } |
224 | |
225 | return $data; |
226 | } |
227 | |
228 | /** |
229 | * Return a valid uploaded file |
230 | */ |
231 | protected function getUploadedPackage(): string |
232 | { |
233 | if (!$this->getQueryParams(self::RESTITEM_PACKAGE_NAME)) { |
234 | throw new common_exception_MissingParameter(self::RESTITEM_PACKAGE_NAME, __CLASS__); |
235 | } |
236 | |
237 | $file = tao_helpers_Http::getUploadedFile(self::RESTITEM_PACKAGE_NAME); |
238 | |
239 | if (!in_array($file['type'], self::$accepted_types)) { |
240 | throw new common_exception_BadRequest('Uploaded file has to be a valid archive.'); |
241 | } |
242 | |
243 | $pathinfo = pathinfo($file['tmp_name']); |
244 | $destination = $pathinfo['dirname'] . DIRECTORY_SEPARATOR . $file['name']; |
245 | tao_helpers_File::move($file['tmp_name'], $destination); |
246 | |
247 | return $destination; |
248 | } |
249 | |
250 | /** |
251 | * Create an empty item |
252 | */ |
253 | public function createQtiItem(): void |
254 | { |
255 | try { |
256 | // Check if it's post method |
257 | if ($this->getRequestMethod() != Request::HTTP_POST) { |
258 | throw new common_exception_NotImplemented('Only post method is accepted to create empty item.'); |
259 | } |
260 | |
261 | $label = $this->getQueryParams('label') ?? ''; |
262 | // Call service to import package |
263 | $item = $this->getDestinationClass()->createInstance($label); |
264 | |
265 | /** @var taoItems_models_classes_ItemsService $itemService */ |
266 | $itemService = $this->getServiceLocator()->get(taoItems_models_classes_ItemsService::class); |
267 | $itemService->setItemModel($item, $this->getResource(ItemModel::MODEL_URI)); |
268 | |
269 | $this->setSuccessJsonResponse($item->getUri()); |
270 | } catch (Exception $e) { |
271 | $this->returnFailure($e); |
272 | } |
273 | } |
274 | |
275 | /** |
276 | * render an item as a Qti zip package |
277 | * @author christophe GARCIA <christopheg@taotesting.com> |
278 | */ |
279 | public function export() |
280 | { |
281 | |
282 | try { |
283 | if ($this->getRequestMethod() != Request::HTTP_GET) { |
284 | throw new common_exception_NotImplemented('Only GET method is accepted to export QIT Item.'); |
285 | } |
286 | |
287 | if (!$this->getQueryParams('id')) { |
288 | $this->returnFailure(new common_exception_MissingParameter('required parameter `id` is missing')); |
289 | } |
290 | |
291 | $id = $this->getQueryParams('id'); |
292 | |
293 | $item = $this->getResource($id); |
294 | |
295 | /** @var taoItems_models_classes_ItemsService $itemService */ |
296 | $itemService = $this->getServiceLocator()->get(taoItems_models_classes_ItemsService::class); |
297 | |
298 | if ($itemService->hasItemModel($item, [ItemModel::MODEL_URI])) { |
299 | $path = tao_helpers_Export::getExportFile(); |
300 | $tmpZip = new ZipArchive(); |
301 | $tmpZip->open($path, ZipArchive::CREATE); |
302 | |
303 | $exporter = new QTIPackedItemExporter($item, $tmpZip); |
304 | $exporter->export(['apip' => false]); |
305 | |
306 | $exporter->getZip()->close(); |
307 | |
308 | header('Content-Type: application/zip'); |
309 | tao_helpers_Http::returnFile($path, false); |
310 | |
311 | return; |
312 | } else { |
313 | $this->returnFailure(new common_exception_NotFound('item can\'t be found')); |
314 | } |
315 | } catch (Exception $e) { |
316 | $this->returnFailure($e); |
317 | } |
318 | } |
319 | |
320 | /** |
321 | * Create an Item Class |
322 | * |
323 | * Label parameter is mandatory |
324 | * If parent class parameter is an uri of valid test class, new class will be created under it |
325 | * If not parent class parameter is provided, class will be created under root class |
326 | * Comment parameter is not mandatory, used to describe new created class |
327 | * |
328 | */ |
329 | public function createClass(): void |
330 | { |
331 | try { |
332 | $class = $this->createSubClass($this->getClass(TaoOntology::CLASS_URI_ITEM)); |
333 | |
334 | $result = [ |
335 | 'message' => __('Class successfully created.'), |
336 | 'class-uri' => $class->getUri(), |
337 | ]; |
338 | |
339 | $this->setSuccessJsonResponse($result); |
340 | } catch (common_exception_ClassAlreadyExists $e) { |
341 | $result = [ |
342 | 'message' => $e->getMessage(), |
343 | 'class-uri' => $e->getClass()->getUri(), |
344 | ]; |
345 | $this->setSuccessJsonResponse($result); |
346 | } catch (\Exception $e) { |
347 | $this->returnFailure($e); |
348 | } |
349 | } |
350 | } |