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

LinkAnalyzerResult::getLocalizedPageId()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 37
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
eloc 25
nc 3
nop 2
dl 0
loc 37
rs 9.52
c 1
b 0
f 0
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\Result;
19
20
use TYPO3\CMS\Backend\Utility\BackendUtility;
21
use TYPO3\CMS\Core\Database\Connection;
22
use TYPO3\CMS\Core\Database\ConnectionPool;
23
use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
24
use TYPO3\CMS\Core\Localization\LanguageService;
25
use TYPO3\CMS\Core\Utility\GeneralUtility;
26
use TYPO3\CMS\Linkvalidator\LinkAnalyzer;
27
use TYPO3\CMS\Linkvalidator\Repository\BrokenLinkRepository;
28
29
/**
30
 * Used to work with LinkAnalyzer results
31
 *
32
 * @internal
33
 */
34
class LinkAnalyzerResult
35
{
36
    /**
37
     * @var LinkAnalyzer
38
     */
39
    protected $linkAnalyzer;
40
41
    /**
42
     * @var BrokenLinkRepository
43
     */
44
    protected $brokenLinkRepository;
45
46
    /**
47
     * @var ConnectionPool
48
     */
49
    protected $connectionPool;
50
51
    /**
52
     * @var array
53
     */
54
    protected $brokenLinks = [];
55
56
    /**
57
     * @var array
58
     */
59
    protected $newBrokenLinkCounts = [];
60
61
    /**
62
     * @var array
63
     */
64
    protected $oldBrokenLinkCounts = [];
65
66
    /**
67
     * @var bool
68
     */
69
    protected $differentToLastResult = false;
70
71
    /**
72
     * Save language codes to reduce database requests
73
     *
74
     * @var array<int, string>
75
     */
76
    protected $languageCodes = ['default'];
77
78
    /**
79
     * Save localized pages to reduce database requests
80
     *
81
     * @var array<string, int>
82
     */
83
    protected $localizedPages = [];
84
85
    public function __construct(
86
        LinkAnalyzer $linkAnalyzer,
87
        BrokenLinkRepository $brokenLinkRepository,
88
        ConnectionPool $connectionPool
89
    ) {
90
        $this->linkAnalyzer = $linkAnalyzer;
91
        $this->brokenLinkRepository = $brokenLinkRepository;
92
        $this->connectionPool = $connectionPool;
93
    }
94
95
    /**
96
     * Call LinkAnalyzer with provided task configuration and process result values
97
     *
98
     * @param int    $page
99
     * @param int    $depth
100
     * @param array  $pageRow
101
     * @param array  $modTSconfig
102
     * @param array  $searchFields
103
     * @param array  $linkTypes
104
     * @param string $languages
105
     *
106
     * @return $this
107
     */
108
    public function getResultForTask(
109
        int $page,
110
        int $depth,
111
        array $pageRow,
112
        array $modTSconfig,
113
        array $searchFields = [],
114
        array $linkTypes = [],
115
        string $languages = ''
116
    ): self {
117
        $rootLineHidden = $this->linkAnalyzer->getRootLineIsHidden($pageRow);
118
        $checkHidden = $modTSconfig['checkhidden'] === 1;
119
120
        if ($rootLineHidden && !$checkHidden) {
121
            return $this;
122
        }
123
124
        $treeList = $this->linkAnalyzer->extGetTreeList($page, $depth, 0, '1=1', $modTSconfig['checkhidden']);
125
126
        if ($pageRow['hidden'] === 0 || $checkHidden) {
127
            $treeList .= $page;
128
        }
129
130
        if ($treeList === '') {
131
            return $this;
132
        }
133
134
        $pageList = $this->addPageTranslationsToPageList(
135
            $treeList,
136
            $page,
137
            (bool)$modTSconfig['checkhidden'],
138
            $languages
139
        );
140
141
        $this->linkAnalyzer->init($searchFields, $pageList, $modTSconfig);
142
        $this->oldBrokenLinkCounts = $this->linkAnalyzer->getLinkCounts();
143
144
        $this->linkAnalyzer->getLinkStatistics($linkTypes, $modTSconfig['checkhidden']);
145
        $this->newBrokenLinkCounts = $this->linkAnalyzer->getLinkCounts();
146
147
        $this->brokenLinks = $this->brokenLinkRepository->getAllBrokenLinksForPages(
148
            GeneralUtility::intExplode(',', $pageList, true),
149
            array_keys($linkTypes),
150
            $searchFields,
151
            GeneralUtility::intExplode(',', $languages, true)
152
        );
153
154
        $this
155
            ->processLinkCounts($linkTypes)
156
            ->processBrokenLinks();
157
158
        return $this;
159
    }
160
161
    public function setBrokenLinks(array $brokenLinks): void
162
    {
163
        $this->brokenLinks = $brokenLinks;
164
    }
165
166
    public function getBrokenLinks(): array
167
    {
168
        return $this->brokenLinks;
169
    }
170
171
    public function setNewBrokenLinkCounts(array $newBrokenLinkCounts): void
172
    {
173
        $this->newBrokenLinkCounts = $newBrokenLinkCounts;
174
    }
175
176
    public function getNewBrokenLinkCounts(): array
177
    {
178
        return $this->newBrokenLinkCounts;
179
    }
180
181
    public function setOldBrokenLinkCounts(array $oldBrokenLinkCounts): void
182
    {
183
        $this->oldBrokenLinkCounts = $oldBrokenLinkCounts;
184
    }
185
186
    public function getOldBrokenLinkCounts(): array
187
    {
188
        return $this->oldBrokenLinkCounts;
189
    }
190
191
    public function getTotalBrokenLinksCount(): int
192
    {
193
        return $this->newBrokenLinkCounts['total'] ?? 0;
194
    }
195
196
    public function isDifferentToLastResult(): bool
197
    {
198
        return $this->differentToLastResult;
199
    }
200
201
    /**
202
     * Process the link counts (old and new) and ensures that all link types are available in the array
203
     *
204
     * @param array<int, string> $linkTypes list of link types
205
     * @return LinkAnalyzerResult
206
     */
207
    protected function processLinkCounts(array $linkTypes): self
208
    {
209
        foreach (array_keys($linkTypes) as $linkType) {
210
            if (!isset($this->newBrokenLinkCounts[$linkType])) {
211
                $this->newBrokenLinkCounts[$linkType] = 0;
212
            }
213
            if (!isset($this->oldBrokenLinkCounts[$linkType])) {
214
                $this->oldBrokenLinkCounts[$linkType] = 0;
215
            }
216
            if ($this->newBrokenLinkCounts[$linkType] !== $this->oldBrokenLinkCounts[$linkType]) {
217
                $this->differentToLastResult = true;
218
            }
219
        }
220
221
        return $this;
222
    }
223
224
    /**
225
     * Process broken link values and assign them to new variables which are used in the templates
226
     * shipped by the core but can also be used in custom templates. The raw data is untouched and
227
     * can also still be used in custom templates.
228
     *
229
     * @return LinkAnalyzerResult
230
     */
231
    protected function processBrokenLinks(): self
232
    {
233
        foreach ($this->brokenLinks as $key => &$brokenLink) {
234
            $fullRecord = BackendUtility::getRecord($brokenLink['table_name'], $brokenLink['record_uid']);
235
236
            if ($fullRecord !== null) {
237
                $brokenLink['full_record'] = $fullRecord;
238
                $brokenLink['record_title'] = BackendUtility::getRecordTitle($brokenLink['table_name'], $fullRecord);
239
            }
240
241
            $brokenLink['real_pid'] = ((int)($brokenLink['language'] ?? 0) > 0 && (string)($brokenLink['table_name'] ?? '') !== 'pages')
242
                ? $this->getLocalizedPageId((int)$brokenLink['record_pid'], (int)$brokenLink['language'])
243
                : $brokenLink['record_pid'];
244
            $pageRecord = BackendUtility::getRecord('pages', $brokenLink['real_pid']);
245
246
            if ($pageRecord !== null) {
247
                $brokenLink['page_record'] = $pageRecord;
248
            }
249
250
            $brokenLink['record_type'] = $this->getLanguageService()->sL($GLOBALS['TCA'][$brokenLink['table_name']]['ctrl']['title'] ?? '');
251
            $brokenLink['target'] = (((string)($brokenLink['link_type'] ?? '') === 'db') ? 'id:' : '') . $brokenLink['url'];
252
            $brokenLink['language_code'] = $this->getLanguageCode((int)$brokenLink['language']);
253
        }
254
255
        return $this;
256
    }
257
258
    /**
259
     * Add localized page ids to the list of pages to get broken links from
260
     *
261
     * @param string $pageList
262
     * @param int $page
263
     * @param bool $checkHidden
264
     * @param string $languages
265
     * @return string
266
     */
267
    protected function addPageTranslationsToPageList(
268
        string $pageList,
269
        int $page,
270
        bool $checkHidden,
271
        string $languages = ''
272
    ): string {
273
        $queryBuilder = $this->connectionPool->getQueryBuilderForTable('pages');
274
        $queryBuilder->getRestrictions()->removeAll()->add(GeneralUtility::makeInstance(DeletedRestriction::class));
275
276
        $constraints[] = $queryBuilder->expr()->eq(
0 ignored issues
show
Comprehensibility Best Practice introduced by
$constraints was never initialized. Although not strictly required by PHP, it is generally a good practice to add $constraints = array(); before regardless.
Loading history...
277
            'l10n_parent',
278
            $queryBuilder->createNamedParameter($page, Connection::PARAM_INT)
279
        );
280
281
        if ($languages !== '') {
282
            $constraints[] = $queryBuilder->expr()->in(
283
                'sys_language_uid',
284
                $queryBuilder->createNamedParameter(
285
                    GeneralUtility::intExplode(',', $languages, true),
286
                    Connection::PARAM_INT_ARRAY
287
                )
288
            );
289
        }
290
291
        $result = $queryBuilder
292
            ->select('uid', 'hidden')
293
            ->from('pages')
294
            ->where(...$constraints)
295
            ->execute();
296
297
        while ($row = $result->fetch()) {
298
            if ($row['hidden'] === 0 || $checkHidden) {
299
                $pageList .= ',' . $row['uid'];
300
            }
301
        }
302
303
        return $pageList;
304
    }
305
306
    /**
307
     * Get language iso code and store it in the local property languageCodes
308
     *
309
     * @param int $languageId
310
     * @return string
311
     */
312
    protected function getLanguageCode(int $languageId): string
313
    {
314
        if ((bool)($this->languageCodes[$languageId] ?? false)) {
315
            return $this->languageCodes[$languageId];
316
        }
317
318
        $queryBuilder = $this->connectionPool->getQueryBuilderForTable('sys_language');
319
        $queryBuilder
320
            ->getRestrictions()
321
            ->removeAll()
322
            ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
323
324
        $langauge = $queryBuilder
325
            ->select('uid', 'language_isocode')
326
            ->from('sys_language')
327
            ->where($queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($languageId, \PDO::PARAM_INT)))
328
            ->setMaxResults(1)
329
            ->execute()
330
            ->fetch() ?: [];
331
332
        if (is_array($langauge) && $langauge !== []) {
333
            $this->languageCodes[(int)$langauge['uid']] = $langauge['language_isocode'];
334
            return $langauge['language_isocode'];
335
        }
336
337
        return '';
338
    }
