tao-test/app/taoOutcomeUi/model/ResultsService.php

1727 lines
61 KiB
PHP

<?php
/**
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; under version 2
* of the License (non-upgradable).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* Copyright (c) 2013-2017 Open Assessment Technologies S.A.
*
*
* @access public
* @author Joel Bout, <joel.bout@tudor.lu>
* @package taoOutcomeUi
*/
namespace oat\taoOutcomeUi\model;
use common_Exception;
use common_exception_Error;
use common_Logger;
use core_kernel_classes_Resource;
use League\Flysystem\FileNotFoundException;
use oat\generis\model\GenerisRdf;
use oat\generis\model\OntologyRdfs;
use oat\oatbox\service\ServiceManager;
use oat\oatbox\user\User;
use oat\tao\helpers\metadata\ResourceCompiledMetadataHelper;
use oat\tao\model\metadata\compiler\ResourceJsonMetadataCompiler;
use oat\tao\model\metadata\compiler\ResourceMetadataCompilerInterface;
use oat\tao\model\OntologyClassService;
use oat\taoDelivery\model\execution\DeliveryExecution;
use oat\taoDelivery\model\execution\ServiceProxy;
use oat\taoDelivery\model\RuntimeService;
use oat\taoDeliveryRdf\model\DeliveryAssemblyService;
use oat\taoItems\model\ItemCompilerIndex;
use oat\taoOutcomeUi\helper\Datatypes;
use oat\taoOutcomeUi\model\table\ContextTypePropertyColumn;
use oat\taoOutcomeUi\model\table\GradeColumn;
use oat\taoOutcomeUi\model\table\ResponseColumn;
use oat\taoOutcomeUi\model\table\TraceVariableColumn;
use oat\taoOutcomeUi\model\table\VariableColumn;
use oat\taoOutcomeUi\model\Wrapper\ResultServiceWrapper;
use oat\taoQtiTest\models\QtiTestCompilerIndex;
use oat\taoResultServer\models\classes\NoResultStorage;
use oat\taoResultServer\models\classes\NoResultStorageException;
use oat\taoResultServer\models\classes\ResultManagement;
use oat\taoResultServer\models\classes\ResultServerService;
use oat\taoResultServer\models\classes\ResultService;
use oat\taoResultServer\models\Formatter\ItemResponseVariableSplitter;
use tao_helpers_Date;
use tao_models_classes_service_StorageDirectory;
use taoQtiTest_models_classes_QtiTestService;
use taoResultServer_models_classes_ReadableResultStorage;
use taoResultServer_models_classes_Variable as Variable;
use oat\taoOutcomeUi\model\table\TestCenterColumn;
class ResultsService extends OntologyClassService
{
public const SERVICE_ID = 'taoOutcomeUi/OutcomeUiResultService';
public const VARIABLES_FILTER_LAST_SUBMITTED = 'lastSubmitted';
public const VARIABLES_FILTER_FIRST_SUBMITTED = 'firstSubmitted';
public const VARIABLES_FILTER_ALL = 'all';
// Only need to correct formatting trace variables
protected const VARIABLES_FILTER_TRACE = 'trace';
public const PERSISTENCE_CACHE_KEY = 'resultCache';
public const PERIODS = [self::FILTER_START_FROM, self::FILTER_START_TO, self::FILTER_END_FROM, self::FILTER_END_TO];
public const DELIVERY_EXECUTION_STARTED_AT = 'delivery_execution_started_at';
public const DELIVERY_EXECUTION_FINISHED_AT = 'delivery_execution_finished_at';
public const FILTER_START_FROM = 'startfrom';
public const FILTER_START_TO = 'startto';
public const FILTER_END_FROM = 'endfrom';
public const FILTER_END_TO = 'endto';
public const OPTION_ALLOW_SQL_EXPORT = 'allow_sql_export';
public const OPTION_ALLOW_TRACE_VARIABLES_EXPORT = 'allow_trace_variable_export';
public const SEPARATOR = ' | ';
/** @var taoResultServer_models_classes_ReadableResultStorage */
private $implementation;
/**
* Internal cache for item info.
*
* @var array
*/
private $itemInfoCache = [];
/**
* External cache.
*
* @var \common_persistence_KvDriver
*/
private $resultCache;
/** @var array */
private $indexerCache = [];
/** @var array */
private $executionCache = [];
/** @var array */
private $testMetadataCache = [];
/**
* @return \common_persistence_KvDriver|null
*/
public function getCache()
{
if (is_null($this->resultCache)) {
/** @var \common_persistence_Manager $persistenceManager */
$persistenceManager = $this->getServiceLocator()->get(\common_persistence_Manager::SERVICE_ID);
if ($persistenceManager->hasPersistence(self::PERSISTENCE_CACHE_KEY)) {
$this->resultCache = $persistenceManager->getPersistenceById(self::PERSISTENCE_CACHE_KEY);
}
}
return $this->resultCache;
}
public function getCacheKey($resultIdentifier, $suffix = '')
{
return 'resultPageCache:' . $resultIdentifier . ':' . $suffix;
}
protected function getContainerCacheKey($resultIdentifier)
{
return $this->getCacheKey($resultIdentifier, 'keys');
}
public function setCacheValue($resultIdentifier, $fullKey, $value)
{
if (is_null($this->getCache())) {
return false;
}
$fullKeys = [];
$containerKey = $this->getContainerCacheKey($resultIdentifier);
if ($this->getCache()->exists($containerKey)) {
$fullKeys = $this->getContainerCacheValue($containerKey);
}
$fullKeys[] = $fullKey;
if ($this->getCache()->set($fullKey, $value)) {
// let's save the container of the keys as well
return $this->setContainerCacheValue($containerKey, $fullKeys);
}
return false;
}
public function deleteCacheFor($resultIdentifier)
{
if (is_null($this->getCache())) {
return false;
}
$containerKey = $this->getContainerCacheKey($resultIdentifier);
if (!$this->getCache()->exists($containerKey)) {
return false;
}
$fullKeys = $this->getContainerCacheValue($containerKey);
$initialCount = count($fullKeys);
foreach ($fullKeys as $i => $key) {
if ($this->getCache()->del($key)) {
unset($fullKeys[$i]);
}
}
if (empty($fullKeys)) {
// delete the whole container
return $this->getCache()->del($containerKey);
} elseif (count($fullKeys) < $initialCount) {
// update the container
return $this->setContainerCacheValue($containerKey, $fullKeys);
}
// no cache has been deleted
return false;
}
protected function setContainerCacheValue($containerKey, array $fullKeys)
{
return $this->getCache()->set($containerKey, gzencode(json_encode(array_unique($fullKeys)), 9));
}
protected function getContainerCacheValue($containerKey)
{
return json_decode(gzdecode($this->getCache()->get($containerKey)), true);
}
/**
* (non-PHPdoc)
* @see tao_models_classes_ClassService::getRootClass()
*/
public function getRootClass()
{
return $this->getClass(ResultService::DELIVERY_RESULT_CLASS_URI);
}
public function setImplementation(ResultManagement $implementation)
{
$this->implementation = $implementation;
}
/**
* @return ResultManagement
* @throws common_exception_Error
*/
public function getImplementation()
{
if ($this->implementation == null) {
throw new \common_exception_Error('No result storage defined');
}
return $this->implementation;
}
/**
* return all variable for that deliveryResults (uri identifiers)
*
* @access public
*
* @param string $resultIdentifier
* @param boolean $flat a flat array is returned or a structured delvieryResult-ItemResult-Variable
*
* @return array
* @author Joel Bout, <joel.bout@tudor.lu>
*/
public function getVariables($resultIdentifier, $flat = true)
{
$variables = [];
//this service is slow due to the way the data model design
//if the delvieryResult related execution is finished, the data is stored in cache.
$serial = 'deliveryResultVariables:' . $resultIdentifier;
//if (common_cache_FileCache::singleton()->has($serial)) {
// $variables = common_cache_FileCache::singleton()->get($serial);
//} else {
$resultVariables = $this->getImplementation()->getDeliveryVariables($resultIdentifier);
foreach ($resultVariables as $resultVariable) {
$currentItem = current($resultVariable);
$key = isset($currentItem->callIdItem) ? $currentItem->callIdItem : $currentItem->callIdTest;
$variables[$key][] = $resultVariable;
}
// impossible to determine state DeliveryExecution::STATE_FINISHIED
// if (false) {
// common_cache_FileCache::singleton()->put($variables, $serial);
// }
//}
if ($flat) {
$returnValue = [];
foreach ($variables as $key => $itemResultVariables) {
$newKeys = [];
$oldKeys = array_keys($itemResultVariables);
foreach ($oldKeys as $oldKey) {
$newKeys[] = $key . '_' . $oldKey;
}
$itemResultVariables = array_combine($newKeys, array_values($itemResultVariables));
$returnValue = array_merge($returnValue, $itemResultVariables);
}
} else {
$returnValue = $variables;
}
return (array)$returnValue;
}
/**
* @param string|array $itemResult
* @param array $wantedTypes
*
* @return array
* @throws common_exception_Error
*/
public function getVariablesFromObjectResult($itemResult, $wantedTypes = [\taoResultServer_models_classes_ResponseVariable::class, \taoResultServer_models_classes_OutcomeVariable::class, \taoResultServer_models_classes_TraceVariable::class])
{
$returnedVariables = [];
$variables = $this->getImplementation()->getVariables($itemResult);
foreach ($variables as $itemVariables) {
foreach ($itemVariables as $variable) {
if (in_array(get_class($variable->variable), $wantedTypes)) {
$returnedVariables[] = [$variable];
}
}
}
unset($variables);
return $returnedVariables;
}
/**
* Return the corresponding delivery
*
* @param string $resultIdentifier
*
* @return core_kernel_classes_Resource delviery
* @author Patrick Plichart, <patrick@taotesting.com>
*/
public function getDelivery($resultIdentifier)
{
return new core_kernel_classes_Resource($this->getImplementation()->getDelivery($resultIdentifier));
}
/**
* Ges the type of items contained by the delivery
*
* @param string $resultIdentifier
*
* @return string
*/
public function getDeliveryItemType($resultIdentifier)
{
$resultsViewerService = $this->getServiceLocator()->get(ResultsViewerService::SERVICE_ID);
return $resultsViewerService->getDeliveryItemType($resultIdentifier);
}
/**
* Returns all label of itemResults related to the delvieryResults
*
* @param string $resultIdentifier
*
* @return array string uri
* */
public function getItemResultsFromDeliveryResult($resultIdentifier)
{
return $this->getImplementation()->getRelatedItemCallIds($resultIdentifier);
}
/**
* Returns all label of itemResults related to the delvieryResults
*
* @param string $resultIdentifier
*
* @return array string uri
* */
public function getTestsFromDeliveryResult($resultIdentifier)
{
return $this->getImplementation()->getRelatedTestCallIds($resultIdentifier);
}
/**
*
* @param string $itemCallId
* @param array $itemVariables already retrieved variables
*
* @return array|null
* @throws \common_exception_NotFound
* @throws common_exception_Error
*/
public function getItemFromItemResult($itemCallId, $itemVariables = [])
{
$item = null;
if (empty($itemVariables)) {
$itemVariables = $this->getImplementation()->getVariables($itemCallId);
}
//get the first variable (item are the same in all)
$tmpItems = array_shift($itemVariables);
//get the first object
$itemUri = $tmpItems[0]->item;
$delivery = $this->getDeliveryByResultId($tmpItems[0]->deliveryResultIdentifier);
$itemIndexer = $this->getItemIndexer($delivery);
if (!is_null($itemUri)) {
$langItem = $itemIndexer->getItem($itemUri, $this->getResultLanguage());
$item = array_merge(is_array($langItem) ? $langItem : [], ['uriResource' => $itemUri]);
}
return $item;
}
/**
*
* @param string $test
*
* @return \core_kernel_classes_Resource
*/
public function getVariableFromTest($test)
{
$returnTest = null;
$tests = $this->getImplementation()->getVariables($test);
//get the first variable (item are the same in all)
$tmpTests = array_shift($tests);
//get the first object
if (!is_null($tmpTests[0]->test)) {
$returnTest = new core_kernel_classes_Resource($tmpTests[0]->test);
}
return $returnTest;
}
/**
*
* @param string $variableUri
*
* @return string
*
*/
public function getVariableCandidateResponse($variableUri)
{
return $this->getImplementation()->getVariableProperty($variableUri, 'candidateResponse');
}
/**
*
* @param string $variableUri
*
* @return string
*/
public function getVariableBaseType($variableUri)
{
return $this->getImplementation()->getVariableProperty($variableUri, 'baseType');
}
/**
*
* @param array $variablesData
*
* @return array ["nbResponses" => x,"nbCorrectResponses" => y,"nbIncorrectResponses" => z,"nbUnscoredResponses" =>
* a,"data" => $variableData]
*/
public function calculateResponseStatistics($variablesData)
{
$numberOfResponseVariables = 0;
$numberOfCorrectResponseVariables = 0;
$numberOfInCorrectResponseVariables = 0;
$numberOfUnscoredResponseVariables = 0;
foreach ($variablesData as $epoch => $itemVariables) {
foreach ($itemVariables as $key => $value) {
if ($key == \taoResultServer_models_classes_ResponseVariable::class) {
foreach ($value as $variable) {
$numberOfResponseVariables++;
switch ($variable['isCorrect']) {
case 'correct':
$numberOfCorrectResponseVariables++;
break;
case 'incorrect':
$numberOfInCorrectResponseVariables++;
break;
case 'unscored':
$numberOfUnscoredResponseVariables++;
break;
default:
common_Logger::w('The value ' . $variable['isCorrect'] . ' is not a valid value');
break;
}
}
}
}
}
$stats = [
"nbResponses" => $numberOfResponseVariables,
"nbCorrectResponses" => $numberOfCorrectResponseVariables,
"nbIncorrectResponses" => $numberOfInCorrectResponseVariables,
"nbUnscoredResponses" => $numberOfUnscoredResponseVariables,
];
return $stats;
}
/**
* @param $itemCallId
* @param $itemVariables
*
* @return array item information ['uri' => xxx, 'label' => yyy]
*/
private function getItemInfos($itemCallId, $itemVariables)
{
$undefinedStr = __('unknown'); //some data may have not been submitted
try {
common_Logger::d("Retrieving related Item for item call " . $itemCallId . "");
$relatedItem = $this->getItemFromItemResult($itemCallId, $itemVariables);
} catch (common_Exception $e) {
common_Logger::w("The item call '" . $itemCallId . "' is not linked to a valid item. (deleted item ?)");
$relatedItem = null;
}
$itemIdentifier = $undefinedStr;
$itemLabel = $undefinedStr;
if ($relatedItem) {
$itemIdentifier = $relatedItem['uriResource'];
// check item info in internal cache
if (isset($this->itemInfoCache[$itemIdentifier])) {
common_Logger::t("Item info found in internal cache for item " . $itemIdentifier . "");
return $this->itemInfoCache[$itemIdentifier];
}
$itemLabel = $relatedItem['label'];
}
$item['itemModel'] = '---';
$item['label'] = $itemLabel;
$item['uri'] = $itemIdentifier;
// storing item info in memory to not hit the db for the same item again and again
// when method "getStructuredVariables" are called multiple times in the same request
if ($relatedItem) {
$this->itemInfoCache[$itemIdentifier] = $item;
}
return $item;
}
/**
* prepare a data set as an associative array, service intended to populate gui controller
*
* @param string $resultIdentifier
* @param string $filter 'lastSubmitted', 'firstSubmitted', 'all'
* @param array $wantedTypes ['taoResultServer_models_classes_ResponseVariable',
* 'taoResultServer_models_classes_OutcomeVariable',
* 'taoResultServer_models_classes_TraceVariable']
*
* @return array
* [
* 'epoch1' => [
* 'label' => Example_0_Introduction,
* 'uri' => http://tao.local/mytao.rdf#i1462952280695832,
* 'internalIdentifier' => item-1,
* 'taoResultServer_models_classes_Variable class name' => [
* 'Variable identifier 1' => [
* 'uri' => 1,
* 'var' => taoResultServer_models_classes_Variable object,
* 'isCorrect' => correct
* ],
* 'Variable identifier 2' => [
* 'uri' => 2,
* 'var' => taoResultServer_models_classes_Variable object,
* 'isCorrect' => unscored
* ]
* ]
* ]
* ]
*
* @throws common_exception_Error
*/
public function getStructuredVariables($resultIdentifier, $filter, $wantedTypes = [])
{
$itemCallIds = $this->getItemResultsFromDeliveryResult($resultIdentifier);
// splitting call ids into chunks to perform bulk queries
$itemCallIdChunks = array_chunk($itemCallIds, 50);
$itemVariables = [];
foreach ($itemCallIdChunks as $ids) {
$itemVariables = array_merge($itemVariables, $this->getVariablesFromObjectResult($ids, $wantedTypes));
}
return $this->structureItemVariables($itemVariables, $filter);
}
public function structureItemVariables($itemVariables, $filter)
{
usort($itemVariables, function ($a, $b) {
$variableA = $a[0]->variable;
$variableB = $b[0]->variable;
[$usec, $sec] = explode(" ", $variableA->getEpoch());
$floata = ((float)$usec + (float)$sec);
[$usec, $sec] = explode(" ", $variableB->getEpoch());
$floatb = ((float)$usec + (float)$sec);
if ((floatval($floata) - floatval($floatb)) > 0) {
return 1;
} elseif ((floatval($floata) - floatval($floatb)) < 0) {
return -1;
} else {
return 0;
}
});
$attempts = $this->splitByItemAndAttempt($itemVariables, $filter);
$variablesByItem = [];
foreach ($attempts as $time => $variables) {
$variablesByItem[$time] = [];
foreach ($variables as $itemVariable) {
$variable = $itemVariable->variable;
$itemCallId = $itemVariable->callIdItem;
if ($variable->getIdentifier() == 'numAttempts') {
$variablesByItem[$time] = array_merge($variablesByItem[$time], $this->getItemInfos($itemCallId, [[$itemVariable]]));
$variablesByItem[$time]['attempt'] = $variable->getValue();
}
$variableDescription = [
'uri' => $itemVariable->uri,
'var' => $variable,
];
if ($variable instanceof \taoResultServer_models_classes_ResponseVariable && !is_null($variable->getCorrectResponse())) {
$variableDescription['isCorrect'] = $variable->getCorrectResponse() >= 1 ? 'correct' : 'incorrect';
} else {
$variableDescription['isCorrect'] = 'unscored';
}
// some dangerous assumptions about the call Id structure
$callIdParts = explode('.', $itemCallId);
$variablesByItem[$time]['internalIdentifier'] = $callIdParts[count($callIdParts) - 2];
$variablesByItem[$time][get_class($variable)][$variable->getIdentifier()] = $variableDescription;
}
}
return $variablesByItem;
}
public function splitByItemAndAttempt($itemVariables, $filter)
{
$sorted = [];
foreach ($this->splitByItem($itemVariables) as $variables) {
$itemCallId = current($variables)->callIdItem;
$byAttempt = $this->splitByAttempt($variables);
switch ($filter) {
case self::VARIABLES_FILTER_ALL:
foreach ($byAttempt as $time => $attempt) {
$sorted[$time . $itemCallId] = $attempt;
}
break;
case self::VARIABLES_FILTER_FIRST_SUBMITTED:
reset($byAttempt);
$sorted[key($byAttempt) . $itemCallId] = current($byAttempt);
break;
case self::VARIABLES_FILTER_LAST_SUBMITTED:
end($byAttempt);
$sorted[key($byAttempt) . $itemCallId] = current($byAttempt);
break;
default:
throw new \common_exception_InconsistentData('Unknown Filter ' . $filter);
}
}
ksort($sorted);
return $sorted;
}
/**
* Split item variables by item
*/
public function splitByItem($itemVariables)
{
$byItem = [];
foreach ($itemVariables as $variable) {
$itemVariable = $variable[0];
if (!is_null($itemVariable->callIdItem)) {
if (!isset($byItem[$itemVariable->callIdItem])) {
$byItem[$itemVariable->callIdItem] = [$itemVariable];
} else {
$byItem[$itemVariable->callIdItem][] = $itemVariable;
}
}
}
return $byItem;
}
public function splitByAttempt($itemVariables)
{
return $this->getItemResponseSplitter()->splitObjByAttempt($itemVariables);
}
private function getItemResponseSplitter(): ItemResponseVariableSplitter
{
return $this->getServiceLocator()->get(ItemResponseVariableSplitter::class);
}
/**
* Filters the complex array structure for variable classes
*
* @param array $structure as defined by getStructuredVariables()
* @param array $filter classes to keep
*
* @return array as defined by getStructuredVariables()
*/
public function filterStructuredVariables(array $structure, array $filter)
{
$all = [
\taoResultServer_models_classes_ResponseVariable::class,
\taoResultServer_models_classes_OutcomeVariable::class,
\taoResultServer_models_classes_TraceVariable::class,
];
$toRemove = array_diff($all, $filter);
$filtered = $structure;
foreach ($filtered as $timestamp => $entry) {
foreach ($entry as $key => $value) {
if (in_array($key, $toRemove)) {
unset($filtered[$timestamp][$key]);
}
}
}
return $filtered;
}
/**
*
* @param $resultIdentifier
* @param string $filter 'lastSubmitted', 'firstSubmitted'
*
* @return array ["nbResponses" => x,"nbCorrectResponses" => y,"nbIncorrectResponses" => z,"nbUnscoredResponses" =>
* a,"data" => $variableData]
* @deprecated
*/
public function getItemVariableDataStatsFromDeliveryResult($resultIdentifier, $filter = null)
{
$numberOfResponseVariables = 0;
$numberOfCorrectResponseVariables = 0;
$numberOfInCorrectResponseVariables = 0;
$numberOfUnscoredResponseVariables = 0;
$numberOfOutcomeVariables = 0;
$variablesData = $this->getItemVariableDataFromDeliveryResult($resultIdentifier, $filter);
foreach ($variablesData as $itemVariables) {
foreach ($itemVariables['sortedVars'] as $key => $value) {
if ($key == \taoResultServer_models_classes_ResponseVariable::class) {
foreach ($value as $variable) {
$variable = array_shift($variable);
$numberOfResponseVariables++;
switch ($variable['isCorrect']) {
case 'correct':
$numberOfCorrectResponseVariables++;
break;
case 'incorrect':
$numberOfInCorrectResponseVariables++;
break;
case 'unscored':
$numberOfUnscoredResponseVariables++;
break;
default:
common_Logger::w('The value ' . $variable['isCorrect'] . ' is not a valid value');
break;
}
}
} else {
$numberOfOutcomeVariables++;
}
}
}
$stats = [
"nbResponses" => $numberOfResponseVariables,
"nbCorrectResponses" => $numberOfCorrectResponseVariables,
"nbIncorrectResponses" => $numberOfInCorrectResponseVariables,
"nbUnscoredResponses" => $numberOfUnscoredResponseVariables,
"data" => $variablesData,
];
return $stats;
}
/**
* prepare a data set as an associative array, service intended to populate gui controller
*
* @param string $resultIdentifier
* @param string $filter 'lastSubmitted', 'firstSubmitted'
*
* @return array
* @deprecated
*/
public function getItemVariableDataFromDeliveryResult($resultIdentifier, $filter)
{
$itemCallIds = $this->getItemResultsFromDeliveryResult($resultIdentifier);
$variablesByItem = [];
foreach ($itemCallIds as $itemCallId) {
$itemVariables = $this->getVariablesFromObjectResult($itemCallId);
$item = $this->getItemInfos($itemCallId, $itemVariables);
$itemIdentifier = $item['uri'];
$itemLabel = $item['label'];
$variablesByItem[$itemIdentifier]['itemModel'] = $item['itemModel'];
foreach ($itemVariables as $variable) {
//retrieve the type of the variable
$variableTemp = $variable[0]->variable;
$variableDescription = [];
$type = get_class($variableTemp);
$variableIdentifier = $variableTemp->getIdentifier();
$variableDescription["uri"] = $variable[0]->uri;
$variableDescription["var"] = $variableTemp;
if (method_exists($variableTemp, 'getCorrectResponse') && !is_null($variableTemp->getCorrectResponse())) {
if ($variableTemp->getCorrectResponse() >= 1) {
$variableDescription["isCorrect"] = "correct";
} else {
$variableDescription["isCorrect"] = "incorrect";
}
} else {
$variableDescription["isCorrect"] = "unscored";
}
$variablesByItem[$itemIdentifier]['sortedVars'][$type][$variableIdentifier][$variableTemp->getEpoch()] = $variableDescription;
$variablesByItem[$itemIdentifier]['label'] = $itemLabel;
}
}
//sort by epoch and filter
foreach ($variablesByItem as $itemIdentifier => $itemVariables) {
foreach ($itemVariables['sortedVars'] as $variableType => $variables) {
foreach ($variables as $variableIdentifier => $observation) {
uksort($variablesByItem[$itemIdentifier]['sortedVars'][$variableType][$variableIdentifier], "self::sortTimeStamps");
switch ($filter) {
case self::VARIABLES_FILTER_LAST_SUBMITTED:
{
$variablesByItem[$itemIdentifier]['sortedVars'][$variableType][$variableIdentifier] = [array_pop($variablesByItem[$itemIdentifier]['sortedVars'][$variableType][$variableIdentifier])];
break;
}
case self::VARIABLES_FILTER_FIRST_SUBMITTED:
{
$variablesByItem[$itemIdentifier]['sortedVars'][$variableType][$variableIdentifier] = [array_shift($variablesByItem[$itemIdentifier]['sortedVars'][$variableType][$variableIdentifier])];
break;
}
}
}
}
}
return $variablesByItem;
}
/**
*
* @param string $a epoch
* @param string $b epoch
*
* @return number
*/
public static function sortTimeStamps($a, $b)
{
[$usec, $sec] = explode(" ", $a);
$floata = ((float)$usec + (float)$sec);
[$usec, $sec] = explode(" ", $b);
$floatb = ((float)$usec + (float)$sec);
//the callback is expecting an int returned, for the case where the difference is of less than a second
//intval(round(floatval($b) - floatval($a),1, PHP_ROUND_HALF_EVEN));
if ((floatval($floata) - floatval($floatb)) > 0) {
return 1;
} elseif ((floatval($floata) - floatval($floatb)) < 0) {
return -1;
} else {
return 0;
}
}
/**
* return all variables linked to the delviery result and that are not linked to a particular itemResult
*
* @param string $resultIdentifier
* @param array $wantedTypes
*
* @return array
*/
public function getVariableDataFromDeliveryResult($resultIdentifier, $wantedTypes = [\taoResultServer_models_classes_ResponseVariable::class, \taoResultServer_models_classes_OutcomeVariable::class, \taoResultServer_models_classes_TraceVariable::class])
{
$testCallIds = $this->getTestsFromDeliveryResult($resultIdentifier);
return $this->extractTestVariables($this->getVariablesFromObjectResult($testCallIds), $wantedTypes);
}
public function extractTestVariables(array $variableObjects, array $wantedTypes, string $filter = self::VARIABLES_FILTER_ALL)
{
$variableObjects = array_filter($variableObjects, static function (array $variableObject) use ($wantedTypes) {
$variable = current($variableObject);
return $variable->callIdItem === null && in_array(get_class($variable->variable), $wantedTypes, true);
});
$variableObjects = array_map(static function (array $variableObject) {
return current($variableObject)->variable;
}, $variableObjects);
usort($variableObjects, static function (
Variable $a,
Variable $b
) use ($filter) {
if ($filter === self::VARIABLES_FILTER_LAST_SUBMITTED) {
return $b->getCreationTime() - $a->getCreationTime();
}
return $a->getCreationTime() - $b->getCreationTime();
});
if (in_array($filter, [self::VARIABLES_FILTER_FIRST_SUBMITTED, self::VARIABLES_FILTER_LAST_SUBMITTED], true)) {
$uniqueVariableIdentifiers = [];
$variableObjects = array_filter($variableObjects, static function (
Variable $variable
) use (&$uniqueVariableIdentifiers) {
if (in_array($variable->getIdentifier(), $uniqueVariableIdentifiers, true)) {
return false;
}
$uniqueVariableIdentifiers[] = $variable->getIdentifier();
return true;
});
}
return $variableObjects;
}
/**
* returns the test taker related to the delivery
*
* @param string $resultIdentifier
*
* @return User
*/
public function getTestTaker($resultIdentifier)
{
$testTaker = $this->getImplementation()->getTestTaker($resultIdentifier);
/** @var \tao_models_classes_UserService $userService */
$userService = $this->getServiceLocator()->get(\tao_models_classes_UserService::SERVICE_ID);
$user = $userService->getUserById($testTaker);
return $user;
}
/**
* Delete a delivery result
*
* @param string $resultIdentifier
*
* @return boolean
*/
public function deleteResult($resultIdentifier)
{
return $this->getImplementation()->deleteResult($resultIdentifier);
}
/**
* Return the file data associate to a variable
*
* @param $variableUri
*
* @return array file data
* @throws \core_kernel_persistence_Exception
*/
public function getVariableFile($variableUri)
{
//distinguish QTI file from other "file" base type
$baseType = $this->getVariableBaseType($variableUri);
switch ($baseType) {
case "file":
{
$value = $this->getVariableCandidateResponse($variableUri);
common_Logger::i(var_export(strlen($value), true));
$decodedFile = Datatypes::decodeFile($value);
common_Logger::i("FileName:");
common_Logger::i(var_export($decodedFile["name"], true));
common_Logger::i("Mime Type:");
common_Logger::i(var_export($decodedFile["mime"], true));
$file = [
"data" => $decodedFile["data"],
"mimetype" => "Content-type: " . $decodedFile["mime"],
"filename" => $decodedFile["name"]];
break;
}
default:
{ //legacy files
$file = [
"data" => $this->getVariableCandidateResponse($variableUri),
"mimetype" => "Content-type: text/xml",
"filename" => "trace.xml"];
}
}
return $file;
}
/**
* To be reviewed as it implies a dependency towards taoSubjects
*
* @param string $resultIdentifier
*
* @return array test taker properties values
*/
public function getTestTakerData($resultIdentifier)
{
$testTaker = $this->gettestTaker($resultIdentifier);
if (get_class($testTaker) == 'core_kernel_classes_Literal') {
return $testTaker;
} elseif (empty($testTaker)) {
return null;
} else {
$arrayOfProperties = [
OntologyRdfs::RDFS_LABEL,
GenerisRdf::PROPERTY_USER_LOGIN,
GenerisRdf::PROPERTY_USER_FIRSTNAME,
GenerisRdf::PROPERTY_USER_LASTNAME,
GenerisRdf::PROPERTY_USER_MAIL,
];
$propValues = [];
foreach ($arrayOfProperties as $property) {
$values = [];
foreach ($testTaker->getPropertyValues($property) as $value) {
$values[] = new \core_kernel_classes_Literal($value);
}
$propValues[$property] = $values;
}
}
return $propValues;
}
/**
*
* @param \core_kernel_classes_Resource $delivery
*
* @return taoResultServer_models_classes_ReadableResultStorage
* @throws common_exception_Error
*/
public function getReadableImplementation(\core_kernel_classes_Resource $delivery)
{
/** @var ResultServerService $service */
$service = $this->getServiceLocator()->get(ResultServerService::SERVICE_ID);
$resultStorage = $service->getResultStorage($delivery);
/** NoResultStorage it's not readable only writable */
if ($resultStorage instanceof NoResultStorage) {
throw NoResultStorageException::create();
}
if (!$resultStorage instanceof taoResultServer_models_classes_ReadableResultStorage) {
throw new \common_exception_Error('The results storage it is not readable');
}
return $resultStorage;
}
/**
* Get the array of column names indexed by their unique column id.
*
* @param \tao_models_classes_table_Column[] $columns
*
* @return array
*/
public function getColumnNames(array $columns)
{
return array_reduce($columns, function ($carry, \tao_models_classes_table_Column $column) {
/** @var ContextTypePropertyColumn|VariableColumn $column */
$carry[$this->getColumnId($column)] = $column->getLabel();
return $carry;
});
}
/**
* @param \tao_models_classes_table_Column|ContextTypePropertyColumn|VariableColumn $column
*
* @return string
*/
private function getColumnId(\tao_models_classes_table_Column $column)
{
if ($column instanceof ContextTypePropertyColumn) {
$id = $column->getProperty()->getUri() . '_' . $column->getContextType();
} elseif ($column instanceof TestCenterColumn) {
$id = $column->getProperty()->getUri();
} else {
$id = $column->getContextIdentifier() . '_' . $column->getIdentifier();
}
return $id;
}
/**
* @param core_kernel_classes_Resource $delivery
* @param array $storageOptions
* @param array $filters
*
* @throws common_Exception
* @throws common_exception_Error
*/
public function getResultsByDelivery(\core_kernel_classes_Resource $delivery, array $storageOptions = [], array $filters = [])
{
//The list of delivery Results matching the current selection filters
$this->setImplementation($this->getReadableImplementation($delivery));
return $this->findResultsByDeliveryAndFilters($delivery, $filters, $storageOptions);
}
/**
* @param string $result
*
* @return bool
*/
protected function shouldResultBeSkipped($result)
{
return false;
}
/**
* @param array $results
* @param $columns - columns to be exported
* @param $filter 'lastSubmitted' or 'firstSubmitted'
* @param array $filters
* @param int $offset
* @param int $limit
*
* @return array
* @throws common_Exception
* @throws common_exception_Error
*/
public function getCellsByResults(array $results, $columns, $filter, array $filters = [], $offset = 0, $limit = null)
{
$rows = [];
$dataProviderMap = $this->collectColumnDataProviderMap($columns);
if (!array_key_exists($offset, $results)) {
return null;
}
/** @var DeliveryExecution $result */
for ($i = $offset; $i < ($offset + $limit); $i++) {
if (!array_key_exists($i, $results)) {
break;
}
$result = $results[$i];
if ($this->shouldResultBeSkipped($result)) {
continue;
}
// initialize column data providers for single result
foreach ($dataProviderMap as $element) {
$element['instance']->prepare([$result], $element['columns']);
}
$cellData = [];
/** @var ContextTypePropertyColumn|VariableColumn $column */
foreach ($columns as $column) {
$cellKey = $this->getColumnId($column);
$cellData[$cellKey] = null;
if ($column instanceof TraceVariableColumn && count($column->getDataProvider()->getCache()) > 0) {
$cellData[$cellKey] = self::filterCellData($column->getDataProvider()->getValue(new core_kernel_classes_Resource($result), $column), self::VARIABLES_FILTER_TRACE);
} elseif (count($column->getDataProvider()->cache) > 0) {
// grade or response column values
$cellData[$cellKey] = self::filterCellData($column->getDataProvider()->getValue(new core_kernel_classes_Resource($result), $column), $filter);
continue;
} elseif ($column instanceof ContextTypePropertyColumn) {
// test taker or delivery property values
$resource = $column->isTestTakerType()
? $this->getTestTaker($result)
: $this->getDelivery($result);
$property = $column->getProperty();
if ($resource instanceof User) {
$property = $column->getProperty()->getUri();
}
$values = $resource->getPropertyValues($property);
$values = array_map(function ($value) use ($column) {
if (\common_Utils::isUri($value)) {
$value = (new core_kernel_classes_Resource($value))->getLabel();
} else {
$value = (string)$value;
}
if (in_array($column->getProperty()->getUri(), [DeliveryAssemblyService::PROPERTY_START, DeliveryAssemblyService::PROPERTY_END])) {
$value = tao_helpers_Date::displayeDate($value, tao_helpers_Date::FORMAT_VERBOSE);
}
return $value;
}, $values);
// if it's a guest test taker (it has no property values at all), let's display the uri as label
if ($column->isTestTakerType() && empty($values) && $column->getProperty()->getUri() == OntologyRdfs::RDFS_LABEL) {
switch (true) {
case $resource instanceof core_kernel_classes_Resource:
$values[] = $resource->getUri();
break;
case $resource instanceof User:
$values[] = $resource->getIdentifier();
break;
default:
throw new \Exception('Invalid type of resource property values.');
}
}
} elseif ($column instanceof TestCenterColumn) {
$property = $column->getProperty();
$testTaker = $this->getTestTaker($result);
$values = $testTaker->getPropertyValues($property);
$values = array_map(function ($value) use ($column, $result) {
$currentDelivery = $this->getDelivery($result);
return $column->getTestCenterLabel($value, $currentDelivery);
}, $values);
}
$cellData[$cellKey] = [self::filterCellData(implode(self::SEPARATOR, array_filter($values)), $filter)];
}
if ($this->filterData($cellData, $filters)) {
$this->convertDates($cellData);
$rows[] = [
'id' => $result,
'cell' => $cellData,
];
}
}
return $rows;
}
/**
* @param $columns
*
* @return array
*/
private function collectColumnDataProviderMap($columns)
{
$dataProviderMap = [];
foreach ($columns as $column) {
$dataProvider = $column->getDataProvider();
$found = false;
foreach ($dataProviderMap as $index => $element) {
if ($element['instance'] == $dataProvider) {
$dataProviderMap[$index]['columns'][] = $column;
$found = true;
}
}
if (!$found) {
$dataProviderMap[] = [
'instance' => $dataProvider,
'columns' => [$column],
];
}
}
return $dataProviderMap;
}
/**
* @param $data
*
* @throws common_Exception
*/
private function convertDates(&$data)
{
$sd = current($data[self::DELIVERY_EXECUTION_STARTED_AT]);
$data[self::DELIVERY_EXECUTION_STARTED_AT][0] = $sd ? tao_helpers_Date::displayeDate($sd) : '';
$ed = current($data[self::DELIVERY_EXECUTION_FINISHED_AT]);
$data[self::DELIVERY_EXECUTION_FINISHED_AT][0] = $ed ? tao_helpers_Date::displayeDate($ed) : '';
}
/**
* Check that data is apply to these filter params
*
* @param $row
* @param array $filters
*
* @return bool
*/
private function filterData($row, array $filters)
{
$matched = true;
if (count($filters) && count(array_intersect(self::PERIODS, array_keys($filters)))) {
$startDate = current($row[self::DELIVERY_EXECUTION_STARTED_AT]);
$startTime = $startDate ? tao_helpers_Date::getTimeStamp($startDate) : 0;
$endDate = current($row[self::DELIVERY_EXECUTION_FINISHED_AT]);
$endTime = $endDate ? tao_helpers_Date::getTimeStamp($endDate) : 0;
if ($matched && array_key_exists(self::FILTER_START_FROM, $filters) && $filters[self::FILTER_START_FROM]) {
$matched = $startTime >= $filters[self::FILTER_START_FROM];
}
if ($matched && array_key_exists(self::FILTER_START_TO, $filters) && $filters[self::FILTER_START_TO]) {
$matched = $startTime <= $filters[self::FILTER_START_TO];
}
if ($matched && array_key_exists(self::FILTER_END_FROM, $filters) && $filters[self::FILTER_END_FROM]) {
$matched = $endTime >= $filters[self::FILTER_END_FROM];
}
if ($matched && array_key_exists(self::FILTER_END_TO, $filters) && $filters[self::FILTER_END_TO]) {
$matched = $endTime <= $filters[self::FILTER_END_TO];
}
}
return $matched;
}
/**
* @param array $deliveryUris
*
* @return int
* @throws common_exception_Error
*/
public function countResultByDelivery(array $deliveryUris)
{
return $this->getImplementation()->countResultByDelivery($deliveryUris);
}
/**
* @param array|string $resultsIds
*
* @return mixed
* @throws common_exception_Error
*/
protected function getResultsVariables($resultsIds)
{
return $this->getImplementation()->getDeliveryVariables($resultsIds);
}
/**
* Retrieve the different variables columns pertainign to the current selection of results
* Implementation note : it nalyses all the data collected to identify the different response variables submitted
* by the items in the context of activities
*/
public function getVariableColumns($delivery, $variableClassUri, array $filters = [], array $storageOptions = [])
{
$columns = [];
/** @var ResultServiceWrapper $resultServiceWrapper */
$resultServiceWrapper = $this->getServiceLocator()->get(ResultServiceWrapper::SERVICE_ID);
$this->setImplementation($this->getReadableImplementation($delivery));
//The list of delivery Results matching the current selection filters
$resultsIds = $this->findResultsByDeliveryAndFilters($delivery, $filters, $storageOptions);
//retrieveing all individual response variables referring to the selected delivery results
$itemIndex = $this->getItemIndexer($delivery);
//retrieving The list of the variables identifiers per activities defintions as observed
$variableTypes = [];
$resultLanguage = $this->getResultLanguage();
foreach (array_chunk($resultsIds, $resultServiceWrapper->getOption(ResultServiceWrapper::RESULT_COLUMNS_CHUNK_SIZE_OPTION)) as $resultsIdsItem) {
$selectedVariables = $this->getResultsVariables($resultsIdsItem);
foreach ($selectedVariables as $variable) {
$variable = $variable[0];
if ($this->isResultVariable($variable, $variableClassUri)) {
//variableIdentifier
$variableIdentifier = $variable->variable->getIdentifier();
if (!is_null($variable->item)) {
$uri = $variable->item;
$contextIdentifierLabel = $itemIndex->getItemValue($uri, $resultLanguage, 'label');
} else {
$uri = $variable->test;
$testData = $this->getTestMetadata($delivery, $variable->test);
$contextIdentifierLabel = $testData->getLabel();
}
$columnType = $this->defineTypeColumn($variable->variable);
$variableTypes[$uri . $variableIdentifier] = [
"contextLabel" => $contextIdentifierLabel,
"contextId" => $uri,
"variableIdentifier" => $variableIdentifier,
"columnType" => $columnType
];
if ($variable->variable instanceof \taoResultServer_models_classes_ResponseVariable
&& $variable->variable->getCorrectResponse() !== null) {
$variableTypes[$uri . $variableIdentifier . '_is_correct'] = [
"contextLabel" => $contextIdentifierLabel,
"contextId" => $uri,
"variableIdentifier" => $variableIdentifier . '_is_correct',
"columnType" => Variable::TYPE_VARIABLE_IDENTIFIER
];
}
}
}
}
foreach ($variableTypes as $variableType) {
switch ($variableClassUri) {
case \taoResultServer_models_classes_OutcomeVariable::class:
$columns[] = new GradeColumn($variableType["contextId"], $variableType["contextLabel"], $variableType["variableIdentifier"], $variableType["columnType"]);
break;
case \taoResultServer_models_classes_ResponseVariable::class:
$columns[] = new ResponseColumn($variableType["contextId"], $variableType["contextLabel"], $variableType["variableIdentifier"], $variableType["columnType"]);
break;
default:
$columns[] = new ResponseColumn($variableType["contextId"], $variableType["contextLabel"], $variableType["variableIdentifier"], $variableType["columnType"]);
}
}
$arr = [];
foreach ($columns as $column) {
$arr[] = $column->toArray();
}
return $arr;
}
/**
* Check if provided variable is a result variable.
*
* @param $variable
* @param $variableClassUri
*
* @return bool
*/
private function isResultVariable($variable, $variableClassUri)
{
$responseVariableClass = \taoResultServer_models_classes_ResponseVariable::class;
$outcomeVariableClass = \taoResultServer_models_classes_OutcomeVariable::class;
$class = isset($variable->class) ? $variable->class : get_class($variable->variable);
return (null != $variable->item || null != $variable->test)
&& (
$class == $outcomeVariableClass
&& $variableClassUri == $outcomeVariableClass
) || (
$class == $responseVariableClass
&& $variableClassUri == $responseVariableClass
);
}
/**
* @param Variable $variable
* @return string|null
*/
private function defineTypeColumn(Variable $variable)
{
$stringColumns = [
'SCORE',
'MAXSCORE',
'numAttempts',
'duration'
];
if (in_array($variable->getIdentifier(), $stringColumns) ||
($variable instanceof \taoResultServer_models_classes_ResponseVariable && $variable->getCorrectResponse() !== null)) {
return Variable::TYPE_VARIABLE_IDENTIFIER;
}
return $variable->getBaseType();
}
/**
* Sort the list of variables by filters
*
* List of variables contains the response for an interaction.
* Each attempts is an entry in $observationList
*
* 3 allowed filters: firstSubmitted, lastSubmitted, all, trace
*
* @param array $observationsList The list of variable values
* @param string $filterData The filter
* @param string $allDelimiter $delimiter to separate values in "all" filter context
*
* @return array
*/
public static function filterCellData($observationsList, $filterData, $allDelimiter = '|')
{
//if the cell content is not an array with multiple entries, do not filter
if (!is_array($observationsList)) {
return $observationsList;
}
// Sort by TimeStamps
uksort($observationsList, "oat\\taoOutcomeUi\\model\\ResultsService::sortTimeStamps");
// Extract the value to make this array flat
$observationsList = array_map(function ($obs) {
return $obs[0];
}, $observationsList);
switch ($filterData) {
case self::VARIABLES_FILTER_LAST_SUBMITTED:
$value = array_pop($observationsList);
break;
case self::VARIABLES_FILTER_FIRST_SUBMITTED:
$value = array_shift($observationsList);
break;
case self::VARIABLES_FILTER_TRACE:
$value = json_encode(array_map(function ($value) {
return json_decode($value, true);
}, array_values($observationsList)));
break;
case self::VARIABLES_FILTER_ALL:
default:
$value = implode($allDelimiter, $observationsList);
break;
}
return [$value];
}
/**
* @param $delivery
*
* @return ItemCompilerIndex
* @throws common_exception_Error
*/
private function getItemIndexer($delivery)
{
$deliveryUri = $delivery->getUri();
if (!array_key_exists($deliveryUri, $this->indexerCache)) {
$directory = $this->getPrivateDirectory($delivery);
$indexer = $this->getDecompiledIndexer($directory);
$this->indexerCache[$deliveryUri] = $indexer;
}
$indexer = $this->indexerCache[$deliveryUri];
return $indexer;
}
/**
* @param $delivery
* @param $testUri
*
* @return ResourceCompiledMetadataHelper
*/
private function getTestMetadata(core_kernel_classes_Resource $delivery, $testUri)
{
if (isset($this->testMetadataCache[$testUri])) {
return $this->testMetadataCache[$testUri];
}
$compiledMetadataHelper = new ResourceCompiledMetadataHelper();
try {
$directory = $this->getPrivateDirectory($delivery);
$testMetadata = $this->loadTestMetadata($directory, $testUri);
if (!empty($testMetadata)) {
$compiledMetadataHelper->unserialize($testMetadata);
}
} catch (\Exception $e) {
\common_Logger::d('Ignoring data not found exception for Test Metadata');
}
$this->testMetadataCache[$testUri] = $compiledMetadataHelper;
return $this->testMetadataCache[$testUri];
}
/**
* Load test metadata from file. For deliveries without compiled file try to compile test metadata.
*
* @param tao_models_classes_service_StorageDirectory $directory
* @param string $testUri
*
* @return false|string
* @throws \FileNotFoundException
* @throws common_Exception
*/
private function loadTestMetadata(tao_models_classes_service_StorageDirectory $directory, $testUri)
{
try {
$testMetadata = $this->loadTestMetadataFromFile($directory);
} catch (FileNotFoundException $e) {
\common_Logger::d('Compiled test metadata file not found. Try to compile a new file.');
$this->compileTestMetadata($directory, $testUri);
$testMetadata = $this->loadTestMetadataFromFile($directory);
}
return $testMetadata;
}
/**
* Get teast metadata from file.
*
* @param tao_models_classes_service_StorageDirectory $directory
*
* @return false|string
*/
private function loadTestMetadataFromFile(tao_models_classes_service_StorageDirectory $directory)
{
return $directory->read(taoQtiTest_models_classes_QtiTestService::TEST_COMPILED_METADATA_FILENAME);
}
/**
* Compile test metadata and store into file.
* Added for backward compatibility for deliveries without compiled test metadata.
*
* @param tao_models_classes_service_StorageDirectory $directory
* @param $testUri
*
* @throws \FileNotFoundException
* @throws common_Exception
*/
private function compileTestMetadata(tao_models_classes_service_StorageDirectory $directory, $testUri)
{
$resource = $this->getResource($testUri);
/** @var ResourceMetadataCompilerInterface $resourceMetadataCompiler */
$resourceMetadataCompiler = $this->getServiceLocator()->get(ResourceJsonMetadataCompiler::SERVICE_ID);
$metadata = $resourceMetadataCompiler->compile($resource);
$directory->write(taoQtiTest_models_classes_QtiTestService::TEST_COMPILED_METADATA_FILENAME, json_encode($metadata));
}
/**
* @param string $directory
*
* @return QtiTestCompilerIndex
*/
private function getDecompiledIndexer(tao_models_classes_service_StorageDirectory $directory)
{
$itemIndex = new QtiTestCompilerIndex();
try {
$data = $directory->read(taoQtiTest_models_classes_QtiTestService::TEST_COMPILED_INDEX);
if ($data) {
$itemIndex->unserialize($data);
}
} catch (\Exception $e) {
\common_Logger::d('Ignoring file not found exception for Items Index');
}
return $itemIndex;
}
/**
* Should be changed if real result language would matter
*
* @return string
*/
private function getResultLanguage()
{
return DEFAULT_LANG;
}
/**
* @param $executionUri
*
* @return core_kernel_classes_Resource
* @throws \common_exception_NotFound
*/
private function getDeliveryByResultId($executionUri)
{
if (!array_key_exists($executionUri, $this->executionCache)) {
/** @var DeliveryExecution $execution */
$execution = $this->getServiceManager()->get(ServiceProxy::class)->getDeliveryExecution($executionUri);
$delivery = $execution->getDelivery();
$this->executionCache[$executionUri] = $delivery;
}
$delivery = $this->executionCache[$executionUri];
return $delivery;
}
/**
* @param $delivery
*
* @return array
* @throws common_exception_Error
*/
private function getDirectoryIds($delivery)
{
$runtime = $this->getServiceLocator()->get(RuntimeService::SERVICE_ID)->getRuntime($delivery);
$inputParameters = \tao_models_classes_service_ServiceCallHelper::getInputValues($runtime, []);
$directoryIds = explode('|', $inputParameters['QtiTestCompilation']);
return $directoryIds;
}
/**
* @param $delivery
*
* @return tao_models_classes_service_StorageDirectory
* @throws common_exception_Error
*/
private function getPrivateDirectory($delivery)
{
$directoryIds = $this->getDirectoryIds($delivery);
$fileStorage = \tao_models_classes_service_FileStorage::singleton();
return $fileStorage->getDirectoryById($directoryIds[0]);
}
/**
* @return mixed
*/
public function getServiceLocator()
{
if (!$this->serviceLocator) {
$this->setServiceLocator(ServiceManager::getServiceManager());
}
return $this->serviceLocator;
}
/**
* @param core_kernel_classes_Resource $delivery
* @param array $filters
* @param array $storageOptions
*
* @return array
* @throws common_exception_Error
*/
protected function findResultsByDeliveryAndFilters($delivery, array $filters = [], array $storageOptions = [])
{
$results = [];
foreach ($this->getImplementation()->getResultByDelivery([$delivery->getUri()], $storageOptions) as $result) {
$results[] = $result['deliveryResultIdentifier'];
}
return $results;
}
}