Completed
Push — master ( 5da019...46d695 )
by
unknown
19:03
created

BrokenLinkRepository   A

Complexity

Total Complexity 15

Size/Duplication

Total Lines 250
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 143
dl 0
loc 250
rs 10
c 1
b 0
f 0
wmc 15

7 Methods

Rating   Name   Duplication   Size   Complexity  
A removeAllBrokenLinksOfRecordsOnPageIds() 0 32 1
A setNeedsRecheckForRecord() 0 18 1
A getNumberOfBrokenLinksForRecordsOnPages() 0 47 3
A removeBrokenLinksForRecord() 0 17 1
A isLinkTargetBrokenLink() 0 16 2
B getAllBrokenLinksForPages() 0 66 5
A addBrokenLink() 0 10 2
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
18
namespace TYPO3\CMS\Linkvalidator\Repository;
19
20
use Doctrine\DBAL\Exception\TableNotFoundException;
21
use TYPO3\CMS\Core\Database\Connection;
22
use TYPO3\CMS\Core\Database\ConnectionPool;
23
use TYPO3\CMS\Core\Utility\GeneralUtility;
24
use TYPO3\CMS\Linkvalidator\QueryRestrictions\EditableRestriction;
25
26
/**
27
 * Repository for finding broken links that were detected previously.
28
 */
