Completed
Push — master ( 7318d6...70b3a0 )
by
unknown
26:56 queued 08:19
created

ContentFetcher::getTranslationData()   C

Complexity

Conditions 12
Paths 14

Size

Total Lines 55
Code Lines 36

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 12
eloc 36
c 1
b 0
f 0
nc 14
nop 2
dl 0
loc 55
rs 6.9666

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
declare(strict_types = 1);
3
4
namespace TYPO3\CMS\Backend\View\BackendLayout;
5
6
/*
7
 * This file is part of the TYPO3 CMS project.
8
 *
9
 * It is free software; you can redistribute it and/or modify it under
10
 * the terms of the GNU General Public License, either version 2
11
 * of the License, or any later version.
12
 *
13
 * For the full copyright and license information, please read the
14
 * LICENSE.txt file that was distributed with this source code.
15
 *
16
 * The TYPO3 project - inspiring people to share!
17
 */
18
19
use Doctrine\DBAL\Driver\Statement;
20
use TYPO3\CMS\Backend\Utility\BackendUtility;
21
use TYPO3\CMS\Backend\View\PageLayoutView;
22
use TYPO3\CMS\Core\Database\ConnectionPool;
23
use TYPO3\CMS\Core\Database\Query\QueryBuilder;
24
use TYPO3\CMS\Core\Database\Query\Restriction\BackendWorkspaceRestriction;
25
use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
26
use TYPO3\CMS\Core\Localization\LanguageService;
27
use TYPO3\CMS\Core\Messaging\FlashMessage;
28
use TYPO3\CMS\Core\Messaging\FlashMessageService;
29
use TYPO3\CMS\Core\Utility\GeneralUtility;
30
31
/**
32
 * Class responsible for fetching the content data related to a BackendLayout
33
 *
34
 * - Reads content records
35
 * - Performs workspace overlay on records
36
 * - Capable of returning all records in active language as flat array
37
 * - Capable of returning records for a given column in a given (optional) language
38
 * - Capable of returning translation data (brief info about translation consistency)
39
 */
