Passed
Push — master ( c012b2...0bdf3e )
by
unknown
13:54
created

LinkAnalyzerResult::getLanguageCode()   A

Complexity

Conditions 5
Paths 3

Size

Total Lines 26
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 18
dl 0
loc 26
rs 9.3554
c 0
b 0
f 0
cc 5
nc 3
nop 1
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\ConnectionPool;
22
use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
23
use TYPO3\CMS\Core\Exception\SiteNotFoundException;
24
use TYPO3\CMS\Core\Localization\LanguageService;
25
use TYPO3\CMS\Core\Site\SiteFinder;
26
use TYPO3\CMS\Core\Utility\GeneralUtility;
27
use TYPO3\CMS\Linkvalidator\LinkAnalyzer;
28
use TYPO3\CMS\Linkvalidator\Repository\BrokenLinkRepository;
29
use TYPO3\CMS\Linkvalidator\Repository\PagesRepository;
30
31
/**
32
 * Used to work with LinkAnalyzer results
33
 *
34
 * @internal
35
 */
36
class LinkAnalyzerResult
37
{
38
    /**
39
     * @var LinkAnalyzer
40
     */
41
    protected $linkAnalyzer;
42
43
    /**
44
     * @var BrokenLinkRepository
45
     */
46
    protected $brokenLinkRepository;
47
48
    /**
49
     * @var PagesRepository
50
     */
51
    protected $pagesRepository;
52
53
    /**
54
     * @var ConnectionPool
55
     */
56
    protected $connectionPool;
57
58
    /**
59
     * @var array
60
     */
61
    protected $brokenLinks = [];
62
63
    /**
64
     * @var array
65
     */
66
    protected $newBrokenLinkCounts = [];
67
68
    /**
69
     * @var array
70
     */
71
    protected $oldBrokenLinkCounts = [];
72
73
    /**
74
     * @var bool
75
     */
76
    protected $differentToLastResult = false;
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
        PagesRepository $pagesRepository
90
    ) {
91
        $this->linkAnalyzer = $linkAnalyzer;
92
        $this->brokenLinkRepository = $brokenLinkRepository;
93
        $this->connectionPool = $connectionPool;
94
        $this->pagesRepository = $pagesRepository;
95
    }
96
97
    /**
98
     * Call LinkAnalyzer with provided task configuration and process result values
99
     *
100
     * @param int    $page
101
     * @param int    $depth
102
     * @param array  $pageRow
103
     * @param array  $modTSconfig
104
     * @param array  $searchFields
105
     * @param array  $linkTypes
106
     * @param string $languages
107
     *
108
     * @return $this
109
     */
110
    public function getResultForTask(
111
        int $page,
112
        int $depth,
113
        array $pageRow,
114
        array $modTSconfig,
115
        array $searchFields = [],
116
        array $linkTypes = [],
117
        string $languages = ''
118
    ): self {
119
        $rootLineHidden = $this->pagesRepository->doesRootLineContainHiddenPages($pageRow);
120
        $checkHidden = $modTSconfig['checkhidden'] === 1;
121
122
        if ($rootLineHidden && !$checkHidden) {
123
            return $this;
124
        }
125
126
        $pageIds = $this->pagesRepository->getAllSubpagesForPage(
127
            $page,
128
            $depth,
129
            '',
130
            $checkHidden
131
        );
132
133
        if ($pageRow['hidden'] === 0 || $checkHidden) {
134
            $pageIds[] = $page;
135
        }
136
137
        if (empty($pageIds)) {
138
            return $this;
139
        }
140
141
        $languageIds = GeneralUtility::intExplode(',', $languages, true);
142
        $pageTranslations = $this->pagesRepository->getTranslationForPage(
143
            $page,
144
            '',
145
            $checkHidden,
146
            $languageIds
147
        );
148
149
        $pageIds = array_merge($pageIds, $pageTranslations);
150
151
        $this->linkAnalyzer->init($searchFields, $pageIds, $modTSconfig);
152
        $this->oldBrokenLinkCounts = $this->linkAnalyzer->getLinkCounts();
153
154
        $this->linkAnalyzer->getLinkStatistics($linkTypes, $checkHidden);
155
        $this->newBrokenLinkCounts = $this->linkAnalyzer->getLinkCounts();
156
157
        $this->brokenLinks = $this->brokenLinkRepository->getAllBrokenLinksForPages(
158
            $pageIds,
159
            $linkTypes,
160
            $searchFields,
161
            $languageIds
162
        );
163
164
        $this
165
            ->processLinkCounts($linkTypes)
166
            ->processBrokenLinks();
167
168
        return $this;
169
    }
170
171
    public function setBrokenLinks(array $brokenLinks): void
172
    {
173
        $this->brokenLinks = $brokenLinks;
174
    }
175
176
    public function getBrokenLinks(): array
177
    {
178
        return $this->brokenLinks;
179
    }
180
181
    public function setNewBrokenLinkCounts(array $newBrokenLinkCounts): void
182
    {
183
        $this->newBrokenLinkCounts = $newBrokenLinkCounts;
184
    }
185
186
    public function getNewBrokenLinkCounts(): array
187
    {
188
        return $this->newBrokenLinkCounts;
189
    }
190
191
    public function setOldBrokenLinkCounts(array $oldBrokenLinkCounts): void
192
    {
193
        $this->oldBrokenLinkCounts = $oldBrokenLinkCounts;
194
    }
