From b6389b549adefbdbd5441e39b6514e7ed1f0e566 Mon Sep 17 00:00:00 2001
From: Marcus Eibrink-Lunzenauer <lunzenauer@elan-ev.de>
Date: Fri, 21 Jan 2022 10:10:40 +0000
Subject: [PATCH] Courseware-Fortschrittseite beschleunigen (SORMless edition)

---
 app/controllers/course/courseware.php | 47 +++++++++++++++++----------
 lib/models/Courseware/Instance.php    | 35 ++++++++++++++++----
 2 files changed, 58 insertions(+), 24 deletions(-)

diff --git a/app/controllers/course/courseware.php b/app/controllers/course/courseware.php
index c4aae64c745..1d93a0a7516 100755
--- a/app/controllers/course/courseware.php
+++ b/app/controllers/course/courseware.php
@@ -131,7 +131,7 @@ class Course_CoursewareController extends AuthenticatedController
         return array_combine(array_column($elements, 'id'), $elements);
     }
 
-    private function computeChildrenOf(iterable $elements): iterable
+    private function computeChildrenOf(iterable &$elements): iterable
     {
         $childrenOf = [];
         foreach ($elements as $elementId => $element) {
@@ -149,19 +149,28 @@ class Course_CoursewareController extends AuthenticatedController
     private function computeSelfProgresses(
         Instance $instance,
         User $user,
-        iterable $elements,
+        iterable &$elements,
         bool $showProgressForAllParticipants
     ): iterable {
         $progress = [];
         /** @var \Course $course */
         $course = $instance->getRange();
-        $allBlocks = $instance->findAllBlocksGroupedByStructuralElementId();
+        $allBlockIds = $instance->findAllBlocksGroupedByStructuralElementId(function ($row) {
+            return $row['id'];
+        });
         $courseMemberIds = $showProgressForAllParticipants
             ? array_column($course->getMembersWithStatus('autor'), 'user_id')
             : [$user->getId()];
-        $userProgresses = UserProgress::findBySQL('user_id IN (?)', [$courseMemberIds]);
+
+        $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($allBlocks, $elementId, $userProgresses, $courseMemberIds);
+            $selfProgress = $this->getSelfProgresses($allBlockIds, $elementId, $userProgresses, $courseMemberIds);
             $progress[$elementId] = [
                 'self' => $selfProgress['counter'] ? $selfProgress['progress'] / $selfProgress['counter'] : 1,
             ];
@@ -171,31 +180,35 @@ class Course_CoursewareController extends AuthenticatedController
     }
 
     private function getSelfProgresses(
-        array $allBlocks,
+        array &$allBlockIds,
         string $elementId,
-        array $userProgresses,
-        array $courseMemberIds
+        array &$userProgresses,
+        array &$courseMemberIds
     ): array {
-        $blks = $allBlocks[$elementId] ?: [];
+        $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 = array_filter($userProgresses, function ($progress) use ($blk, $courseMemberIds) {
-                return $progress->block_id === $blk->getId() && in_array($progress->user_id, $courseMemberIds);
-            });
-            $usersProgress = count($progresses) ? array_sum(array_column($progresses, 'grade')) : 0;
-
+            $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
+    private function computeCumulativeProgresses(Instance $instance, iterable &$elements, iterable &$progress): iterable
     {
         $childrenOf = $this->computeChildrenOf($elements);
 
@@ -225,7 +238,7 @@ class Course_CoursewareController extends AuthenticatedController
         return $progress;
     }
 
-    private function prepareProgressData(iterable $elements, iterable $progress): iterable
+    private function prepareProgressData(iterable &$elements, iterable &$progress): iterable
     {
         $data = [];
         foreach ($elements as $elementId => $element) {
@@ -246,7 +259,7 @@ class Course_CoursewareController extends AuthenticatedController
         return $data;
     }
 
-    private function getChapterCounter(array $chapters): array
+    private function getChapterCounter(array &$chapters): array
     {
         $finished = 0;
         $started = 0;
diff --git a/lib/models/Courseware/Instance.php b/lib/models/Courseware/Instance.php
index cc9189c6a8e..c823a4edbde 100755
--- a/lib/models/Courseware/Instance.php
+++ b/lib/models/Courseware/Instance.php
@@ -37,12 +37,19 @@ class Instance
         $range->getConfiguration()->delete('COURSEWARE_SEQUENTIAL_PROGRESSION');
         $range->getConfiguration()->delete('COURSEWARE_EDITING_PERMISSION');
 
-        $last_element_configs = \ConfigValue::findBySQL('field = ? AND value LIKE ?', ['COURSEWARE_LAST_ELEMENT', '%'.$range->getRangeId().'%']);
+        $last_element_configs = \ConfigValue::findBySQL('field = ? AND value LIKE ?', [
+            'COURSEWARE_LAST_ELEMENT',
+            '%' . $range->getRangeId() . '%',
+        ]);
         foreach ($last_element_configs as $config) {
             $arr = json_decode($config->value, true);
-            $arr = array_filter($arr, function ($key) use ($range) {
-                return $key !== $range->id;
-            }, ARRAY_FILTER_USE_KEY);
+            $arr = array_filter(
+                $arr,
+                function ($key) use ($range) {
+                    return $key !== $range->id;
+                },
+                ARRAY_FILTER_USE_KEY
+            );
             \UserConfig::get($config->range_id)->unsetValue('COURSEWARE_LAST_ELEMENT');
             \UserConfig::get($config->range_id)->store('COURSEWARE_LAST_ELEMENT', $arr);
         }
@@ -270,8 +277,23 @@ class Instance
         return $data;
     }
 
-    public function findAllBlocksGroupedByStructuralElementId(): iterable
+    /**
+     * Find all blocks of this instance and group them by their structural element's ID.
+     * You may specify your own `$formatter` instead of the default one which stores the blocks as instances of \Courseware\Block.
+     *
+     * @param ?callable(array $row): mixed $formatter Provide your own callable if you need something else instead of
+     *                                                full-blown instances of \Courseware\Block.
+     * @return iterable all the (optionally formatted) blocks grouped by the IDs of the structural element containing
+     *                  that block.
+     */
+    public function findAllBlocksGroupedByStructuralElementId(callable $formatter = null): iterable
     {
+        if (!$formatter) {
+            $formatter = function ($row) {
+                return \Courseware\Block::build($row, false);
+            };
+        }
+
         $sql = 'SELECT se.id AS structural_element_id, b.*
                 FROM cw_structural_elements se
                 JOIN cw_containers c ON se.id = c.structural_element_id
@@ -286,11 +308,10 @@ class Instance
             $structuralElementId = $row['structural_element_id'];
             unset($row['structural_element_id']);
 
-            $block = \Courseware\Block::build($row, false);
             if (!isset($data[$structuralElementId])) {
                 $data[$structuralElementId] = [];
             }
-            $data[$structuralElementId][] = $block;
+            $data[$structuralElementId][] = $formatter($row);
         }
 
         return $data;
-- 
GitLab