Newer
Older
<?php
use Courseware\StructuralElement;
use Courseware\Instance;
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/**
* @property ?string $entry_element_id
* @property int $last_visitdate
* @property mixed $courseware_progress_data
* @property mixed $courseware_chapter_counter
*/
class Course_CoursewareController extends AuthenticatedController
{
protected $_autobind = true;
public function before_filter(&$action, &$args)
{
parent::before_filter($action, $args);
PageLayout::setTitle(_('Courseware'));
PageLayout::setHelpKeyword('Basis.Courseware');
checkObject();
if (!Context::isCourse()) {
throw new CheckObjectException(_('Es wurde keine passende Veranstaltung gefunden.'));
}
$this->studip_module = checkObjectModule('CoursewareModule', true);
object_set_visit_module($this->studip_module->getPluginId());
$this->last_visitdate = object_get_visit(Context::getId(), $this->studip_module->getPluginId());
}
public function index_action()
{
/** @var array<mixed> $last */
$last = UserConfig::get($GLOBALS['user']->id)->getValue('COURSEWARE_LAST_ELEMENT');
if (isset($last[Context::getId()])) {
$this->entry_element_id = $last[Context::getId()];
/** @var ?StructuralElement $struct */
$struct = StructuralElement::findOneBySQL("id = ? AND range_id = ? AND range_type = 'course'", [
$this->entry_element_id,
Context::getId(),
]);
}
if (!$this->entry_element_id || !$struct || !$struct->canRead($GLOBALS['user'])) {
$course = Course::find(Context::getId());
if (!$course->courseware) {
// create initial courseware dataset
StructuralElement::createEmptyCourseware(Context::getId(), 'course');
}
$this->entry_element_id = $course->courseware->id;
}
$last[Context::getId()] = $this->entry_element_id;
UserConfig::get($GLOBALS['user']->id)->store('COURSEWARE_LAST_ELEMENT', $last);
Navigation::activateItem('course/courseware/content');
$this->setIndexSidebar();
$this->licenses = [];
$sorm_licenses = License::findBySQL('1 ORDER BY name ASC');
foreach ($sorm_licenses as $license) {
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
array_push($this->licenses, $license->toArray());
}
$this->licenses = json_encode($this->licenses);
}
public function dashboard_action(): void
{
global $perm, $user;
$course_progress = $perm->have_studip_perm('dozent', Context::getId(), $user->id);
$this->courseware_progress_data = $this->getProgressData($course_progress);
$this->courseware_chapter_counter = $this->getChapterCounter($this->courseware_progress_data);
Navigation::activateItem('course/courseware/dashboard');
}
public function manager_action(): void
{
$courseId = Context::getId();
$element = StructuralElement::getCoursewareCourse($courseId);
$instance = new Instance($element);
if (!$GLOBALS['perm']->have_studip_perm($instance->getEditingPermissionLevel(), $courseId)) {
$this->redirect('course/courseware/index');
} else {
Navigation::activateItem('course/courseware/manager');
}
}
private function setIndexSidebar(): void
{
$sidebar = Sidebar::Get();
$actions = new TemplateWidget(
_('Aktionen'),
$this->get_template_factory()->open('course/courseware/action_widget')
);
$sidebar->addWidget($actions)->addLayoutCSSClass('courseware-action-widget');
$views = new TemplateWidget(
_('Ansichten'),
$this->get_template_factory()->open('course/courseware/view_widget')
);
$sidebar->addWidget($views)->addLayoutCSSClass('courseware-view-widget');
}
private function getProgressData(bool $showProgressForAllParticipants = false): iterable
/** @var ?\Course $course */
$course = Context::get();
if (!$course || !$course->courseware) {
return [];
}
$instance = new Instance($course->courseware);
$user = \User::findCurrent();
$elements = $this->findElements($instance, $user);
$progress = $this->computeSelfProgresses($instance, $user, $elements, $showProgressForAllParticipants);
$progress = $this->computeCumulativeProgresses($instance, $elements, $progress);
return $this->prepareProgressData($elements, $progress);
}
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 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 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
$blks = $allBlockIds[$elementId] ?: [];
if (!count($blks)) {
return [
'counter' => 0,
'progress' => 1,
];
}
$data = [
'counter' => count($blks),
'progress' => 0,
];
$usersCounter = count($courseMemberIds);
foreach ($blks as $blk) {
$progresses = $userProgresses[$blk];
$usersProgress = $progresses['count'] ? (float) $progresses['sum'] : 0;
$data['progress'] += $usersProgress / $usersCounter;
}
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());
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,
],
];
private function getChapterCounter(array &$chapters): array
{
$finished = 0;
$started = 0;
$ahead = 0;
foreach ($chapters as $chapter) {
if ($chapter['parent_id'] != null) {
if ($chapter['progress']['self'] == 0) {
if ($chapter['progress']['self'] > 0 && $chapter['progress']['self'] < 100) {
if ($chapter['progress']['self'] == 100) {