Skip to content
Snippets Groups Projects
Select Git revision
  • cd956de6175a5851fb71caf89097958f718af3cd
  • main default protected
  • step-3263
  • feature/plugins-cli
  • feature/vite
  • step-2484-peerreview
  • biest/issue-5051
  • tests/simplify-jsonapi-tests
  • fix/typo-in-1a70031
  • feature/broadcasting
  • database-seeders-and-factories
  • feature/peer-review-2
  • feature-feedback-jsonapi
  • feature/peerreview
  • feature/balloon-plus
  • feature/stock-images-unsplash
  • tic-2588
  • 5.0
  • 5.2
  • biest/unlock-blocks
  • biest-1514
21 results

InstituteSelectWidget.class.php

Blame
  • 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.
    courseware.php 11.24 KiB
    <?php
    
    use Courseware\StructuralElement;
    use Courseware\Instance;
    use Courseware\UserProgress;
    
    /**
     * @property ?string $entry_element_id
     * @property int $last_visitdate
     * @property mixed $course_id
     * @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(),
                ]);
            }
    
            // load courseware for course
            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(),
                ]);
            }
            $this->setIndexSidebar();
        }
    
        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');
            $this->setDashboardSidebar();
        }
    
        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');
            }
        }
    
        public function pdf_export_action($element_id, $with_children)
        {
            $element = \Courseware\StructuralElement::findOneById($element_id);
            $user = User::find($GLOBALS['user']->id);
            $this->render_pdf($element->pdfExport($user, $with_children), trim($element->title).'.pdf');
        }
    
        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');
    
            $exports = new TemplateWidget(
                _('Export '),
                $this->get_template_factory()->open('course/courseware/export_widget')
            );
            $sidebar->addWidget($exports)->addLayoutCSSClass('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
        ): array {
            $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());
    
            return $progress;
        }
    
        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;
        }
    
        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) {
                        $ahead += 1;
                    }
                    if ($chapter['progress']['self'] > 0 && $chapter['progress']['self'] < 100) {
                        $started += 1;
                    }
                    if ($chapter['progress']['self'] == 100) {
                        $finished += 1;
                    }
                }
            }
    
            return [
                'started' => $started,
                'finished' => $finished,
                'ahead' => $ahead,
            ];
        }
    }