195
196
    public function getOldBrokenLinkCounts(): array
197
    {
198
        return $this->oldBrokenLinkCounts;
199
    }
200
201
    public function getTotalBrokenLinksCount(): int
202
    {
203
        return $this->newBrokenLinkCounts['total'] ?? 0;
204
    }
205
206
    public function isDifferentToLastResult(): bool
207
    {
208
        return $this->differentToLastResult;
209
    }
210
211
    /**
212
     * Process the link counts (old and new) and ensures that all link types are available in the array
213
     *
214
     * @param array<int, string> $linkTypes list of link types
215
     * @return LinkAnalyzerResult
216
     */
217
    protected function processLinkCounts(array $linkTypes): self
218
    {
219
        foreach (array_keys($linkTypes) as $linkType) {
220
            if (!isset($this->newBrokenLinkCounts[$linkType])) {
221
                $this->newBrokenLinkCounts[$linkType] = 0;
222
            }
223
            if (!isset($this->oldBrokenLinkCounts[$linkType])) {
224
                $this->oldBrokenLinkCounts[$linkType] = 0;
225
            }
226
            if ($this->newBrokenLinkCounts[$linkType] !== $this->oldBrokenLinkCounts[$linkType]) {
227
                $this->differentToLastResult = true;
228
            }
229
        }
230
231
        return $this;
232
    }
233
234
    /**
235
     * Process broken link values and assign them to new variables which are used in the templates
236
     * shipped by the core but can also be used in custom templates. The raw data is untouched and
237
     * can also still be used in custom templates.
238
     *
239
     * @return LinkAnalyzerResult
240
     */
241
    protected function processBrokenLinks(): self
242
    {
243
        foreach ($this->brokenLinks as $key => &$brokenLink) {
244
            $fullRecord = BackendUtility::getRecord($brokenLink['table_name'], $brokenLink['record_uid']);
245
246
            if ($fullRecord !== null) {
247
                $brokenLink['full_record'] = $fullRecord;
248
                $brokenLink['record_title'] = BackendUtility::getRecordTitle($brokenLink['table_name'], $fullRecord);
249
            }
250
251
            $brokenLink['real_pid'] = ((int)($brokenLink['language'] ?? 0) > 0 && (string)($brokenLink['table_name'] ?? '') !== 'pages')
252
                ? $this->getLocalizedPageId((int)$brokenLink['record_pid'], (int)$brokenLink['language'])
253
                : $brokenLink['record_pid'];
254
            $pageRecord = BackendUtility::getRecord('pages', $brokenLink['real_pid']);
255
256
            try {
257
                $site = GeneralUtility::makeInstance(SiteFinder::class)->getSiteByPageId($brokenLink['real_pid']);
258
                $languageCode = $site->getLanguageById($brokenLink['language'])->getTwoLetterIsoCode() ?: 'default';
259
            } catch (SiteNotFoundException | \InvalidArgumentException $e) {
260
                $languageCode = 'default';
261
            }
262
            if ($pageRecord !== null) {
263
                $brokenLink['page_record'] = $pageRecord;
264
            }
265
266
            $brokenLink['record_type'] = $this->getLanguageService()->sL($GLOBALS['TCA'][$brokenLink['table_name']]['ctrl']['title'] ?? '');
267
            $brokenLink['target'] = (((string)($brokenLink['link_type'] ?? '') === 'db') ? 'id:' : '') . $brokenLink['url'];
268
            $brokenLink['language_code'] = $languageCode;
269
        }
270
271
        return $this;
272
    }
273
274
    /**
275
     * Get localized page id and store it in the local property localizedPages
276
     *
277
     * @param int $parentId
278
     * @param int $languageId
279
     * @return int
280
     */
281
    protected function getLocalizedPageId(int $parentId, int $languageId): int
282
    {
283
        $identifier = $parentId . '-' . $languageId;
284
285
        if ((bool)($this->localizedPages[$identifier] ?? false)) {
286
            return $this->localizedPages[$identifier];
287
        }
288
289
        $queryBuilder = $this->connectionPool->getQueryBuilderForTable('pages');
290
        $queryBuilder
291
            ->getRestrictions()
292
            ->removeAll()
293
            ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
294
295
        $localizedPageId = (int)$queryBuilder
296
            ->select('uid')
297
            ->from('pages')
298
            ->where(
299
                $queryBuilder->expr()->eq(
300
                    $GLOBALS['TCA']['pages']['ctrl']['transOrigPointerField'],
301
                    $queryBuilder->createNamedParameter($parentId, \PDO::PARAM_INT)
302
                ),
303
                $queryBuilder->expr()->eq(
304
                    $GLOBALS['TCA']['pages']['ctrl']['languageField'],
305
                    $queryBuilder->createNamedParameter($languageId, \PDO::PARAM_INT)
306
                )
307
            )
308
            ->setMaxResults(1)
309
            ->execute()
310
            ->fetch()['uid'] ?: 0;
311
312
        if ($localizedPageId) {
313
            $this->localizedPages[$identifier] = $localizedPageId;
314
            return $localizedPageId;
315
        }
316
317
        return $parentId;
318
    }
319
320
    /**
321
     * @return LanguageService
322
     */
323
    protected function getLanguageService(): LanguageService
324
    {
325
        return $GLOBALS['LANG'];
326
    }
327
}
328