Passed
Pull Request — master (#7168)
by
unknown
09:56
created

UserSessionSubscriptionsStateProvider::provide()   B

Complexity

Conditions 10
Paths 22

Size

Total Lines 59
Code Lines 33

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 10
eloc 33
nc 22
nop 3
dl 0
loc 59
rs 7.6666
c 2
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
/* For licensing terms, see /license.txt */
6
7
namespace Chamilo\CoreBundle\State;
8
9
use ApiPlatform\Doctrine\Orm\Extension\PaginationExtension;
10
use ApiPlatform\Doctrine\Orm\Paginator;
11
use ApiPlatform\Doctrine\Orm\Util\QueryNameGenerator;
12
use ApiPlatform\Metadata\Operation;
13
use ApiPlatform\State\ProviderInterface;
14
use Chamilo\CoreBundle\Entity\Session;
15
use Chamilo\CoreBundle\Helpers\AccessUrlHelper;
16
use Chamilo\CoreBundle\Helpers\UserHelper;
17
use Chamilo\CoreBundle\Repository\Node\UserRepository;
18
use Chamilo\CoreBundle\Repository\SessionRepository;
19
use Exception;
20
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
21
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
22
23
/**
24
 * @template-implements ProviderInterface<Session>
25
 */
26
class UserSessionSubscriptionsStateProvider implements ProviderInterface
27
{
28
    public function __construct(
29
        private readonly UserHelper $userHelper,
30
        private readonly AccessUrlHelper $accessUrlHelper,
31
        private readonly UserRepository $userRepository,
32
        private readonly SessionRepository $sessionRepository,
33
        private readonly PaginationExtension $paginationExtension,
34
    ) {}
35
36
    /**
37
     * @throws Exception
38
     */
39
    public function provide(Operation $operation, array $uriVariables = [], array $context = []): array
40
    {
41
        $user = $this->userRepository->find($uriVariables['id']);
42
43
        if (!$user) {
44
            throw new NotFoundHttpException('User not found');
45
        }
46
47
        $currentUser = $this->userHelper->getCurrent();
48
        $url = $this->accessUrlHelper->getCurrent();
49
50
        $isAllowed = $user === $currentUser || ($currentUser && $currentUser->isAdmin());
51
52
        if (!$isAllowed) {
53
            throw new AccessDeniedException();
54
        }
55
56
        if ('user_session_subscriptions_past' === $operation->getName()) {
57
            $sessions = $this->sessionRepository->getPastSessionsOfUserInUrl($user, $url);
58
            $this->hydrateDaysLeft($sessions, $user);
59
60
            return $sessions;
61
        }
62
63
        if ('user_session_subscriptions_current' === $operation->getName()) {
64
            $sessions = $this->getCurrentSessionsPagedAndFiltered($operation, $context, $user, $url);
65
            $this->hydrateDaysLeft($sessions, $user);
66
67
            return $sessions;
68
        }
69
70
        // Upcoming can stay as a pure DB filter (duration sessions won't be upcoming anyway)
71
        $qb = $this->sessionRepository->getUpcomingSessionsOfUserInUrl($user, $url);
72
73
        $this->paginationExtension->applyToCollection(
74
            $qb,
75
            new QueryNameGenerator(),
76
            Session::class,
77
            $operation,
78
            $context
79
        );
80
81
        $paginator = $this->paginationExtension->getResult($qb, Session::class, $operation, $context);
82
83
        if ($paginator instanceof Paginator) {
84
            $sessions = iterator_to_array($paginator);
85
            $this->hydrateDaysLeft($sessions, $user);
86
87
            return $sessions;
88
        }
89
90
        if (is_iterable($paginator)) {
91
            $sessions = \is_array($paginator) ? $paginator : iterator_to_array($paginator);
92
            $this->hydrateDaysLeft($sessions, $user);
93
94
            return $sessions;
95
        }
96
97
        return [];
98
    }
99
100
    /**
101
     * Ensures pagination is not broken by filtering duration sessions in PHP.
102
     * We scan ahead until we fill the requested page size or we run out of DB results.
103
     *
104
     * @return Session[]
105
     */
106
    private function getCurrentSessionsPagedAndFiltered(
107
        Operation $operation,
108
        array $context,
109
                  $user,
110
                  $url
111
    ): array {
112
        $filters = $context['filters'] ?? [];
113
114
        $page = (int) ($filters['page'] ?? 1);
115
        if ($page < 1) {
116
            $page = 1;
117
        }
118
119
        $itemsPerPage = (int) (
120
            $filters['itemsPerPage']
121
            ?? $context['pagination_items_per_page']
122
            ?? 10
123
        );
124
        if ($itemsPerPage < 1) {
125
            $itemsPerPage = 10;
126
        }
127
128
        // Base query: current sessions by date rules (this currently includes duration sessions due to NULL dates).
129
        $baseQb = $this->sessionRepository->getCurrentSessionsOfUserInUrl($user, $url);
130
131
        $wantedOffset = ($page - 1) * $itemsPerPage;
132
        $scanOffset = $wantedOffset;
133
134
        // Scan chunks to compensate filtering
135
        $scanSize = max($itemsPerPage * 3, 30);
136
137
        $result = [];
138
139
        while (\count($result) < $itemsPerPage) {
140
            $qb = clone $baseQb;
141
            $qb->setFirstResult($scanOffset);
142
            $qb->setMaxResults($scanSize);
143
144
            $chunk = $qb->getQuery()->getResult();
145
            if (empty($chunk)) {
146
                break;
147
            }
148
149
            foreach ($chunk as $session) {
150
                // Duration sessions: decide current/past based on remaining days (students only).
151
                if ($session->getDuration() > 0 && !$session->hasCoach($user)) {
152
                    $daysLeft = $session->getDaysLeftByUser($user);
153
                    $session->setDaysLeft($daysLeft);
154
155
                    if ($daysLeft < 0) {
156
                        continue; // It's past for the student
157
                    }
158
                }
159
160
                $result[] = $session;
161
162
                if (\count($result) >= $itemsPerPage) {
163
                    break;
164
                }
165
            }
166
167
            $scanOffset += $scanSize;
168
        }
169
170
        return $result;
171
    }
172
173
    /**
174
     * Adds daysLeft to duration sessions so the frontend can show remaining/expired properly.
175
     *
176
     * @param Session[] $sessions
177
     */
178
    private function hydrateDaysLeft(array $sessions, $user): void
179
    {
180
        foreach ($sessions as $session) {
181
            if ($session->getDuration() > 0 && !$session->hasCoach($user)) {
182
                $session->setDaysLeft($session->getDaysLeftByUser($user));
183
            }
184
        }
185
    }
186
}
187