‪TYPO3CMS  ‪main
ReportRepository.php
Go to the documentation of this file.
1 <?php
2 
3 declare(strict_types=1);
4 
5 /*
6  * This file is part of the TYPO3 CMS project.
7  *
8  * It is free software; you can redistribute it and/or modify it under
9  * the terms of the GNU General Public License, either version 2
10  * of the License, or any later version.
11  *
12  * For the full copyright and license information, please read the
13  * LICENSE.txt file that was distributed with this source code.
14  *
15  * The TYPO3 project - inspiring people to share!
16  */
17 
19 
20 use Doctrine\DBAL\ArrayParameterType;
21 use Symfony\Component\Uid\UuidV4;
24 use TYPO3\CMS\Core\Database\Query\QueryBuilder;
26 
31 {
32  protected const ‪TABLE_NAME = 'sys_http_report';
33  protected const ‪TYPE = 'csp-report';
34 
35  public function ‪__construct(protected readonly ‪ConnectionPool $pool) {}
36 
40  public function ‪findAll(‪ReportDemand $demand = null): array
41  {
42  $demand ??= ‪ReportDemand::create();
43  $result = $this->‪prepareQueryBuilder($demand)
44  ->select('*')
45  ->executeQuery();
46  return array_map(
47  static fn(array $row) => ‪Report::fromArray($row),
48  $result->fetchAllAssociative()
49  );
50  }
51 
55  public function ‪findAllSummarized(‪ReportDemand $demand = null): array
56  {
57  $demand ??= ‪ReportDemand::create();
58  $queryBuilder = $this->‪prepareQueryBuilder($demand, 'report');
59  $uuidQueryBuilder = $this->‪getQueryBuilder()->from(self::TABLE_NAME, 'tab_uuid');
60  $summaryQueryBuilder = $this->‪getQueryBuilder()->from(self::TABLE_NAME, 'tab_summary');
61  $expr = $queryBuilder->expr();
62 
63  // these nested query builders are doing a bunch of things to meet `ONLY_FULL_GROUP_BY` constraints
64  // + inner "summary" builder: build [summary; created] relation, summary must be distinct
65  // + helping "uuid" builder: build [uuid <= {summary; created}] relation, summary must be distinct
66  // + outer "report" builder: finally query [* <= {uuid <= {summary; created}}],
67  // conditions/filters are applied to this effective outer query builder
68 
69  $summaryQueryBuilder
70  ->selectLiteral($this->‪createFunctionLiteral(
71  $queryBuilder,
72  'MAX',
73  'tab_summary.created',
74  'created'
75  ))
76  ->addSelectLiteral('summary')
77  ->groupBy('summary');
78 
79  $this->‪applySummaryJoin(
80  $uuidQueryBuilder,
81  'tab_uuid',
82  $summaryQueryBuilder->getSQL(),
83  'res_summary',
84  (string)$expr->and(
85  $expr->eq('tab_uuid.summary', 'res_summary.summary'),
86  $expr->eq('tab_uuid.created', 'res_summary.created')
87  )
88  );
89  $uuidQueryBuilder
90  ->selectLiteral($this->‪createFunctionLiteral(
91  $queryBuilder,
92  // using `MAX(col)` since `ANY_VALUE(col)` is not supported by PostgreSQL
93  'MAX',
94  'tab_uuid.uuid',
95  'uuid'
96  ))
97  ->groupBy('tab_uuid.summary');
98 
99  $this->‪applySummaryJoin(
100  $queryBuilder,
101  'report',
102  $uuidQueryBuilder->getSQL(),
103  'res_uuid',
104  $expr->eq('report.uuid', 'res_uuid.uuid')
105  );
106  $result = $queryBuilder
107  ->select('report.*')
108  ->executeQuery();
109 
110  $summaryCountMap = $this->fetchSummaryCountMap();
111 
112  return array_map(
113  static fn(array $row) => ‪SummarizedReport::fromArray($row)
114  ->withCount($summaryCountMap[$row['summary']] ?? 0),
115  $result->fetchAllAssociative()
116  );
117  }
118 
119  public function ‪findByUuid(UuidV4 $uuid): ?‪Report
120  {
121  $result = $this->‪getConnection()->select(
122  ['*'],
123  self::TABLE_NAME,
124  ['uuid' => (string)$uuid]
125  );
126  $row = $result->fetchAssociative();
127  if (empty($row)) {
128  return null;
129  }
130  return ‪Report::fromArray($row);
131  }
132 
136  public function ‪findBySummary(string ...$summaries): array
137  {
138  if ($summaries === []) {
139  return [];
140  }
141  $demand = ‪ReportDemand::forSummaries($summaries);
142  $result = $this->‪prepareQueryBuilder($demand)
143  ->select('*')
144  ->executeQuery();
145  return array_map(
146  static fn(array $row) => ‪SummarizedReport::fromArray($row),
147  $result->fetchAllAssociative()
148  );
149  }
150 
151  public function ‪add(‪Report $report): bool
152  {
153  return $this->‪getConnection()->insert(
154  self::TABLE_NAME,
155  array_merge(
156  $report->‪toArray(),
157  ['type' => self::TYPE]
158  )
159  ) === 1;
160  }
161 
162  public function ‪updateStatus(‪ReportStatus $status, UuidV4 ...$uuids): int
163  {
164  $queryBuilder = $this->‪getQueryBuilder();
165  return $queryBuilder
166  ->update(self::TABLE_NAME)
167  ->set('status', $status->value)
168  ->set('changed', time())
169  ->where(
170  $queryBuilder->expr()->in(
171  'uuid',
172  $queryBuilder->createNamedParameter($uuids, ArrayParameterType::STRING)
173  )
174  )
175  ->executeStatement();
176  }
177 
178  public function remove(UuidV4 $uuid): bool
179  {
180  return $this->‪getConnection()->delete(
181  self::TABLE_NAME,
182  ['uuid' => (string)$uuid]
183  ) === 1;
184  }
185 
186  public function ‪removeAll(?‪Scope $scope = null): int
187  {
188  if ($scope === null) {
189  return $this->‪getConnection()->truncate(self::TABLE_NAME);
190  }
191  return $this->‪getConnection()->delete(self::TABLE_NAME, ['scope' => (string)$scope]);
192  }
193 
197  protected function fetchSummaryCountMap(): array
198  {
199  $queryBuilder = $this->‪getQueryBuilder();
200  ‪$rows = $queryBuilder
201  ->select('summary')
202  ->addSelectLiteral(sprintf(
203  'COUNT(%s) AS %s',
204  $queryBuilder->quoteIdentifier('summary'),
205  $queryBuilder->quoteIdentifier('summary_count')
206  ))
207  ->from(self::TABLE_NAME)
208  ->groupBy('summary')
209  ->executeQuery()
210  ->fetchAllAssociative();
211  return array_combine(
212  array_column(‪$rows, 'summary'),
213  array_column(‪$rows, 'summary_count'),
214  );
215  }
216 
217  protected function ‪prepareQueryBuilder(‪ReportDemand $demand, string $alias = null): QueryBuilder
218  {
219  $queryBuilder = $this->‪getQueryBuilder();
220  $queryBuilder->from(self::TABLE_NAME, $alias);
221  $this->‪applyStaticTypeCondition($queryBuilder, $alias);
222  $this->‪applyDemand($demand, $queryBuilder, $alias);
223  return $queryBuilder;
224  }
225 
226  protected function ‪applyDemand(‪ReportDemand $demand, QueryBuilder $queryBuilder, string $alias = null): void
227  {
228  $this->‪applyDemandConditions($demand, $queryBuilder, $alias);
229  $this->‪applyDemandSorting($demand, $queryBuilder, $alias);
230  }
231 
232  protected function ‪applyDemandConditions(‪ReportDemand $demand, QueryBuilder $queryBuilder, string $alias = null): void
233  {
234  $expr = $queryBuilder->expr();
235  $aliasPrefix = $this->‪prepareAliasPrefix($alias);
236  if ($demand->status !== null) {
237  $queryBuilder->andWhere($expr->eq(
238  $aliasPrefix . 'status',
239  $queryBuilder->createNamedParameter($demand->status->value, ‪Connection::PARAM_INT)
240  ));
241  }
242  if ($demand->scope !== null) {
243  $queryBuilder->andWhere($expr->eq(
244  $aliasPrefix . 'scope',
245  $queryBuilder->createNamedParameter((string)$demand->scope)
246  ));
247  }
248  if ($demand->summaries !== null) {
249  $queryBuilder->andWhere($expr->in(
250  $aliasPrefix . 'summary',
251  $queryBuilder->createNamedParameter(
252  $demand->summaries,
253  ArrayParameterType::STRING
254  ),
255  ));
256  }
257  if ($demand->requestTime !== null) {
258  $requestTimeParam = $queryBuilder->createNamedParameter(
259  $demand->requestTime,
261  );
262  if ($demand->afterRequestTime) {
263  $queryBuilder->andWhere($expr->gt($aliasPrefix . 'request_time', $requestTimeParam));
264  } else {
265  $queryBuilder->andWhere($expr->eq($aliasPrefix . 'request_time', $requestTimeParam));
266  }
267  }
268  }
269 
270  protected function ‪applyDemandSorting(‪ReportDemand $demand, QueryBuilder $queryBuilder, string $alias = null): void
271  {
272  $aliasPrefix = $this->‪prepareAliasPrefix($alias);
273  if ($demand->orderFieldName !== null && $demand->orderDirection !== null) {
274  $queryBuilder->orderBy(
275  $aliasPrefix . $demand->orderFieldName,
276  $demand->orderDirection
277  );
278  }
279  }
280 
281  protected function ‪applyStaticTypeCondition(QueryBuilder $queryBuilder, string $alias = null): void
282  {
283  $aliasPrefix = $this->‪prepareAliasPrefix($alias);
284  $queryBuilder->andWhere(
285  $queryBuilder->expr()->eq(
286  $aliasPrefix . 'type',
287  $queryBuilder->createNamedParameter(self::TYPE)
288  )
289  );
290  }
291 
292  protected function ‪applySummaryJoin(QueryBuilder $queryBuilder, string $fromAlias, string $join, string $alias, string $condition): void
293  {
294  $queryBuilder->getConcreteQueryBuilder()->join(
295  $queryBuilder->quoteIdentifier($fromAlias),
296  sprintf('(%s)', $join),
297  $queryBuilder->quoteIdentifier($alias),
298  $condition
299  );
300  }
301 
302  protected function ‪createFunctionLiteral(QueryBuilder $queryBuilder, string $functionName, string $fieldName, string $alias = null): string
303  {
304  $values = [
305  $functionName,
306  $queryBuilder->quoteIdentifier($fieldName),
307  ];
308  if ($alias === null) {
309  $format = '%s(%s)';
310  } else {
311  $format = '%s(%s) AS %s';
312  $values[] = $queryBuilder->quoteIdentifier($alias);
313  }
314  return vsprintf($format, $values);
315  }
316 
317  protected function ‪prepareAliasPrefix(string $alias = null): string
318  {
319  return $alias === null ? '' : $alias . '.';
320  }
321 
322  protected function ‪getQueryBuilder(): QueryBuilder
323  {
324  return $this->pool->getQueryBuilderForTable(self::TABLE_NAME);
325  }
326 
327  protected function ‪getConnection(): ‪Connection
328  {
329  return $this->pool->getConnectionForTable(self::TABLE_NAME);
330  }
331 }
‪TYPO3\CMS\Core\Database\Connection\PARAM_INT
‪const PARAM_INT
Definition: Connection.php:52
‪TYPO3\CMS\Core\Security\ContentSecurityPolicy\Reporting\ReportRepository\applyDemandConditions
‪applyDemandConditions(ReportDemand $demand, QueryBuilder $queryBuilder, string $alias=null)
Definition: ReportRepository.php:232
‪TYPO3\CMS\Core\Security\ContentSecurityPolicy\Reporting\ReportRepository
Definition: ReportRepository.php:31
‪TYPO3\CMS\Core\Security\ContentSecurityPolicy\Reporting\ReportRepository\removeAll
‪removeAll(?Scope $scope=null)
Definition: ReportRepository.php:186
‪TYPO3\CMS\Core\Security\ContentSecurityPolicy\Reporting\ReportDemand\create
‪static create()
Definition: ReportDemand.php:37
‪TYPO3\CMS\Core\Security\ContentSecurityPolicy\Reporting\ReportRepository\TYPE
‪const TYPE
Definition: ReportRepository.php:33
‪TYPO3\CMS\Core\Security\ContentSecurityPolicy\Reporting\ReportRepository\findBySummary
‪list< Report > findBySummary(string ... $summaries)
Definition: ReportRepository.php:136
‪TYPO3\CMS\Core\Security\ContentSecurityPolicy\Reporting\ReportDemand
Definition: ReportDemand.php:28
‪TYPO3\CMS\Core\Security\ContentSecurityPolicy\Reporting\ReportRepository\$rows
‪$rows
Definition: ReportRepository.php:200
‪TYPO3\CMS\Core\Security\ContentSecurityPolicy\Reporting\ReportRepository\getQueryBuilder
‪array< string, fetchSummaryCountMap():array { $queryBuilder=$this-> getQueryBuilder()
‪TYPO3\CMS\Core\Security\ContentSecurityPolicy\Reporting\ReportRepository\prepareAliasPrefix
‪prepareAliasPrefix(string $alias=null)
Definition: ReportRepository.php:317
‪TYPO3\CMS\Core\Security\ContentSecurityPolicy\Reporting\ReportRepository\applySummaryJoin
‪applySummaryJoin(QueryBuilder $queryBuilder, string $fromAlias, string $join, string $alias, string $condition)
Definition: ReportRepository.php:292
‪TYPO3\CMS\Core\Security\ContentSecurityPolicy\Reporting\ReportStatus
‪ReportStatus
Definition: ReportStatus.php:24
‪TYPO3\CMS\Core\Security\ContentSecurityPolicy\Reporting\ReportRepository\add
‪add(Report $report)
Definition: ReportRepository.php:151
‪TYPO3\CMS\Core\Security\ContentSecurityPolicy\Reporting\ReportRepository\applyDemand
‪applyDemand(ReportDemand $demand, QueryBuilder $queryBuilder, string $alias=null)
Definition: ReportRepository.php:226
‪TYPO3\CMS\Core\Security\ContentSecurityPolicy\Reporting\ReportRepository\findByUuid
‪findByUuid(UuidV4 $uuid)
Definition: ReportRepository.php:119
‪TYPO3\CMS\Core\Security\ContentSecurityPolicy\Reporting\ReportRepository\TABLE_NAME
‪const TABLE_NAME
Definition: ReportRepository.php:32
‪TYPO3\CMS\Core\Security\ContentSecurityPolicy\Reporting\ReportRepository\updateStatus
‪updateStatus(ReportStatus $status, UuidV4 ... $uuids)
Definition: ReportRepository.php:162
‪TYPO3\CMS\Core\Security\ContentSecurityPolicy\Reporting\ReportRepository\findAll
‪list< Report > findAll(ReportDemand $demand=null)
Definition: ReportRepository.php:40
‪TYPO3\CMS\Core\Security\ContentSecurityPolicy\Scope
Definition: Scope.php:30
‪TYPO3\CMS\Core\Security\ContentSecurityPolicy\Reporting
Definition: Report.php:18
‪TYPO3\CMS\Core\Security\ContentSecurityPolicy\Reporting\Report\fromArray
‪static fromArray(array $array)
Definition: Report.php:32
‪TYPO3\CMS\Core\Security\ContentSecurityPolicy\Reporting\ReportRepository\applyDemandSorting
‪applyDemandSorting(ReportDemand $demand, QueryBuilder $queryBuilder, string $alias=null)
Definition: ReportRepository.php:270
‪TYPO3\CMS\Core\Security\ContentSecurityPolicy\Reporting\ReportRepository\findAllSummarized
‪list< SummarizedReport > findAllSummarized(ReportDemand $demand=null)
Definition: ReportRepository.php:55
‪TYPO3\CMS\Core\Security\ContentSecurityPolicy\Reporting\Report\toArray
‪toArray()
Definition: Report.php:80
‪TYPO3\CMS\Core\Security\ContentSecurityPolicy\Reporting\ReportRepository\createFunctionLiteral
‪createFunctionLiteral(QueryBuilder $queryBuilder, string $functionName, string $fieldName, string $alias=null)
Definition: ReportRepository.php:302
‪TYPO3\CMS\Core\Database\Connection
Definition: Connection.php:41
‪TYPO3\CMS\Core\Security\ContentSecurityPolicy\Reporting\Report
Definition: Report.php:27
‪TYPO3\CMS\Core\Database\ConnectionPool
Definition: ConnectionPool.php:46
‪TYPO3\CMS\Core\Security\ContentSecurityPolicy\Reporting\ReportRepository\prepareQueryBuilder
‪prepareQueryBuilder(ReportDemand $demand, string $alias=null)
Definition: ReportRepository.php:217
‪TYPO3\CMS\Core\Security\ContentSecurityPolicy\Reporting\ReportRepository\getQueryBuilder
‪getQueryBuilder()
Definition: ReportRepository.php:322
‪TYPO3\CMS\Core\Security\ContentSecurityPolicy\Reporting\ReportRepository\getConnection
‪getConnection()
Definition: ReportRepository.php:327
‪TYPO3\CMS\Core\Security\ContentSecurityPolicy\Reporting\ReportRepository\applyStaticTypeCondition
‪applyStaticTypeCondition(QueryBuilder $queryBuilder, string $alias=null)
Definition: ReportRepository.php:281
‪TYPO3\CMS\Core\Security\ContentSecurityPolicy\Reporting\ReportRepository\__construct
‪__construct(protected readonly ConnectionPool $pool)
Definition: ReportRepository.php:35
‪TYPO3\CMS\Core\Security\ContentSecurityPolicy\Reporting\ReportDemand\forSummaries
‪static forSummaries(array $summaries)
Definition: ReportDemand.php:42