Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 341
0.00% covered (danger)
0.00%
0 / 22
CRAP
0.00% covered (danger)
0.00%
0 / 1
Results
0.00% covered (danger)
0.00%
0 / 341
0.00% covered (danger)
0.00%
0 / 22
5852
0.00% covered (danger)
0.00%
0 / 1
 getResultsService
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getServiceProxy
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getDeliveryAssemblyService
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 index
0.00% covered (danger)
0.00%
0 / 45
0.00% covered (danger)
0.00%
0 / 1
30
 getResults
0.00% covered (danger)
0.00%
0 / 51
0.00% covered (danger)
0.00%
0 / 1
30
 delete
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
12
 isCacheable
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 viewResult
0.00% covered (danger)
0.00%
0 / 91
0.00% covered (danger)
0.00%
0 / 1
306
 downloadXML
0.00% covered (danger)
0.00%
0 / 22
0.00% covered (danger)
0.00%
0 / 1
42
 getFile
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
20
 getVariableFile
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
6
 getErrorResponse
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
20
 getStatusCode
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
42
 getResultStorage
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
12
 formatItemVariables
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
20
 getResultsListPlugin
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
 getTreeOptionsFromRequest
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
6
 export
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 exportSql
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 getExporter
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
30
 getNormalizer
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getResultService
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) 2002-2008 (original work) Public Research Centre Henri Tudor & University of Luxembourg
19 *                         (under the project TAO & TAO2);
20 *               2008-2010 (update and modification) Deutsche Institut für Internationale Pädagogische Forschung
21 *                         (under the project TAO-TRANSFER);
22 *               2009-2012 (update and modification) Public Research Centre Henri Tudor
23 *                         (under the project TAO-SUSTAIN & TAO-DEV); *
24 */
25
26namespace oat\taoOutcomeUi\controller;
27
28use common_Exception;
29use common_exception_BadRequest;
30use common_exception_NotFound;
31use Exception;
32use oat\generis\model\GenerisRdf;
33use oat\generis\model\OntologyAwareTrait;
34use oat\generis\model\OntologyRdfs;
35use oat\oatbox\event\EventManager;
36use oat\tao\helpers\UserHelper;
37use oat\tao\model\datatable\implementation\DatatableRequest;
38use oat\tao\model\plugins\PluginModule;
39use oat\tao\model\taskQueue\TaskLogActionTrait;
40use oat\taoDelivery\model\execution\DeliveryExecutionInterface;
41use oat\taoDelivery\model\execution\ServiceProxy;
42use oat\taoDeliveryRdf\model\DeliveryAssemblyService;
43use oat\taoOutcomeUi\helper\ResponseVariableFormatter;
44use oat\taoOutcomeUi\model\event\ResultsListPluginEvent;
45use oat\taoOutcomeUi\model\export\DeliveryCsvResultsExporterFactory;
46use oat\taoOutcomeUi\model\export\DeliveryResultsExporterFactoryInterface;
47use oat\taoOutcomeUi\model\export\DeliverySqlResultsExporterFactory;
48use oat\taoOutcomeUi\model\export\ResultsExporter;
49use oat\taoOutcomeUi\model\plugins\ResultsPluginService;
50use oat\taoOutcomeUi\model\ResultsService;
51use oat\taoOutcomeUi\model\search\ResultsWatcher;
52use oat\taoOutcomeUi\model\table\ResultsMonitoringDatatable;
53use oat\taoOutcomeUi\model\Wrapper\ResultServiceWrapper;
54use oat\taoResultServer\models\classes\NoResultStorage;
55use oat\taoResultServer\models\classes\NoResultStorageException;
56use oat\taoResultServer\models\classes\QtiResultsService;
57use oat\taoResultServer\models\classes\ResultServerService;
58use oat\taoResultServer\models\Formatter\ItemResponseCollectionNormalizer;
59use tao_helpers_Uri;
60
61/**
62 * Results Controller provide actions performed from url resolution
63 *
64 *
65 * @author Patrick Plichart <patrick@taotesting.com>
66 * @author Bertrand Chevrier, <taosupport@tudor.lu>
67 * @package taoOutcomeUi
68 * @license GPLv2  http://www.opensource.org/licenses/gpl-2.0.php
69 */
70class Results extends \tao_actions_CommonModule
71{
72    use TaskLogActionTrait;
73    use OntologyAwareTrait;
74
75    public const PARAMETER_DELIVERY_URI = 'uri';
76    public const PARAMETER_DELIVERY_CLASS_URI = 'classUri';
77
78    /**
79     * @return ResultsService
80     */
81    protected function getResultsService()
82    {
83        return $this->getServiceLocator()->get(ResultServiceWrapper::SERVICE_ID)->getService();
84    }
85
86    /**
87     * @return object|ServiceProxy
88     */
89    protected function getServiceProxy()
90    {
91        return $this->getServiceLocator()->get(ServiceProxy::SERVICE_ID);
92    }
93
94    /**
95     * @return DeliveryAssemblyService
96     */
97    protected function getDeliveryAssemblyService()
98    {
99        return $this->getServiceLocator()->get(DeliveryAssemblyService::class);
100    }
101
102    /**
103     * Action called on click on a delivery (class) construct and call the view to see the table of
104     * all delivery execution for a specific delivery
105     */
106    public function index()
107    {
108        $this->defaultData();
109
110        // if delivery class has been selected, return nothing
111        if (!$this->hasRequestParameter(self::PARAMETER_DELIVERY_URI)) {
112            return;
113        }
114
115        $model = [
116            [
117                'id' => 'ttakerid',
118                'label' => __('Test Taker ID'),
119                'sortable' => false
120            ],
121            [
122                'id' => 'ttaker',
123                'label' => __('Test Taker'),
124                'sortable' => false
125            ],
126            [
127                'id' => 'time',
128                'label' => __('Start Time'),
129                'sortable' => false
130            ]
131        ];
132
133        $deliveryService = DeliveryAssemblyService::singleton();
134        $delivery = $this->getResource($this->getRequestParameter('id'));
135        if ($delivery->getUri() !== $deliveryService->getRootClass()->getUri()) {
136            try {
137                // display delivery
138                $this->getResultStorage($delivery);
139
140                $this->setData('uri', $delivery->getUri());
141                $this->setData('title', $delivery->getLabel());
142                $this->setData('config', [
143                    'dataModel' => $model,
144                    'plugins' => $this->getResultsListPlugin(),
145                    'searchable' => $this->getServiceLocator()->get(ResultsWatcher::SERVICE_ID)->isResultSearchEnabled()
146                ]);
147
148                if ($this->hasRequestParameter('export-callback-url')) {
149                    $this->setData('export-callback-url', $this->getRequestParameter('export-callback-url'));
150                }
151                $this->setView('resultList.tpl');
152            } catch (\common_exception_Error $e) {
153                $this->setData('type', 'error');
154                $this->setData('error', $e->getMessage());
155                $this->setView('index.tpl');
156            }
157        } else {
158            $this->setData('type', 'info');
159            $this->setData(
160                'error',
161                // phpcs:disable Generic.Files.LineLength
162                __('No tests have been taken yet. As soon as a test-taker will take a test his results will be displayed here.')
163                // phpcs:enable Generic.Files.LineLength
164            );
165            $this->setView('index.tpl');
166        }
167    }
168
169
170    /**
171     * Get all result delivery execution to display
172     */
173    public function getResults()
174    {
175        $limit = $this->getRequestParameter('rows');
176        $start = $limit * $this->getRequestParameter('page') - $limit;
177
178        try {
179            $data = [];
180            $readOnly = [];
181            $rights = [
182                'view' => !$this->hasAccess('oat\taoOutcomeUi\controller\Results', 'viewResult', []),
183                'delete' => !$this->hasAccess('oat\taoOutcomeUi\controller\Results', 'delete', []),
184            ];
185
186            if ($this->hasRequestParameter('filterquery')) {
187                $resultsData = new ResultsMonitoringDatatable(DatatableRequest::fromGlobals());
188                $resultsData->setServiceLocator($this->getServiceLocator());
189
190                $payload = $resultsData->getPayload();
191                $results = $payload['data'];
192                $count = $payload['records'];
193            } else {
194                $delivery = new \core_kernel_classes_Resource(
195                    tao_helpers_Uri::decode($this->getRequestParameter('classUri'))
196                );
197                $this->getResultStorage($delivery);
198
199                $results = $this->getResultsService()->getImplementation()->getResultByDelivery([$delivery->getUri()], [
200                    'order' => $this->getRequestParameter('sortby'),
201                    'orderdir' => strtoupper($this->getRequestParameter('sortorder')),
202                    'offset' => $start,
203                    'limit' => $limit,
204                    'recursive' => true,
205                ]);
206                $count = $this->getResultsService()->getImplementation()->countResultByDelivery([$delivery->getUri()]);
207            }
208
209            foreach ($results as $res) {
210                $deliveryExecution = $this->getServiceProxy()->getDeliveryExecution($res['deliveryResultIdentifier']);
211
212                try {
213                    $startTime = \tao_helpers_Date::displayeDate($deliveryExecution->getStartTime());
214                } catch (common_exception_NotFound $e) {
215                    $this->logWarning($e->getMessage());
216                    $startTime = '';
217                }
218
219                $user = UserHelper::getUser($res['testTakerIdentifier']);
220
221                $data[] = [
222                    'id' => $deliveryExecution->getIdentifier(),
223                    'ttakerid' => $res['testTakerIdentifier'],
224                    'ttaker' => _dh(UserHelper::getUserName($user, true)),
225                    'time' => $startTime,
226                ];
227
228                $readOnly[$deliveryExecution->getIdentifier()] = $rights;
229            }
230
231            $this->returnJson([
232                'data' => $data,
233                'page' => floor($start / $limit) + 1,
234                'total' => ceil($count / $limit),
235                'records' => count($data),
236                'readonly' => $readOnly,
237            ]);
238        } catch (\common_exception_Error $e) {
239            $this->returnJson([
240                'error' => $e->getMessage(),
241            ]);
242        }
243    }
244
245    /**
246     * Delete a result or a result class
247     * @throws Exception
248     * @throws common_exception_BadRequest
249     * @return string json {'deleted' : true}
250     */
251    public function delete()
252    {
253        if (!$this->isXmlHttpRequest()) {
254            throw new common_exception_BadRequest('wrong request mode');
255        }
256        $deliveryExecutionUri = tao_helpers_Uri::decode($this->getRequestParameter('uri'));
257        $de = $this->getServiceProxy()->getDeliveryExecution($deliveryExecutionUri);
258
259        try {
260            $this->getResultStorage($de->getDelivery());
261
262            $deleted = $this->getResultsService()->deleteResult($deliveryExecutionUri);
263
264            $this->returnJson(['deleted' => $deleted]);
265        } catch (\common_exception_Error $e) {
266            $this->returnJson(['error' => $e->getMessage()]);
267        }
268    }
269
270    /**
271     * Is the given delivery execution aka. result cacheable?
272     *
273     * @param string $resultIdentifier
274     * @return bool
275     * @throws common_exception_NotFound
276     */
277    private function isCacheable($resultIdentifier)
278    {
279        $uri = $this->getServiceProxy()->getDeliveryExecution($resultIdentifier)->getState()->getUri();
280
281        return $uri == DeliveryExecutionInterface::STATE_FINISHIED;
282    }
283
284    /**
285     * Get info on the current Result and display it
286     */
287    public function viewResult()
288    {
289        $this->defaultData();
290
291        $resultId = $this->getRawParameter('id');
292        $delivery = $this->getResource($this->getRequestParameter('classUri'));
293
294        try {
295            $this->getResultStorage($delivery);
296
297            $testTaker = $this->getResultsService()->getTestTakerData($resultId);
298
299            if (
300                (is_object($testTaker) and (get_class($testTaker) == 'core_kernel_classes_Literal'))
301                or
302                (is_null($testTaker))
303            ) {
304                //the test taker is unknown
305                $this->setData('userLogin', $testTaker);
306                $this->setData('userLabel', $testTaker);
307                $this->setData('userFirstName', $testTaker);
308                $this->setData('userLastName', $testTaker);
309                $this->setData('userEmail', $testTaker);
310            } else {
311                $login = (count($testTaker[GenerisRdf::PROPERTY_USER_LOGIN]) > 0) ? current(
312                    $testTaker[GenerisRdf::PROPERTY_USER_LOGIN]
313                )->literal : "";
314                $label = (count($testTaker[OntologyRdfs::RDFS_LABEL]) > 0)
315                    ? current($testTaker[OntologyRdfs::RDFS_LABEL])->literal
316                    : "";
317                $firstName = (count($testTaker[GenerisRdf::PROPERTY_USER_FIRSTNAME]) > 0) ? current(
318                    $testTaker[GenerisRdf::PROPERTY_USER_FIRSTNAME]
319                )->literal : "";
320                $userLastName = (count($testTaker[GenerisRdf::PROPERTY_USER_LASTNAME]) > 0) ? current(
321                    $testTaker[GenerisRdf::PROPERTY_USER_LASTNAME]
322                )->literal : "";
323                $userEmail = (count($testTaker[GenerisRdf::PROPERTY_USER_MAIL]) > 0) ? current(
324                    $testTaker[GenerisRdf::PROPERTY_USER_MAIL]
325                )->literal : "";
326
327                $this->setData('userLogin', $login);
328                $this->setData('userLabel', $label);
329                $this->setData('userFirstName', $firstName);
330                $this->setData('userLastName', $userLastName);
331                $this->setData('userEmail', $userEmail);
332            }
333            $filterSubmission = ($this->hasRequestParameter("filterSubmission"))
334                ? $this->getRequestParameter("filterSubmission")
335                : ResultsService::VARIABLES_FILTER_LAST_SUBMITTED;
336            $filterTypes = ($this->hasRequestParameter("filterTypes"))
337                ? $this->getRequestParameter("filterTypes")
338                : [
339                    \taoResultServer_models_classes_ResponseVariable::class,
340                    \taoResultServer_models_classes_OutcomeVariable::class,
341                    \taoResultServer_models_classes_TraceVariable::class,
342                ];
343
344            // check the result page cache; if we have hit than return the gzencoded string and let the client to encode
345            // the data
346            $cacheKey = $this->getResultsService()->getCacheKey(
347                $resultId,
348                md5($filterSubmission . implode(',', $filterTypes))
349            );
350            $isResultCacheable = $this->isCacheable($resultId);
351
352            if (
353                $isResultCacheable
354                && $this->getResultsService()->getCache()
355                && $this->getResultsService()->getCache()->exists($cacheKey)
356            ) {
357                $this->logDebug('Result page cache hit for "' . $cacheKey . '"');
358
359                $gzipOutput = $this->getResultsService()->getCache()->get($cacheKey);
360
361                header('Content-Encoding: gzip');
362                header('Content-Length: ' . strlen($gzipOutput));
363
364                echo $gzipOutput;
365                exit;
366            }
367
368            $variables = $this->getResultsService()->getImplementation()->getDeliveryVariables($resultId);
369            $variables = $this->getNormalizer()->normalize($variables);
370
371            $structuredItemVariables = $this->getResultsService()->structureItemVariables(
372                $variables,
373                $filterSubmission
374            );
375            $itemVariables = $this->formatItemVariables($structuredItemVariables, $filterTypes);
376            $testVariables = $this->getResultsService()->extractTestVariables(
377                $variables,
378                $filterTypes,
379                $filterSubmission
380            );
381
382            // render item variables
383            $this->setData('variables', $itemVariables);
384            $stats = $this->getResultsService()->calculateResponseStatistics($itemVariables);
385            $this->setData('nbResponses', $stats["nbResponses"]);
386            $this->setData('nbCorrectResponses', $stats["nbCorrectResponses"]);
387            $this->setData('nbIncorrectResponses', $stats["nbIncorrectResponses"]);
388            $this->setData('nbUnscoredResponses', $stats["nbUnscoredResponses"]);
389            // render test variables
390            $this->setData('deliveryVariables', $testVariables);
391
392            $this->setData('itemType', $this->getResultsService()->getDeliveryItemType($resultId));
393            $this->setData('id', $resultId);
394            $this->setData('classUri', $delivery->getUri());
395            $this->setData('filterSubmission', $filterSubmission);
396            $this->setData('filterTypes', $filterTypes);
397            $this->setView('viewResult.tpl');
398
399            // quick hack to gain performance: caching the entire result page if it is cacheable
400            // "gzencode" is used to reduce the size of the string to be cached
401            ob_start(function ($buffer) use ($isResultCacheable, $resultId, $cacheKey) {
402                if (
403                    $isResultCacheable
404                    && $this->getResultsService()->setCacheValue($resultId, $cacheKey, gzencode($buffer, 9))
405                ) {
406                    \common_Logger::d('Result page cache set for "' . $cacheKey . '"');
407                }
408
409                return $buffer;
410            });
411        } catch (\common_exception_Error $e) {
412            $this->setData('type', 'error');
413            $this->setData('error', $e->getMessage());
414            $this->setView('index.tpl');
415        }
416    }
417
418    /**
419     * Download delivery execution XML
420     *
421     * @author Gyula Szucs, <gyula@taotesting.com>
422     * @throws \common_exception_MissingParameter
423     * @throws common_exception_NotFound
424     * @throws \common_exception_ValidationFailed
425     */
426    public function downloadXML()
427    {
428        try {
429            if (!$this->hasRequestParameter('id') || empty($this->getRequestParameter('id'))) {
430                throw new \common_exception_MissingParameter(
431                    'Result id is missing from the request.',
432                    $this->getRequestURI()
433                );
434            }
435            if (!$this->hasRequestParameter('delivery') || empty($this->getRequestParameter('delivery'))) {
436                throw new \common_exception_MissingParameter(
437                    'Delivery id is missing from the request.',
438                    $this->getRequestURI()
439                );
440            }
441
442            $qtiResultService = $this->getServiceManager()->get(QtiResultsService::SERVICE_ID);
443            $xml = $qtiResultService->getQtiResultXml(
444                $this->getRequestParameter('delivery'),
445                $this->getRawParameter('id')
446            );
447
448            //used by jquery file download to find out the download has been triggered ...
449            header('Set-Cookie: fileDownload=true');
450            setcookie("fileDownload", "true", 0, "/");
451            header('Content-Disposition: attachment; filename="delivery_execution_' . date('YmdHis') . '.xml"');
452            header('Content-Type: application/xml');
453
454            echo $xml;
455        } catch (\common_exception_UserReadableException $e) {
456            $this->returnJson(['error' => $e->getUserMessage()]);
457        }
458    }
459
460    /**
461     * Get the data for the file in the response and allow user to download it
462     */
463    public function getFile()
464    {
465        $variableUri = $_POST["variableUri"];
466
467        $delivery = $this->getResource(tao_helpers_Uri::decode($this->getRequestParameter('deliveryUri')));
468        try {
469            $this->getResultStorage($delivery);
470
471            $file = $this->getResultsService()->getVariableFile($variableUri);
472            header(
473                'Set-Cookie: fileDownload=true'
474            ); //used by jquery file download to find out the download has been triggered ...
475            setcookie("fileDownload", "true", 0, "/");
476            header("Content-type: " . $file["mimetype"]);
477            if (!isset($file["filename"]) || $file["filename"] == "") {
478                header('Content-Disposition: attachment; filename=download');
479            } else {
480                header('Content-Disposition: attachment; filename=' . $file["filename"]);
481            }
482
483            echo $file["data"];
484        } catch (\common_exception_Error $e) {
485            echo $e->getMessage();
486        }
487    }
488
489    /**
490     * Get the data for the file in the response as a variable data
491     */
492    public function getVariableFile()
493    {
494        $delivery = $this->getResource(tao_helpers_Uri::decode($this->getRequestParameter('deliveryUri')));
495        $variableUri = $this->getResource(tao_helpers_Uri::decode($this->getRequestParameter('variableUri')));
496        try {
497            $this->getResultStorage($delivery);
498
499            $file = $this->getResultsService()->getVariableFile($variableUri);
500
501            // weirdly, the mime type declaration can be expressed as a HTTP header notation
502            $mime = trim(str_replace('content-type:', '', strtolower($file["mimetype"])));
503
504            $this->returnJson(
505                [
506                    'success' => true,
507                    'data' => base64_encode($file["data"]),
508                    'name' => $file["filename"],
509                    'mime' => $mime,
510                ]
511            );
512        } catch (\common_exception_Error $e) {
513            $this->returnJson(
514                $this->getErrorResponse($e),
515                $this->getStatusCode($e)
516            );
517        }
518    }
519
520    /**
521     * Gets an error response object
522     * @param Exception $e Exception from which extract the error context
523     * @return array
524     */
525    protected function getErrorResponse(Exception $e): array
526    {
527        $this->logError($e->getMessage());
528
529        $response = [
530            'success' => false,
531            'type' => 'error',
532        ];
533        if ($e instanceof Exception) {
534            $response['type'] = 'exception';
535            $response['code'] = $e->getCode();
536        }
537        if ($e instanceof \common_exception_UserReadableException) {
538            $response['message'] = $e->getUserMessage();
539        } else {
540            $response['message'] = __('Internal server error!');
541        }
542        if ($e instanceof \common_exception_Unauthorized) {
543            $response['code'] = 403;
544        }
545        return $response;
546    }
547
548    /**
549     * Gets an HTTP response code
550     * @param ?Exception [$e] Optional exception from which extract the error context
551     * @return int
552     */
553    protected function getStatusCode(?Exception $e = null): int
554    {
555        $code = 200;
556        if ($e) {
557            $code = 500;
558
559            switch (true) {
560                case $e instanceof \common_exception_NotImplemented:
561                case $e instanceof \common_exception_NoImplementation:
562                    $code = 501;
563                    break;
564
565                case $e instanceof \common_exception_Unauthorized:
566                    $code = 403;
567                    break;
568
569                case $e instanceof \tao_models_classes_FileNotFoundException:
570                    $code = 404;
571                    break;
572            }
573        }
574        return $code;
575    }
576
577    /**
578     * Returns the currently configured result storage
579     *
580     * @param \core_kernel_classes_Resource $delivery
581     * @return \taoResultServer_models_classes_ReadableResultStorage
582     */
583    protected function getResultStorage($delivery)
584    {
585        /** @var ResultServerService $resultServerService */
586        $resultServerService = $this->getServiceManager()->get(ResultServerService::SERVICE_ID);
587        $resultStorage = $resultServerService->getResultStorage($delivery->getUri());
588        if ($resultStorage instanceof NoResultStorage) {
589            throw NoResultStorageException::create();
590        }
591
592        if (!$resultStorage instanceof \taoResultServer_models_classes_ReadableResultStorage) {
593            throw new \common_exception_Error('The results storage it is not readable');
594        }
595        $this->getResultsService()->setImplementation($resultStorage);
596        return $resultStorage;
597    }
598
599    /**
600     * Regroup item variables by attempt
601     *
602     * @param array $variables
603     * @param array $filterTypes
604     * @return array
605     * @throws common_Exception
606     */
607    protected function formatItemVariables(array $variables, array $filterTypes): array
608    {
609        $displayedVariables = $this->getResultsService()->filterStructuredVariables($variables, $filterTypes);
610        $responses = ResponseVariableFormatter::formatStructuredVariablesToItemState($variables);
611        $excludedVariables = array_flip(['numAttempts', 'duration']);
612
613        foreach ($displayedVariables as $itemKey => &$item) {
614            $state = isset($responses[$itemKey][$item['attempt']])
615                ? array_diff_key($responses[$itemKey][$item['attempt']], $excludedVariables)
616                : [];
617
618            $item['state'] = !empty($state) ? json_encode($state) : '{}';
619        }
620
621        return $displayedVariables;
622    }
623
624    /**
625     * Get the list of active plugins for the list of results
626     * @return PluginModule[] the list of plugins
627     */
628    public function getResultsListPlugin()
629    {
630        /* @var ResultsPluginService $pluginService */
631        $pluginService = $this->getServiceLocator()->get(ResultsPluginService::SERVICE_ID);
632
633        $event = new ResultsListPluginEvent($pluginService->getAllPlugins());
634        $this->getServiceLocator()->get(EventManager::SERVICE_ID)->trigger($event);
635
636        // return the list of active plugins
637        return array_filter($event->getPlugins(), function ($plugin) {
638            return !is_null($plugin) && $plugin->isActive();
639        });
640    }
641
642    /**
643     * @param array $options
644     * @return array
645     * @throws
646     */
647    protected function getTreeOptionsFromRequest($options = [])
648    {
649        $config = $this->getServiceManager()->get('taoDeliveryRdf/DeliveryMgmt')->getConfig();
650        $options =  parent::getTreeOptionsFromRequest($options);
651        $options['order'] = key($config['OntologyTreeOrder']);
652        $options['orderdir'] = $config['OntologyTreeOrder'][$options['order']];
653        if ($this->hasRequestParameter('classUri')) {
654            $options['class'] = $this->getCurrentClass();
655        } else {
656            $options['class'] = $this->getDeliveryAssemblyService()->getRootClass();
657        }
658        return $options;
659    }
660
661    /**
662     * Exports results by either a class or a single delivery in csv format.
663     *
664     * Only creating the export task.
665     *
666     * @throws Exception
667     * @throws common_Exception
668     */
669    public function export()
670    {
671        $exporter = $this->getExporter(new DeliveryCsvResultsExporterFactory());
672        return $this->returnTaskJson($exporter->createExportTask());
673    }
674
675    /**
676     * Exports results by either a class or a single delivery in sql format.
677     *
678     * Only creating the export task.
679     *
680     * @throws Exception
681     * @throws common_Exception
682     */
683    public function exportSql()
684    {
685        $exporter = $this->getExporter(new DeliverySqlResultsExporterFactory());
686        return $this->returnTaskJson($exporter->createExportTask());
687    }
688
689    /**
690     * @param DeliveryResultsExporterFactoryInterface $deliveryResultsExporterFactory
691     * @return ResultsExporter
692     * @throws common_Exception
693     * @throws common_exception_NotFound
694     */
695    private function getExporter(DeliveryResultsExporterFactoryInterface $deliveryResultsExporterFactory)
696    {
697        if (!$this->isXmlHttpRequest()) {
698            throw new \Exception('Only ajax call allowed.');
699        }
700
701        if (
702            !$this->hasRequestParameter(self::PARAMETER_DELIVERY_CLASS_URI)
703            && !$this->hasRequestParameter(self::PARAMETER_DELIVERY_URI)
704        ) {
705            throw new common_Exception(
706                'Parameter "' . self::PARAMETER_DELIVERY_CLASS_URI . '" or "' . self::PARAMETER_DELIVERY_URI
707                    . '" missing'
708            );
709        }
710
711        $resourceUri = $this->hasRequestParameter(self::PARAMETER_DELIVERY_URI)
712            ? \tao_helpers_Uri::decode($this->getRequestParameter(self::PARAMETER_DELIVERY_URI))
713            : \tao_helpers_Uri::decode($this->getRequestParameter(self::PARAMETER_DELIVERY_CLASS_URI));
714
715        /** @var ResultsExporter $exporter */
716        $exporter = $this->propagate(
717            new ResultsExporter(
718                $resourceUri,
719                ResultsService::singleton(),
720                $deliveryResultsExporterFactory
721            )
722        );
723
724        return $exporter;
725    }
726
727    private function getNormalizer(): ItemResponseCollectionNormalizer
728    {
729        return $this->getServiceLocator()->get(ItemResponseCollectionNormalizer::class);
730    }
731
732    /**
733     * @return ResultsService
734     */
735    private function getResultService()
736    {
737        return $this->getServiceLocator()->get(ResultsService::SERVICE_ID);
738    }
739}