Newer
Older
<?php
use Courseware\StructuralElement;
use Courseware\Instance;
/**
* @property ?string $entry_element_id
* @property int $last_visitdate
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
46
* @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
$instance = StructuralElement::createEmptyCourseware(Context::getId(), 'course');
$struct = $instance->getRoot();
}
$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->licenses = [];
$sorm_licenses = License::findBySQL('1 ORDER BY name ASC');
foreach ($sorm_licenses as $license) {
array_push($this->licenses, $license->toArray());
}
$this->licenses = json_encode($this->licenses);
// Make sure struct has value., to evaluate the export (edit) capability.
if (!isset($struct)) {
$struct = StructuralElement::findOneBySQL("id = ? AND range_id = ? AND range_type = 'course'", [
$this->entry_element_id,
Context::getId(),
]);
}
}
public function dashboard_action(): void
{
global $perm, $user;
$this->is_teacher = $perm->have_studip_perm('tutor', Context::getId(), $user->id);
$this->courseware_progress_data = $this->getProgressData($this->is_teacher);
$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');
}
}
{
$element = \Courseware\StructuralElement::findOneById($element_id);
$user = User::find($GLOBALS['user']->id);
$this->render_pdf($element->pdfExport($user, $with_children), trim($element->title).'.pdf');
{
$sidebar = Sidebar::Get();
$sidebar->addWidget(new VueWidget('courseware-action-widget'));
$views = new TemplateWidget(
_('Suche'),
$this->get_template_factory()->open('course/courseware/search_widget')
);
$sidebar->addWidget($views)->addLayoutCSSClass('courseware-search-widget');
$sidebar->addWidget(new VueWidget('courseware-view-widget'));
$sidebar->addWidget(new VueWidget('courseware-export-widget'));
}
private function setDashboardSidebar(): void
{
$sidebar = Sidebar::Get();
$views = new TemplateWidget(
_('Ansichten'),
$this->get_template_factory()->open('course/courseware/dashboard_view_widget')
);
$sidebar->addWidget($views)->addLayoutCSSClass('courseware-dashboard-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['grade'] : 0;
$data['progress'] += $usersCounter ? $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());
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) {