29
class BrokenLinkRepository
30
{
31
    protected const TABLE = 'tx_linkvalidator_link';
32
33
    /**
34
     * Check if linkTarget is in list of broken links.
35
     *
36
     * @param string $linkTarget Url to check for. Can be a URL (for external links)
37
     *   a page uid (for db links), a file reference (for file links), etc.
38
     * @return bool is the link target a broken link
39
     */
40
    public function isLinkTargetBrokenLink(string $linkTarget): bool
41
    {
42
        try {
43
            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
44
                ->getQueryBuilderForTable(static::TABLE);
45
            $queryBuilder
46
                ->count('uid')
47
                ->from(static::TABLE)
48
                ->where(
49
                    $queryBuilder->expr()->eq('url', $queryBuilder->createNamedParameter($linkTarget))
50
                );
51
            return (bool)$queryBuilder
52
                    ->execute()
53
                    ->fetchColumn(0);
54
        } catch (TableNotFoundException $e) {
55
            return false;
56
        }
57
    }
58
59
    /**
60
     * Returns all broken links found on the page record and all records on a page (or multiple pages)
61
     * grouped by the link_type.
62
     *
63
     * @param array $pageIds
64
     * @param array $searchFields [ table => [field1, field2, ...], ...]
65
     * @return array
66
     */
67
    public function getNumberOfBrokenLinksForRecordsOnPages(array $pageIds, array $searchFields): array
68
    {
69
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
70
            ->getQueryBuilderForTable(static::TABLE);
71
        $queryBuilder->getRestrictions()->removeAll();
72
        if (!$GLOBALS['BE_USER']->isAdmin()) {
73
            $queryBuilder->getRestrictions()
74
                ->add(GeneralUtility::makeInstance(EditableRestriction::class, $searchFields, $queryBuilder));
75
        }
76
        $statement = $queryBuilder->select('link_type')
77
            ->addSelectLiteral($queryBuilder->expr()->count(static::TABLE . '.uid', 'amount'))
78
            ->from(static::TABLE)
79
            ->join(
80
                static::TABLE,
81
                'pages',
82
                'pages',
83
                $queryBuilder->expr()->eq('record_pid', $queryBuilder->quoteIdentifier('pages.uid'))
84
            )
85
            ->where(
86
                $queryBuilder->expr()->orX(
87
                    $queryBuilder->expr()->andX(
88
                        $queryBuilder->expr()->in(
89
                            'record_uid',
90
                            $queryBuilder->createNamedParameter($pageIds, Connection::PARAM_INT_ARRAY)
91
                        ),
92
                        $queryBuilder->expr()->eq('table_name', $queryBuilder->createNamedParameter('pages'))
93
                    ),
94
                    $queryBuilder->expr()->andX(
95
                        $queryBuilder->expr()->in(
96
                            'record_pid',
97
                            $queryBuilder->createNamedParameter($pageIds, Connection::PARAM_INT_ARRAY)
98
                        ),
99
                        $queryBuilder->expr()->neq('table_name', $queryBuilder->createNamedParameter('pages'))
100
                    )
101
                )
102
            )
103
            ->groupBy('link_type')
104
            ->execute();
105
106
        $result = [
107
            'total' => 0
108
        ];
109
        while ($row = $statement->fetch()) {
110
            $result[$row['link_type']] = $row['amount'];
111
            $result['total']+= $row['amount'];
112
        }
113
        return $result;
114
    }
115
116
    public function setNeedsRecheckForRecord(int $recordUid, string $tableName): void
117
    {
118
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
119
            ->getQueryBuilderForTable(static::TABLE);
120
121
        $queryBuilder->update(static::TABLE)
122
            ->where(
123
                $queryBuilder->expr()->eq(
124
                    'record_uid',
125
                    $queryBuilder->createNamedParameter($recordUid, \PDO::PARAM_INT)
126
                ),
127
                $queryBuilder->expr()->eq(
128
                    'table_name',
129
                    $queryBuilder->createNamedParameter($tableName)
130
                )
131
            )
132
            ->set('needs_recheck', 1)
133
            ->execute();
134
    }
135
136
    public function removeBrokenLinksForRecord(string $tableName, int $recordUid): void
137
    {
138
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
139
            ->getQueryBuilderForTable(static::TABLE);
140
141
        $queryBuilder->delete(static::TABLE)
142
            ->where(
143
                $queryBuilder->expr()->eq(
144
                    'record_uid',
145
                    $queryBuilder->createNamedParameter($recordUid, \PDO::PARAM_INT)
146
                ),
147
                $queryBuilder->expr()->eq(
148
                    'table_name',
149
                    $queryBuilder->createNamedParameter($tableName)
150
                )
151
            )
152
            ->execute();
153
    }
154
155
    public function removeAllBrokenLinksOfRecordsOnPageIds(array $pageIds, array $linkTypes): void
156
    {
157
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
158
            ->getQueryBuilderForTable(static::TABLE);
159
160
        $queryBuilder->delete(static::TABLE)
161
            ->where(
162
                $queryBuilder->expr()->orX(
163
                    $queryBuilder->expr()->andX(
164
                        $queryBuilder->expr()->in(
165
                            'record_uid',
166
                            $queryBuilder->createNamedParameter($pageIds, Connection::PARAM_INT_ARRAY)
167
                        ),
168
                        $queryBuilder->expr()->eq('table_name', $queryBuilder->createNamedParameter('pages'))
169
                    ),
170
                    $queryBuilder->expr()->andX(
171
                        $queryBuilder->expr()->in(
172
                            'record_pid',
173
                            $queryBuilder->createNamedParameter($pageIds, Connection::PARAM_INT_ARRAY)
174
                        ),
175
                        $queryBuilder->expr()->neq(
176
                            'table_name',
177
                            $queryBuilder->createNamedParameter('pages')
178
                        )
179
                    )
180
                ),
181
                $queryBuilder->expr()->in(
182
                    'link_type',
183
                    $queryBuilder->createNamedParameter($linkTypes, Connection::PARAM_STR_ARRAY)
184
                )
185
            )
186
            ->execute();
187
    }
188
189
    /**
190
     * Prepare database query with pageList and keyOpt data.
191
     *
192
     * This takes permissions of current BE user into account
193
     *
194
     * @param int[] $pageIds Pages to check for broken links
195
     * @param string[] $linkTypes Link types to validate
196
     * @param string[] $searchFields table => [fields1, field2, ...], ... : fields in which linkvalidator should
197
     *   search for broken links
198
     * @param int[] $languages Allowed languages
199
     * @return array
200
     */
201
    public function getAllBrokenLinksForPages(
202
        array $pageIds,
203
        array $linkTypes,
204
        array $searchFields = [],
205
        array $languages = []
206
    ): array {
207
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
208
            ->getQueryBuilderForTable(self::TABLE);
209
        if (!$GLOBALS['BE_USER']->isAdmin()) {
210
            $queryBuilder->getRestrictions()
211
                ->add(GeneralUtility::makeInstance(EditableRestriction::class, $searchFields, $queryBuilder));
212
        }
213
214
        $constraints = [
215
            $queryBuilder->expr()->orX(
216
                $queryBuilder->expr()->andX(
217
                    $queryBuilder->expr()->in(
218
                        'record_uid',
219
                        $queryBuilder->createNamedParameter($pageIds, Connection::PARAM_INT_ARRAY)
220
                    ),
221
                    $queryBuilder->expr()->eq('table_name', $queryBuilder->createNamedParameter('pages'))
222
                ),
223
                $queryBuilder->expr()->andX(
224
                    $queryBuilder->expr()->in(
225
                        'record_pid',
226
                        $queryBuilder->createNamedParameter($pageIds, Connection::PARAM_INT_ARRAY)
227
                    ),
228
                    $queryBuilder->expr()->neq('table_name', $queryBuilder->createNamedParameter('pages'))
229
                )
230
            ),
231
            $queryBuilder->expr()->in(
232
                'link_type',
233
                $queryBuilder->createNamedParameter($linkTypes, Connection::PARAM_STR_ARRAY)
234
            )
235
        ];
236
237
        if ($languages !== []) {
238
            $constraints[] = $queryBuilder->expr()->in(
239
                'language',
240
                $queryBuilder->createNamedParameter($languages, Connection::PARAM_INT_ARRAY)
241
            );
242
        }
243
244
        $records = $queryBuilder
245
            ->select(self::TABLE . '.*')
246
            ->from(self::TABLE)
247
            ->join(
248
                'tx_linkvalidator_link',
249
                'pages',
250
                'pages',
251
                $queryBuilder->expr()->eq('tx_linkvalidator_link.record_pid', $queryBuilder->quoteIdentifier('pages.uid'))
252
            )
253
            ->where(...$constraints)
254
            ->orderBy('tx_linkvalidator_link.record_uid')
255
            ->addOrderBy('tx_linkvalidator_link.uid')
256
            ->execute()
257
            ->fetchAll();
258
        foreach ($records as &$record) {
259
            $response = json_decode($record['url_response'], true);
260
            // Fallback mechanism to still support the old serialized data, could be removed in TYPO3 v12 or later
261
            if ($response === null) {
262
                $response = unserialize($record['url_response'], ['allowed_classes' => false]);
263
            }
264
            $record['url_response'] = $response;
265
        }
266
        return $records;
267
    }
268
269
    public function addBrokenLink($record, bool $isValid, array $errorParams = null): void
270
    {
271
        $response = ['valid' => $isValid];
272
        if ($errorParams) {
273
            $response['errorParams'] = $errorParams;
274
        }
275
        $record['url_response'] = json_encode($response);
276
        GeneralUtility::makeInstance(ConnectionPool::class)
277
            ->getConnectionForTable(self::TABLE)
278
            ->insert(self::TABLE, $record);
279
    }
280
}
281