339
340
    /**
341
     * Get localized page id and store it in the local property localizedPages
342
     *
343
     * @param int $parentId
344
     * @param int $languageId
345
     * @return int
346
     */
347
    protected function getLocalizedPageId(int $parentId, int $languageId): int
348
    {
349
        $identifier = $parentId . '-' . $languageId;
350
351
        if ((bool)($this->localizedPages[$identifier] ?? false)) {
352
            return $this->localizedPages[$identifier];
353
        }
354
355
        $queryBuilder = $this->connectionPool->getQueryBuilderForTable('pages');
356
        $queryBuilder
357
            ->getRestrictions()
358
            ->removeAll()
359
            ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
360
361
        $localizedPageId = (int)$queryBuilder
362
            ->select('uid')
363
            ->from('pages')
364
            ->where(
365
                $queryBuilder->expr()->eq(
366
                    $GLOBALS['TCA']['pages']['ctrl']['transOrigPointerField'],
367
                    $queryBuilder->createNamedParameter($parentId, \PDO::PARAM_INT)
368
                ),
369
                $queryBuilder->expr()->eq(
370
                    $GLOBALS['TCA']['pages']['ctrl']['languageField'],
371
                    $queryBuilder->createNamedParameter($languageId, \PDO::PARAM_INT)
372
                )
373
            )
374
            ->setMaxResults(1)
375
            ->execute()
376
            ->fetch()['uid'] ?: 0;
377
378
        if ($localizedPageId) {
379
            $this->localizedPages[$identifier] = $localizedPageId;
380
            return $localizedPageId;
381
        }
382
383
        return $parentId;
384
    }
385
386
    /**
387
     * @return LanguageService
388
     */
389
    protected function getLanguageService(): LanguageService
390
    {
391
        return $GLOBALS['LANG'];
392
    }
393
}
394