1<?php
2
3declare(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
18namespace TYPO3\CMS\Redirects\Repository;
19
20use TYPO3\CMS\Core\Database\Connection;
21use TYPO3\CMS\Core\Database\ConnectionPool;
22use TYPO3\CMS\Core\Database\Query\QueryBuilder;
23use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
24use TYPO3\CMS\Core\Utility\GeneralUtility;
25
26/**
27 * Class for accessing redirect records from the database
28 * @internal
29 */
30class RedirectRepository
31{
32    /**
33     * Used within the backend module, which also includes the hidden records, but never deleted records.
34     */
35    public function findRedirectsByDemand(Demand $demand): array
36    {
37        return $this->getQueryBuilderForDemand($demand)
38            ->setMaxResults($demand->getLimit())
39            ->setFirstResult($demand->getOffset())
40            ->executeQuery()
41            ->fetchAllAssociative();
42    }
43
44    public function countRedirectsByByDemand(Demand $demand): int
45    {
46        return $this->getQueryBuilderForDemand($demand)->executeQuery()->rowCount();
47    }
48
49    /**
50     * Prepares the QueryBuilder with Constraints from the Demand
51     */
52    protected function getQueryBuilderForDemand(Demand $demand): QueryBuilder
53    {
54        $queryBuilder = $this->getQueryBuilder();
55        $queryBuilder
56            ->select('*')
57            ->from('sys_redirect');
58
59        $queryBuilder->orderBy(
60            $demand->getOrderField(),
61            $demand->getOrderDirection()
62        );
63
64        if ($demand->hasSecondaryOrdering()) {
65            $queryBuilder->addOrderBy($demand->getSecondaryOrderField());
66        }
67
68        $constraints = [];
69        if ($demand->hasSourceHosts()) {
70            $constraints[] =$queryBuilder->expr()->in(
71                'source_host',
72                $queryBuilder->createNamedParameter($demand->getSourceHosts(), Connection::PARAM_STR_ARRAY)
73            );
74        }
75
76        if ($demand->hasSourcePath()) {
77            $escapedLikeString = '%' . $queryBuilder->escapeLikeWildcards($demand->getSourcePath()) . '%';
78            $constraints[] = $queryBuilder->expr()->like(
79                'source_path',
80                $queryBuilder->createNamedParameter($escapedLikeString, \PDO::PARAM_STR)
81            );
82        }
83
84        if ($demand->hasTarget()) {
85            $escapedLikeString = '%' . $queryBuilder->escapeLikeWildcards($demand->getTarget()) . '%';
86            $constraints[] = $queryBuilder->expr()->like(
87                'target',
88                $queryBuilder->createNamedParameter($escapedLikeString, \PDO::PARAM_STR)
89            );
90        }
91
92        if ($demand->hasStatusCodes()) {
93            $constraints[] = $queryBuilder->expr()->in(
94                'target_statuscode',
95                $queryBuilder->createNamedParameter($demand->getStatusCodes(), Connection::PARAM_INT_ARRAY)
96            );
97        }
98
99        if ($demand->hasMaxHits()) {
100            $constraints[] = $queryBuilder->expr()->lt(
101                'hitcount',
102                $queryBuilder->createNamedParameter($demand->getMaxHits(), \PDO::PARAM_INT)
103            );
104            // When max hits is set, exclude records which explicitly disabled the hitcount feature
105            $constraints[] = $queryBuilder->expr()->eq(
106                'disable_hitcount',
107                $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
108            );
109        }
110
111        if (!empty($constraints)) {
112            $queryBuilder->where(...$constraints);
113        }
114        return $queryBuilder;
115    }
116
117    /**
118     * Used for the filtering in the backend
119     */
120    public function findHostsOfRedirects(): array
121    {
122        return $this->getQueryBuilder()
123            ->select('source_host as name')
124            ->from('sys_redirect')
125            ->orderBy('source_host')
126            ->groupBy('source_host')
127            ->executeQuery()
128            ->fetchAllAssociative();
129    }
130
131    /**
132     * Used for the filtering in the backend
133     */
134    public function findStatusCodesOfRedirects(): array
135    {
136        return $this->getQueryBuilder()
137            ->select('target_statuscode as code')
138            ->from('sys_redirect')
139            ->orderBy('target_statuscode')
140            ->groupBy('target_statuscode')
141            ->executeQuery()
142            ->fetchAllAssociative();
143    }
144
145    protected function getQueryBuilder(): QueryBuilder
146    {
147        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_redirect');
148        $queryBuilder->getRestrictions()
149            ->removeAll()
150            ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
151        return $queryBuilder;
152    }
153
154    public function removeByDemand(Demand $demand): void
155    {
156        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
157            ->getQueryBuilderForTable('sys_redirect');
158        $queryBuilder
159            ->delete('sys_redirect')
160            ->where(
161                $queryBuilder->expr()->eq('protected', $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT))
162            );
163
164        if ($demand->hasMaxHits()) {
165            $queryBuilder->andWhere(
166                $queryBuilder->expr()->lt('hitcount', $queryBuilder->createNamedParameter($demand->getMaxHits(), \PDO::PARAM_INT))
167            );
168        }
169        if ($demand->hasSourceHosts()) {
170            $queryBuilder
171                ->andWhere('source_host IN (:domains)')
172                ->setParameter('domains', $demand->getSourceHosts(), Connection::PARAM_STR_ARRAY);
173        }
174        if ($demand->hasStatusCodes()) {
175            $queryBuilder
176                ->andWhere('target_statuscode IN (:statusCodes)')
177                ->setParameter('statusCodes', $demand->getStatusCodes(), Connection::PARAM_INT_ARRAY);
178        }
179        if ($demand->hasOlderThan()) {
180            $timeStamp = $demand->getOlderThan()->getTimestamp();
181            $queryBuilder->andWhere(
182                $queryBuilder->expr()->lt('createdon', $queryBuilder->createNamedParameter($timeStamp, \PDO::PARAM_INT))
183            );
184        }
185        if ($demand->hasSourcePath()) {
186            $queryBuilder
187                ->andWhere($queryBuilder->expr()->like('source_path', ':path'))
188                ->setParameter('path', $demand->getSourcePath(), \PDO::PARAM_STR);
189        }
190
191        $queryBuilder->executeStatement();
192    }
193}
194