Passed
Push — dependabot/npm_and_yarn/cross-... ( 1f4447...a16049 )
by
unknown
10:20
created

MessageStateProvider::handleCollection()   A

Complexity

Conditions 4
Paths 5

Size

Total Lines 28
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
eloc 12
c 1
b 0
f 0
nc 5
nop 2
dl 0
loc 28
rs 9.8666
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\Paginator;
10
use ApiPlatform\Doctrine\Orm\State\CollectionProvider;
11
use ApiPlatform\Doctrine\Orm\State\ItemProvider;
12
use ApiPlatform\Metadata\Operation;
13
use ApiPlatform\State\ProviderInterface;
14
use Chamilo\CoreBundle\Entity\Message;
15
use Doctrine\ORM\EntityManagerInterface;
16
use Doctrine\ORM\QueryBuilder;
17
use LogicException;
18
use Symfony\Bundle\SecurityBundle\Security;
19
use Symfony\Component\HttpFoundation\RequestStack;
20
21
/**
22
 * @template-implements ProviderInterface<Message>
23
 */
24
final class MessageStateProvider implements ProviderInterface
25
{
26
    public function __construct(
27
        private readonly CollectionProvider $collectionProvider,
28
        private readonly ItemProvider $itemProvider,
29
        private readonly EntityManagerInterface $entityManager,
30
        private readonly Security $security,
31
        private readonly RequestStack $requestStack
32
    ) {}
33
34
    /**
35
     * Provides data based on the operation type (collection or item).
36
     *
37
     * @return Paginator|Message|null
38
     */
39
    public function provide(Operation $operation, array $uriVariables = [], array $context = [])
40
    {
41
        $isCollection = 'collection' === $context['operation_type'];
42
43
        if ($isCollection) {
44
            return $this->handleCollection($operation, $context);
45
        }
46
47
        // Delegate to ItemProvider for individual operations
48
        return $this->itemProvider->provide($operation, $uriVariables, $context);
49
    }
50
51
    /**
52
     * Handles collection-level operations with filtering and pagination.
53
     */
54
    private function handleCollection(Operation $operation, array $context): Paginator
55
    {
56
        $user = $this->security->getUser();
57
        if (!$user) {
58
            throw new LogicException('User not found.');
59
        }
60
61
        // Retrieve initial filters if they exist
62
        $filters = $context['filters'] ?? [];
63
        $this->applyFilters($filters);
64
65
        // Add base filters for the authenticated user
66
        $filters['sender'] = $user->getId();
67
        $filters['receivers.receiver'] = $user->getId();
68
69
        // Update context with merged filters
70
        $context['filters'] = $filters;
71
72
        // Check if advanced filtering is applied
73
        $isSearchApplied = isset($filters['search']) || \count($filters) > 2;
74
75
        if (!$isSearchApplied) {
76
            // Delegate to CollectionProvider for standard query handling
77
            return $this->collectionProvider->provide($operation, [], $context);
78
        }
79
80
        // Build custom query for advanced filters
81
        return $this->applyCustomQuery($operation, $context, $filters);
82
    }
83
84
    /**
85
     * Builds and applies a custom query with filtering, sorting, and pagination.
86
     */
87
    private function applyCustomQuery(Operation $operation, array $context, array $filters): Paginator
88
    {
89
        // Main query
90
        $queryBuilder = $this->createQueryWithFilters($filters);
91
92
        // Apply pagination and sorting
93
        $order = $context['filters']['order']['sendDate'] ?? 'ASC';
94
        $queryBuilder->orderBy('m.sendDate', $order);
95
96
        $itemsPerPage = (int) ($context['filters']['itemsPerPage'] ?? 10);
97
        $currentPage = (int) ($context['filters']['page'] ?? 1);
98
99
        $queryBuilder->setFirstResult(($currentPage - 1) * $itemsPerPage)
100
            ->setMaxResults($itemsPerPage)
101
        ;
102
103
        // Count query for total items
104
        $countQueryBuilder = $this->createQueryWithFilters($filters, true);
105
        $totalItems = (int) $countQueryBuilder->getQuery()->getSingleScalarResult();
106
107
        // Doctrine Paginator
108
        $doctrinePaginator = new \Doctrine\ORM\Tools\Pagination\Paginator($queryBuilder, true);
109
110
        // Adjust OutputWalkers as needed
111
        $needsOutputWalkers = \count($queryBuilder->getDQLPart('join')) > 0;
112
        $doctrinePaginator->setUseOutputWalkers($needsOutputWalkers);
113
114
        return new Paginator($doctrinePaginator);
115
    }
116
117
    /**
118
     * Creates a query with filters applied dynamically.
119
     */
120
    private function createQueryWithFilters(array $filters, bool $isCountQuery = false): QueryBuilder
121
    {
122
        $queryBuilder = $this->entityManager->createQueryBuilder();
123
124
        // Adjust SELECT statement for count or distinct query
125
        if ($isCountQuery) {
126
            $queryBuilder->select('COUNT(DISTINCT m.id)');
127
        } else {
128
            $queryBuilder->select('DISTINCT m');
129
        }
130
131
        $queryBuilder->from(Message::class, 'm')
132
            ->leftJoin('m.receivers', 'r', 'WITH', 'r.deletedAt IS NULL OR r.deletedAt > CURRENT_TIMESTAMP()')
133
            ->where('m.sender = :user OR r.receiver = :user')
134
            ->setParameter('user', $filters['sender'])
135
        ;
136
137
        // Dynamically apply filters
138
        foreach ($filters as $key => $value) {
139
            switch ($key) {
140
                case 'msgType':
141
                    $queryBuilder->andWhere('m.msgType = :msgType')->setParameter('msgType', $value);
142
143
                    break;
144
145
                case 'status':
146
                    $queryBuilder->andWhere('m.status = :status')->setParameter('status', $value);
147
148
                    break;
149
150
                case 'receivers.receiver':
151
                    $queryBuilder->andWhere('r.receiver = :receiver')->setParameter('receiver', $value);
152
153
                    break;
154
155
                case 'receivers.receiverType':
156
                    $queryBuilder->andWhere('r.receiverType = :receiverType')->setParameter('receiverType', $value);
157
158
                    break;
159
160
                case 'receivers.read':
161
                    $queryBuilder->andWhere('r.read = :read')->setParameter('read', !('false' === $value));
162
163
                    break;
164
165
                case 'search':
166
                    $queryBuilder->andWhere('m.title LIKE :search OR m.content LIKE :search')
167
                        ->setParameter('search', '%'.$value.'%')
168
                    ;
169
170
                    break;
171
            }
172
        }
173
174
        return $queryBuilder;
175
    }
176
177
    /**
178
     * Merges request filters into the provided filter array.
179
     */
180
    private function applyFilters(array &$filters): void
181
    {
182
        $request = $this->requestStack->getMainRequest();
183
        if ($request) {
184
            $requestFilters = $request->query->all();
185
            $filters = array_merge($filters, $requestFilters);
186
        }
187
    }
188
}
189