40
class ContentFetcher
41
{
42
    /**
43
     * @var BackendLayout
44
     */
45
    protected $backendLayout;
46
47
    /**
48
     * @var array
49
     */
50
    protected $fetchedContentRecords = [];
51
52
    /**
53
     * Stores whether a certain language has translations in it
54
     *
55
     * @var array
56
     */
57
    protected $languageHasTranslationsCache = [];
58
59
    public function __construct(BackendLayout $backendLayout)
60
    {
61
        $this->backendLayout = $backendLayout;
62
    }
63
64
    public function setBackendLayout(BackendLayout $backendLayout): void
65
    {
66
        $this->backendLayout = $backendLayout;
67
    }
68
69
    /**
70
     * Gets content records per column.
71
     * This is required for correct workspace overlays.
72
     *
73
     * @param int|null $columnNumber
74
     * @param int|null $languageId
75
     * @return array Associative array for each column (colPos) or for all columns if $columnNumber is null
76
     */
77
    public function getContentRecordsPerColumn(?int $columnNumber = null, ?int $languageId = null): iterable
78
    {
79
        if (empty($this->fetchedContentRecords)) {
80
            $queryBuilder = $this->getQueryBuilder();
81
            $records = $this->getResult($queryBuilder->execute());
0 ignored issues
show
Bug introduced by
It seems like $queryBuilder->execute() can also be of type integer; however, parameter $result of TYPO3\CMS\Backend\View\B...entFetcher::getResult() does only seem to accept Doctrine\DBAL\Driver\Statement, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

81
            $records = $this->getResult(/** @scrutinizer ignore-type */ $queryBuilder->execute());
Loading history...
82
            foreach ($records as $record) {
83
                $recordLanguage = (int)$record['sys_language_uid'];
84
                $recordColumnNumber = (int)$record['colPos'];
85
                $this->fetchedContentRecords[$recordLanguage][$recordColumnNumber][] = $record;
86
            }
87
        }
88
89
        $languageId = $languageId ?? $this->backendLayout->getDrawingConfiguration()->getLanguageColumnsPointer();
90
91
        $contentByLanguage = &$this->fetchedContentRecords[$languageId];
92
93
        if ($columnNumber === null) {
94
            return $contentByLanguage ?? [];
95
        }
96
97
        return $contentByLanguage[$columnNumber] ?? [];
98
    }
99
100
    public function getFlatContentRecords(): iterable
101
    {
102
        $languageId = $this->backendLayout->getDrawingConfiguration()->getLanguageColumnsPointer();
103
        $contentRecords = $this->getContentRecordsPerColumn(null, $languageId);
104
        return empty($contentRecords) ? [] : array_merge(...$contentRecords);
105
    }
106
107
    public function getUnusedRecords(): iterable
108
    {
109
        $unrendered = [];
110
        $knownColumnPositionNumbers = $this->backendLayout->getColumnPositionNumbers();
111
        $rememberer = $this->backendLayout->getRecordRememberer();
112
        foreach ($this->fetchedContentRecords[$this->backendLayout->getDrawingConfiguration()->getLanguageColumnsPointer()] ?? [] as $contentRecordsInColumn) {
113
            foreach ($contentRecordsInColumn as $contentRecord) {
114
                if (!$rememberer->isRemembered((int)$contentRecord['uid']) && !in_array($contentRecord['colPos'], $knownColumnPositionNumbers)) {
115
                    $unrendered[] = $contentRecord;
116
                }
117
            }
118
        }
119
        return $unrendered;
120
    }
121
122
    public function getTranslationData(iterable $contentElements, int $language): array
123
    {
124
        $configuration = $this->backendLayout->getDrawingConfiguration();
125
126
        if ($language === 0) {
127
            return [];
128
        }
129
130
        if (!isset($this->languageHasTranslationsCache[$language])) {
131
            if ($language) {
132
                $contentRecordsInDefaultLanguage = $this->getContentRecordsPerColumn(null, 0);
133
                if (!empty($contentRecordsInDefaultLanguage)) {
134
                    $contentRecordsInDefaultLanguage = array_merge(...$contentRecordsInDefaultLanguage);
135
                }
136
            } else {
137
                $contentRecordsInDefaultLanguage = $contentElements;
138
            }
139
            $untranslatedRecordUids = array_flip(array_column($contentRecordsInDefaultLanguage, 'uid'));
140
141
            foreach ($contentElements as $contentElement) {
142
                if ((int)$contentElement['l18n_parent'] === 0) {
143
                    $this->languageHasTranslationsCache[$language]['hasStandAloneContent'] = true;
144
                    $this->languageHasTranslationsCache[$language]['mode'] = 'free';
145
                }
146
                if ((int)$contentElement['l18n_parent'] > 0) {
147
                    $this->languageHasTranslationsCache[$language]['hasTranslations'] = true;
148
                    $this->languageHasTranslationsCache[$language]['mode'] = 'connected';
149
                }
150
                if ((int)$contentElement['l10n_source'] > 0) {
151
                    unset($untranslatedRecordUids[(int)$contentElement['l10n_source']]);
152
                }
153
            }
154
            if (!isset($this->languageHasTranslationsCache[$language])) {
155
                $this->languageHasTranslationsCache[$language]['hasTranslations'] = false;
156
            }
157
            $this->languageHasTranslationsCache[$language]['untranslatedRecordUids'] = array_keys($untranslatedRecordUids);
158
159
            // Check for inconsistent translations, force "mixed" mode and dispatch a FlashMessage to user if such a case is encountered.
160
            if (isset($this->languageHasTranslationsCache[$language]['hasStandAloneContent'])
161
                && $this->languageHasTranslationsCache[$language]['hasTranslations']
162
            ) {
163
                $this->languageHasTranslationsCache[$language]['mode'] = 'mixed';
164
                $siteLanguage = $configuration->getSiteLanguage($language);
165
                $message = GeneralUtility::makeInstance(
166
                    FlashMessage::class,
167
                    sprintf($this->getLanguageService()->getLL('staleTranslationWarning'), $siteLanguage->getTitle()),
168
                    sprintf($this->getLanguageService()->getLL('staleTranslationWarningTitle'), $siteLanguage->getTitle()),
169
                    FlashMessage::WARNING
170
                );
171
                $service = GeneralUtility::makeInstance(FlashMessageService::class);
172
                $queue = $service->getMessageQueueByIdentifier();
173
                $queue->addMessage($message);
174
            }
175
        }
176
        return $this->languageHasTranslationsCache[$language];
177
    }
178
179
    protected function getQueryBuilder(): QueryBuilder
180
    {
181
        $fields = ['*'];
182
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
183
            ->getQueryBuilderForTable('tt_content');
184
        $queryBuilder->getRestrictions()
185
            ->removeAll()
186
            ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
187
            ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
188
        $queryBuilder
189
            ->select(...$fields)
190
            ->from('tt_content');
191
192
        $queryBuilder->andWhere(
193
            $queryBuilder->expr()->eq(
194
                'tt_content.pid',
195
                $queryBuilder->createNamedParameter($this->backendLayout->getDrawingConfiguration()->getPageId(), \PDO::PARAM_INT)
196
            )
197
        );
198
199
        $additionalConstraints = [];
200
        $parameters = [
201
            'table' => 'tt_content',
202
            'fields' => $fields,
203
            'groupBy' => null,
204
            'orderBy' => null
205
        ];
206
        foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][PageLayoutView::class]['modifyQuery'] ?? [] as $className) {
207
            $hookObject = GeneralUtility::makeInstance($className);
208
            if (method_exists($hookObject, 'modifyQuery')) {
209
                $hookObject->modifyQuery(
210
                    $parameters,
211
                    'tt_content',
212
                    $this->backendLayout->getDrawingConfiguration()->getPageId(),
213
                    $additionalConstraints,
214
                    $fields,
215
                    $queryBuilder
216
                );
217
            }
218
        }
219
220
        return $queryBuilder;
221
    }
222
223
    protected function getResult(Statement $result): array
224
    {
225
        $output = [];
226
        while ($row = $result->fetch()) {
227
            BackendUtility::workspaceOL('tt_content', $row, -99, true);
228
            if ($row) {
229
                $output[] = $row;
230
            }
231
        }
232
        return $output;
233
    }
234
235
    protected function getLanguageService(): LanguageService
236
    {
237
        return $GLOBALS['LANG'];
238
    }
239
}
240