Select Git revision
Authority.php
Forked from
Stud.IP / Stud.IP
Source project has a limited visibility.
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
UserProgressesOfUnitsShow.php 6.42 KiB
<?php
namespace JsonApi\Routes\Courseware;
use JsonApi\NonJsonApiController;
use Courseware\Instance;
use Courseware\StructuralElement;
use Courseware\Unit;
use Courseware\UserProgress;
use JsonApi\Errors\AuthorizationFailedException;
use JsonApi\Errors\BadRequestException;
use JsonApi\Errors\RecordNotFoundException;
use JsonApi\Errors\UnprocessableEntityException;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
/**
* Displays the progress of a user for a unit
*
* @author Ron Lucke <lucke@elan-ev.de>
* @license GPL2 or any later version
*
* @since Stud.IP 5.3
*/
class UserProgressesOfUnitsShow extends NonJsonApiController
{
public function __invoke(Request $request, Response $response, array $args)
{
$user = $this->getUser($request);
$unit = Unit::find($args['id']);
if (!$unit) {
throw new RecordNotFoundException();
}
$root = $unit->structural_element;
if (!$GLOBALS['perm']->have_studip_perm('autor', $root->range_id) || !$unit->canRead($user)) {
throw new AuthorizationFailedException();
}
$instance = new Instance($root);
$isTeacher = $GLOBALS['perm']->have_studip_perm('tutor', $root->range_id);
$elements = $this->findElements($instance, $user);
$progress = $this->computeSelfProgresses($instance, $user, $elements, $isTeacher);
$progress = $this->computeCumulativeProgresses($instance, $elements, $progress);
$progresses = $this->prepareProgressData($elements, $progress);
$response = $response->withHeader('Content-Type', 'application/json');
$response->getBody()->write((string) json_encode($progresses));
return $response;
}
private function findElements(Instance $instance, \User $user): iterable
{
$elements = $instance->getRoot()->findDescendants($user);
$elements[] = $instance->getRoot();
return array_combine(array_column($elements, 'id'), $elements);
}
private function computeSelfProgresses(
Instance $instance,
\User $user,
iterable &$elements,
bool $showProgressForAllParticipants
): iterable
{
$progress = [];
/** @var \Course $course */
$course = $instance->getRange();
$allBlockIds = $instance->findAllBlocksGroupedByStructuralElementId(function ($row) {
return $row['id'];
});
$courseMemberIds = $showProgressForAllParticipants
? array_column($course->getMembersWithStatus('autor'), 'user_id')
: [$user->getId()];
$sql = "SELECT block_id, COUNT(grade) AS count, SUM(grade) AS grade
FROM cw_user_progresses
WHERE block_id IN (?) AND user_id IN (?)
GROUP BY block_id";
$userProgresses = \DBManager::get()->fetchGrouped($sql, [$allBlockIds, $courseMemberIds]);
foreach ($elements as $elementId => $element) {
$selfProgress = $this->getSelfProgresses($allBlockIds, $elementId, $userProgresses, $courseMemberIds);
$progress[$elementId] = [
'self' => $selfProgress['counter'] ? $selfProgress['progress'] / $selfProgress['counter'] : 1,
];
}
return $progress;
}
private function getSelfProgresses(
array $allBlockIds,
string $elementId,
array $userProgresses,
array $courseMemberIds
): array {
$blks = $allBlockIds[$elementId] ?? [];
if (count($blks) === 0) {
return [
'counter' => 0,
'progress' => 1,
];
}
$data = [
'counter' => count($blks),
'progress' => 0,
];
$usersCounter = count($courseMemberIds);
foreach ($blks as $blk) {
if (isset($userProgresses[$blk])) {
$progresses = $userProgresses[$blk];
$usersProgress = $progresses['count'] ? (float) $progresses['grade'] : 0;
$data['progress'] += $usersCounter > 0 ? $usersProgress / $usersCounter : 0;
}
}
return $data;
}
private function computeCumulativeProgresses(Instance $instance, iterable &$elements, iterable &$progress): iterable
{
$childrenOf = $this->computeChildrenOf($elements);
// compute `cumulative` of each element
$visitor = function (&$progress, $element) use (&$childrenOf, &$elements, &$visitor) {
$elementId = $element->getId();
$numberOfNodes = 0;
$cumulative = 0;
// visit children first
if (isset($childrenOf[$elementId])) {
foreach ($childrenOf[$elementId] as $childId) {
$visitor($progress, $elements[$childId]);
$numberOfNodes += $progress[$childId]['numberOfNodes'];
$cumulative += $progress[$childId]['cumulative'];
}
}
$progress[$elementId]['cumulative'] = $cumulative + $progress[$elementId]['self'];
$progress[$elementId]['numberOfNodes'] = $numberOfNodes + 1;
return $progress;
};
$visitor($progress, $instance->getRoot());
return $progress;
}
private function computeChildrenOf(iterable &$elements): iterable
{
$childrenOf = [];
foreach ($elements as $elementId => $element) {
if ($element['parent_id']) {
if (!isset($childrenOf[$element['parent_id']])) {
$childrenOf[$element['parent_id']] = [];
}
$childrenOf[$element['parent_id']][] = $elementId;
}
}
return $childrenOf;
}
private function prepareProgressData(iterable &$elements, iterable &$progress): iterable
{
$data = [];
foreach ($elements as $elementId => $element) {
$elementProgress = $progress[$elementId];
$cumulative = $elementProgress['cumulative'] / $elementProgress['numberOfNodes'];
$data[$elementId] = [
'id' => (int) $elementId,
'parent_id' => (int) $element['parent_id'],
'name' => $element['title'],
'progress' => [
'cumulative' => round($cumulative, 2) * 100,
'self' => round($elementProgress['self'], 2) * 100,
],
];
}
return $data;
}
}