‪TYPO3CMS  ‪main
BrokenLinkRepository.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\Exception\TableNotFoundException;
27 
32 {
33  protected const ‪TABLE = 'tx_linkvalidator_link';
34 
42  public function ‪isLinkTargetBrokenLink(string $linkTarget, string $linkType = ''): bool
43  {
44  try {
45  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
46  ->getQueryBuilderForTable(static::TABLE);
47  $constraints = [
48  $queryBuilder->expr()->eq('url', $queryBuilder->createNamedParameter($linkTarget)),
49  ];
50  if ($linkType !== '') {
51  $constraints[] = $queryBuilder->expr()->eq('link_type', $queryBuilder->createNamedParameter($linkType));
52  }
53 
54  $queryBuilder
55  ->count('uid')
56  ->from(static::TABLE)
57  ->where(...$constraints);
58 
59  return (bool)$queryBuilder
60  ->executeQuery()
61  ->fetchOne();
62  } catch (TableNotFoundException $e) {
63  return false;
64  }
65  }
66 
74  public function ‪getNumberOfBrokenLinksForRecordsOnPages(array $pageIds, array $searchFields): array
75  {
76  $result = [
77  'total' => 0,
78  ];
79 
80  // We need to do the work in chunks, as it may be quite huge and would hit the one
81  // or other limit depending on the used dbms - and we also avoid placeholder usage
82  // as they are hard to calculate beforehand because of some magic handling of dbal.
84  GeneralUtility::makeInstance(ConnectionPool::class)
85  ->getConnectionForTable(static::TABLE)
86  ->getDatabasePlatform()
87  );
88  foreach (array_chunk($pageIds, (int)floor($maxChunk / 3)) as $pageIdsChunk) {
89  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
90  ->getQueryBuilderForTable(static::TABLE);
91  $queryBuilder->getRestrictions()->removeAll();
92  if (!‪$GLOBALS['BE_USER']->isAdmin()) {
93  $queryBuilder->getRestrictions()
94  ->add(GeneralUtility::makeInstance(EditableRestriction::class, $searchFields, $queryBuilder));
95  }
96  $statement = $queryBuilder->select('link_type')
97  ->addSelectLiteral($queryBuilder->expr()->count(static::TABLE . '.uid', 'amount'))
98  ->from(static::TABLE)
99  ->join(
100  static::TABLE,
101  'pages',
102  'pages',
103  $queryBuilder->expr()->eq('record_pid', $queryBuilder->quoteIdentifier('pages.uid'))
104  )
105  ->where(
106  $queryBuilder->expr()->or(
107  $queryBuilder->expr()->and(
108  $queryBuilder->expr()->in(
109  'record_uid',
110  $queryBuilder->quoteArrayBasedValueListToIntegerList($pageIdsChunk)
111  ),
112  $queryBuilder->expr()->eq('table_name', $queryBuilder->quote('pages'))
113  ),
114  $queryBuilder->expr()->and(
115  $queryBuilder->expr()->in(
116  'record_pid',
117  $queryBuilder->quoteArrayBasedValueListToIntegerList($pageIdsChunk)
118  ),
119  $queryBuilder->expr()->neq('table_name', $queryBuilder->quote('pages'))
120  )
121  )
122  )
123  ->groupBy('link_type')
124  ->executeQuery();
125 
126  while ($row = $statement->fetchAssociative()) {
127  if (!isset($result[$row['link_type']])) {
128  $result[$row['link_type']] = 0;
129  }
130  $result[$row['link_type']] += $row['amount'];
131  $result['total'] += $row['amount'];
132  }
133  }
134  return $result;
135  }
136 
137  public function ‪setNeedsRecheckForRecord(int $recordUid, string $tableName): void
138  {
139  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
140  ->getQueryBuilderForTable(static::TABLE);
141 
142  $queryBuilder->update(static::TABLE)
143  ->where(
144  $queryBuilder->expr()->eq(
145  'record_uid',
146  $queryBuilder->createNamedParameter($recordUid, ‪Connection::PARAM_INT)
147  ),
148  $queryBuilder->expr()->eq(
149  'table_name',
150  $queryBuilder->createNamedParameter($tableName)
151  )
152  )
153  ->set('needs_recheck', 1)
154  ->executeStatement();
155  }
156 
157  public function ‪removeBrokenLinksForRecord(string $tableName, int $recordUid): void
158  {
159  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
160  ->getQueryBuilderForTable(static::TABLE);
161 
162  $queryBuilder->delete(static::TABLE)
163  ->where(
164  $queryBuilder->expr()->eq(
165  'record_uid',
166  $queryBuilder->createNamedParameter($recordUid, ‪Connection::PARAM_INT)
167  ),
168  $queryBuilder->expr()->eq(
169  'table_name',
170  $queryBuilder->createNamedParameter($tableName)
171  )
172  )
173  ->executeStatement();
174  }
175 
180  public function ‪removeAllBrokenLinksOfRecordsOnPageIds(array $pageIds, array $linkTypes): void
181  {
182  // We need to do the work in chunks, as it may be quite huge and would hit the one
183  // or other limit depending on the used dbms - and we also avoid placeholder usage
184  // as they are hard to calculate beforehand because of some magic handling of dbal.
186  GeneralUtility::makeInstance(ConnectionPool::class)
187  ->getConnectionForTable(static::TABLE)
188  ->getDatabasePlatform()
189  );
190  foreach (array_chunk($pageIds, (int)floor($maxChunk / 3)) as $pageIdsChunk) {
191  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
192  ->getQueryBuilderForTable(static::TABLE);
193 
194  $queryBuilder->delete(static::TABLE)
195  ->where(
196  $queryBuilder->expr()->or(
197  $queryBuilder->expr()->and(
198  $queryBuilder->expr()->in(
199  'record_uid',
200  $queryBuilder->quoteArrayBasedValueListToIntegerList($pageIdsChunk)
201  ),
202  $queryBuilder->expr()->eq('table_name', $queryBuilder->quote('pages'))
203  ),
204  $queryBuilder->expr()->and(
205  $queryBuilder->expr()->in(
206  'record_pid',
207  $queryBuilder->quoteArrayBasedValueListToIntegerList($pageIdsChunk)
208  ),
209  $queryBuilder->expr()->neq(
210  'table_name',
211  $queryBuilder->quote('pages')
212  )
213  )
214  ),
215  $queryBuilder->expr()->in(
216  'link_type',
217  $queryBuilder->quoteArrayBasedValueListToStringList($linkTypes)
218  )
219  )
220  ->executeStatement();
221  }
222  }
223 
236  array $pageIds,
237  array $linkTypes,
238  array $searchFields = [],
239  array ‪$languages = []
240  ): array {
241  $results = [];
242 
243  // We need to do the work in chunks, as it may be quite huge and would hit the one
244  // or other limit depending on the used dbms - and we also avoid placeholder usage
245  // as they are hard to calculate beforehand because of some magic handling of dbal.
247  GeneralUtility::makeInstance(ConnectionPool::class)
248  ->getConnectionForTable(static::TABLE)
249  ->getDatabasePlatform()
250  );
251  foreach (array_chunk($pageIds, (int)floor($maxChunk / 2)) as $pageIdsChunk) {
252  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
253  ->getQueryBuilderForTable(self::TABLE);
254 
255  // remove hidden restriction here because we join with pages and checkhidden=1 might be set
256  // we already correctly check for hidden / extendToSubpages when checking the links
257  $queryBuilder->getRestrictions()->removeByType(HiddenRestriction::class);
258 
259  if (!‪$GLOBALS['BE_USER']->isAdmin()) {
260  $queryBuilder->getRestrictions()
261  ->add(GeneralUtility::makeInstance(EditableRestriction::class, $searchFields, $queryBuilder));
262  }
263 
264  $constraints = [
265  $queryBuilder->expr()->or(
266  $queryBuilder->expr()->and(
267  $queryBuilder->expr()->in(
268  'record_uid',
269  $queryBuilder->quoteArrayBasedValueListToIntegerList($pageIdsChunk)
270  ),
271  $queryBuilder->expr()->eq('table_name', $queryBuilder->quote('pages'))
272  ),
273  $queryBuilder->expr()->and(
274  $queryBuilder->expr()->in(
275  'record_pid',
276  $queryBuilder->quoteArrayBasedValueListToIntegerList($pageIdsChunk)
277  ),
278  $queryBuilder->expr()->neq('table_name', $queryBuilder->quote('pages'))
279  )
280  ),
281  $queryBuilder->expr()->in(
282  'link_type',
283  $queryBuilder->quoteArrayBasedValueListToStringList($linkTypes)
284  ),
285  ];
286 
287  if (‪$languages !== []) {
288  $constraints[] = $queryBuilder->expr()->in(
289  'language',
290  $queryBuilder->quoteArrayBasedValueListToIntegerList(‪$languages)
291  );
292  }
293 
294  $records = $queryBuilder
295  ->select(self::TABLE . '.*')
296  ->from(self::TABLE)
297  ->join(
298  'tx_linkvalidator_link',
299  'pages',
300  'pages',
301  $queryBuilder->expr()->eq(
302  'tx_linkvalidator_link.record_pid',
303  $queryBuilder->quoteIdentifier('pages.uid')
304  )
305  )
306  ->where(...$constraints)
307  ->orderBy('tx_linkvalidator_link.record_uid')
308  ->addOrderBy('tx_linkvalidator_link.uid')
309  ->executeQuery()
310  ->fetchAllAssociative();
311  foreach ($records as &‪$record) {
312  $response = json_decode(‪$record['url_response'], true);
313  // Fallback mechanism to still support the old serialized data, could be removed in TYPO3 v12 or later
314  if ($response === null) {
315  $response = unserialize(‪$record['url_response'], ['allowed_classes' => false]);
316  }
317  ‪$record['url_response'] = $response;
318  $results[] = ‪$record;
319  }
320  }
321  return $results;
322  }
323 
333  public function ‪addBrokenLink(‪$record, bool $isValid, array $errorParams = []): void
334  {
335  $response = ['valid' => $isValid];
336  $response['errorParams'] = $errorParams;
337  ‪$record['url_response'] = json_encode($response);
338  GeneralUtility::makeInstance(ConnectionPool::class)
339  ->getConnectionForTable(self::TABLE)
340  ->insert(self::TABLE, ‪$record);
341  }
342 }
‪TYPO3\CMS\Core\Database\Query\Restriction\HiddenRestriction
Definition: HiddenRestriction.php:27
‪TYPO3\CMS\Core\Database\Connection\PARAM_INT
‪const PARAM_INT
Definition: Connection.php:52
‪TYPO3\CMS\Core\Database\Platform\PlatformInformation\getMaxBindParameters
‪static getMaxBindParameters(DoctrineAbstractPlatform $platform)
Definition: PlatformInformation.php:106
‪$languages
‪$languages
Definition: updateIsoDatabase.php:104
‪TYPO3\CMS\Webhooks\Message\$record
‪identifier readonly int readonly array $record
Definition: PageModificationMessage.php:36
‪TYPO3\CMS\Linkvalidator\QueryRestrictions\EditableRestriction
Definition: EditableRestriction.php:29
‪TYPO3\CMS\Core\Database\Connection
Definition: Connection.php:41
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:25
‪TYPO3\CMS\Core\Database\Platform\PlatformInformation
Definition: PlatformInformation.php:33
‪TYPO3\CMS\Core\Database\ConnectionPool
Definition: ConnectionPool.php:46
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:52
‪TYPO3\CMS\Linkvalidator\Repository
Definition: BrokenLinkRepository.php:18