• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

FluidTYPO3 / flux / 27757211628

18 Jun 2026 11:46AM UTC coverage: 89.162% (-3.5%) from 92.646%
27757211628

Pull #2288

github

web-flow
Merge 967f03443 into 2614049c6
Pull Request #2288: [FEATURE] Prepare for v14 support

210 of 348 new or added lines in 56 files covered. (60.34%)

121 existing lines in 9 files now uncovered.

6228 of 6985 relevant lines covered (89.16%)

40.84 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

57.55
/Classes/Integration/PreviewView.php
1
<?php
2
namespace FluidTYPO3\Flux\Integration;
3

4
/*
5
 * This file is part of the FluidTYPO3/Flux project under GPLv2 or later.
6
 *
7
 * For the full copyright and license information, please read the
8
 * LICENSE.md file that was distributed with this source code.
9
 */
10

11
use FluidTYPO3\Flux\Enum\FormOption;
12
use FluidTYPO3\Flux\Enum\PreviewOption;
13
use FluidTYPO3\Flux\Form;
14
use FluidTYPO3\Flux\Hooks\HookHandler;
15
use FluidTYPO3\Flux\Provider\ProviderInterface;
16
use FluidTYPO3\Flux\Proxy\SiteFinderProxy;
17
use FluidTYPO3\Flux\Service\WorkspacesAwareRecordService;
18
use FluidTYPO3\Flux\Utility\RecursiveArrayUtility;
19
use FluidTYPO3\Flux\Utility\RequestResolver;
20
use FluidTYPO3\Flux\Utility\VersionUtility;
21
use TYPO3\CMS\Backend\Utility\BackendUtility;
22
use TYPO3\CMS\Backend\View\BackendViewFactory;
23
use TYPO3\CMS\Backend\View\Drawing\DrawingConfiguration;
24
use TYPO3\CMS\Backend\View\PageLayoutContext;
25
use TYPO3\CMS\Backend\View\PageViewMode;
26
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
27
use TYPO3\CMS\Core\Configuration\Features;
28
use TYPO3\CMS\Core\Domain\RecordFactory;
29
use TYPO3\CMS\Core\Localization\LanguageService;
30
use TYPO3\CMS\Core\Utility\GeneralUtility;
31
use TYPO3\CMS\Extbase\Configuration\ConfigurationManager;
32
use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface;
33
use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface;
34
use TYPO3Fluid\Fluid\View\TemplateView;
35

36
class PreviewView extends TemplateView
37
{
38
    protected array $templates = [
39
        'gridToggle' => '<div class="grid-visibility-toggle" data-toggle-uid="%s">%s</div>',
40
        'link' => '<a href="%s" title="%s" class="btn btn-default btn-sm">%s %s</a>'
41
    ];
42

43
    protected ConfigurationManagerInterface $configurationManager;
44
    protected WorkspacesAwareRecordService $workspacesAwareRecordService;
45

46
    public function __construct(?RenderingContextInterface $context = null)
47
    {
48
        parent::__construct($context);
8✔
49

50
        /** @var ConfigurationManagerInterface $configurationManager */
51
        $configurationManager = GeneralUtility::makeInstance(ConfigurationManager::class);
8✔
52
        $this->configurationManager = $configurationManager;
8✔
53

54
        /** @var WorkspacesAwareRecordService $workspacesAwareRecordService */
55
        $workspacesAwareRecordService = GeneralUtility::makeInstance(WorkspacesAwareRecordService::class);
8✔
56
        $this->workspacesAwareRecordService = $workspacesAwareRecordService;
8✔
57
    }
58

59
    public function getPreview(ProviderInterface $provider, array $row, bool $withoutGrid = false): string
60
    {
61
        $form = $provider->getForm($row);
12✔
62
        $options = $this->getPreviewOptions($form);
12✔
63
        $mode = $this->getOptionMode($options);
12✔
64
        $previewContent = (string) $this->renderPreviewSection($provider, $row, $form);
12✔
65

66
        if ($withoutGrid || PreviewOption::MODE_NONE === $mode || !is_object($form)) {
12✔
67
            return $previewContent;
4✔
68
        }
69

70
        $gridContent = $this->renderGrid($provider, $row, $form);
8✔
71
        $collapsedClass = '';
8✔
72
        if (in_array($row['uid'], (array) json_decode($this->getCookie() ?? ''))) {
8✔
73
            $collapsedClass = ' flux-grid-hidden';
8✔
74
        }
75
        $gridContent = sprintf(
8✔
76
            '<div class="flux-collapse%s" data-grid-uid="%d">%s</div>',
8✔
77
            $collapsedClass,
8✔
78
            $row['uid'],
8✔
79
            $gridContent
8✔
80
        );
8✔
81
        if (PreviewOption::MODE_APPEND === $mode) {
8✔
82
            $previewContent = $previewContent . $gridContent;
4✔
83
        } elseif (PreviewOption::MODE_PREPEND === $mode) {
4✔
84
            $previewContent = $gridContent . $previewContent;
4✔
85
        }
86

87
        $previewContent = trim($previewContent);
8✔
88

89
        return HookHandler::trigger(
8✔
90
            HookHandler::PREVIEW_RENDERED,
8✔
91
            ['form' => $form, 'preview' => $previewContent]
8✔
92
        )['preview'];
8✔
93
    }
94

95
    protected function getPreviewOptions(?Form $form = null): array
96
    {
97
        if (!is_object($form) || !$form->hasOption(PreviewOption::PREVIEW)) {
24✔
98
            return [
12✔
99
                PreviewOption::MODE => $this->getOptionMode(),
12✔
100
                PreviewOption::TOGGLE => $this->getOptionToggle()
12✔
101
            ];
12✔
102
        }
103

104
        return (array) $form->getOption(PreviewOption::PREVIEW);
12✔
105
    }
106

107
    protected function getOptionMode(array $options = []): string
108
    {
109
        return $options[PreviewOption::MODE] ?? PreviewOption::MODE_APPEND;
28✔
110
    }
111

112
    protected function getOptionToggle(array $options = []): bool
113
    {
114
        return (bool) ($options[PreviewOption::TOGGLE] ?? true);
12✔
115
    }
116

117
    protected function renderPreviewSection(ProviderInterface $provider, array $row, ?Form $form = null): ?string
118
    {
119
        $templatePathAndFilename = $provider->getTemplatePathAndFilename($row);
8✔
120
        if (!$templatePathAndFilename) {
8✔
121
            return null;
4✔
122
        }
123
        $extensionKey = $provider->getExtensionKey($row);
4✔
124

125
        $templateVariables = $provider->getTemplateVariables($row);
4✔
126
        $flexformVariables = $provider->getFlexFormValues($row);
4✔
127
        $variables = RecursiveArrayUtility::merge($templateVariables, $flexformVariables);
4✔
128
        $variables['row'] = $row;
4✔
129
        $variables['record'] = $row;
4✔
130

131
        if (is_object($form)) {
4✔
132
            $formLabel = $form->getLabel();
4✔
133
            $label = $this->getLanguageService()->sL((string) $formLabel);
4✔
134
            $variables['label'] = $label;
4✔
135
        }
136

137
        $renderingContext = $this->getRenderingContext();
4✔
138
        $renderingContext->setControllerName($provider->getControllerNameFromRecord($row));
4✔
139
        $renderingContext->setControllerAction($provider->getControllerActionFromRecord($row));
4✔
140
        $renderingContext->getTemplatePaths()->setTemplatePathAndFilename($templatePathAndFilename);
4✔
141
        return $this->renderSection('Preview', $variables, true);
4✔
142
    }
143

144
    protected function renderGrid(ProviderInterface $provider, array $row, Form $form): string
145
    {
146
        $content = '';
12✔
147
        $grid = $provider->getGrid($row);
12✔
148
        if ($grid->hasChildren()) {
12✔
149
            $options = $this->getPreviewOptions($form);
8✔
150
            if ($this->getOptionToggle($options)) {
8✔
151
                $content = $this->drawGridToggle($row, $content);
8✔
152
            }
153

154
            // Live-patching TCA to add items, which will be read by the BackendLayoutView in order to read
155
            // the LLL labels of individual columns. Unfortunately, BackendLayoutView calls functions in a way
156
            // that it is not possible to overrule the colPos values via the BackendLayout without creating an
157
            // XCLASS - so a bit of runtime TCA patching is preferable.
158
            $tcaBackup = $GLOBALS['TCA']['tt_content']['columns']['colPos']['config']['items'];
8✔
159
            $GLOBALS['TCA']['tt_content']['columns']['colPos']['config']['items'] = array_merge(
8✔
160
                $GLOBALS['TCA']['tt_content']['columns']['colPos']['config']['items'],
8✔
161
                $grid->buildExtendedBackendLayoutArray($row['uid'])['__items']
8✔
162
            );
8✔
163

164
            $pageUid = $row['pid'];
8✔
165
            if (($workspaceId = $this->getBackendUser()->workspace) > 0) {
8✔
166
                $workspaceVersion = $this->fetchWorkspaceVersionOfRecord($workspaceId, $row['uid']);
4✔
167
                $pageUid = $workspaceVersion['pid'] ?? $pageUid;
4✔
168
            }
169
            $pageLayoutView = $this->getInitializedBackendLayoutView($provider, $row);
8✔
170

171
            $content .= $pageLayoutView->drawContent(
8✔
172
                $GLOBALS['TYPO3_REQUEST'],
8✔
173
                $pageLayoutView->getContext(),
8✔
174
                $form->getOption(FormOption::RECORD_TABLE) === 'pages' // render unused area only for "pages"
8✔
175
            );
8✔
176

177
            $GLOBALS['TCA']['tt_content']['columns']['colPos']['config']['items'] = $tcaBackup;
8✔
178
        }
179
        return $content;
12✔
180
    }
181

182
    protected function drawGridToggle(array $row, string $content): string
183
    {
184
        return sprintf($this->templates['gridToggle'], $row['uid'], $content);
8✔
185
    }
186

187
    protected function getInitializedBackendLayoutView(ProviderInterface $provider, array $row): BackendLayoutRenderer
188
    {
UNCOV
189
        $pageId = (int) $row['pid'];
×
UNCOV
190
        $pageRecord = $this->workspacesAwareRecordService->getSingle('pages', '*', $pageId);
×
UNCOV
191
        $moduleData = $this->getBackendUser()->getModuleData('web_layout', '');
×
UNCOV
192
        $showHiddenRecords = (int) ($moduleData['tt_content_showHidden'] ?? 1);
×
193

194
        // For all elements to be shown in draft workspaces & to also show hidden elements by default if user hasn't
195
        // disabled the option analog behavior to the PageLayoutController at the end of menuConfig()
UNCOV
196
        if ($this->getActiveWorkspaceId() !== 0 || !$showHiddenRecords) {
×
197
            $moduleData['tt_content_showHidden'] = 1;
×
198
        }
199

NEW
200
        $parentRecordUid = ($row['l18n_parent'] ?? 0) > 0
×
NEW
201
            ? $row['l18n_parent']
×
NEW
202
            : (($row['t3ver_oid'] ?? null) ?: $row['uid']);
×
203

UNCOV
204
        $backendLayout = $provider->getGrid($row)->buildBackendLayout($parentRecordUid);
×
UNCOV
205
        $layoutConfiguration = $backendLayout->getStructure();
×
206

NEW
207
        if (VersionUtility::isCoreBelow14()) {
×
208
            /** @var Features $features */
NEW
209
            $features = GeneralUtility::makeInstance(Features::class);
×
NEW
210
            $fluidBasedLayoutFeatureEnabled = $features->isFeatureEnabled('fluidBasedPageModule');
×
NEW
211
            if (!$fluidBasedLayoutFeatureEnabled) {
×
NEW
212
                throw new \UnexpectedValueException(
×
NEW
213
                    'Flux requires the "fluidBasedPageModule" feature flag to be ENABLED',
×
NEW
214
                    1776970729
×
UNCOV
215
                );
×
216
            }
217
        }
218

219
        /** @var SiteFinderProxy $siteFinder */
NEW
220
        $siteFinder = GeneralUtility::makeInstance(SiteFinderProxy::class);
×
NEW
221
        $site = $siteFinder->getSiteByPageId($pageId);
×
NEW
222
        $language = null;
×
NEW
223
        if (($row['sys_language_uid'] ?? -1) >= 0) {
×
NEW
224
            $language = $site->getLanguageById((int) $row['sys_language_uid']);
×
225
        }
226

NEW
227
        $request = RequestResolver::getRequest();
×
228

NEW
229
        if (VersionUtility::isCoreAtLeast14()) {
×
230
            // @TODO: fix this instancing
NEW
231
            $context = GeneralUtility::makeInstance(
×
NEW
232
                PageLayoutContext::class,
×
NEW
233
                $request->getAttribute('pageContext'),
×
NEW
234
                $backendLayout,
×
NEW
235
                $configuration = DrawingConfiguration::create($backendLayout, [], PageViewMode::LayoutView),
×
NEW
236
                $request
×
NEW
237
            );
×
NEW
238
        } elseif (VersionUtility::isCoreAtLeast13()) {
×
NEW
239
            $configuration = DrawingConfiguration::create($backendLayout, [], PageViewMode::LayoutView);
×
NEW
240
            $configuration->setSelectedLanguageId($language->getLanguageId());
×
241

242
            /** @var PageLayoutContext $context */
NEW
243
            $context = GeneralUtility::makeInstance(
×
NEW
244
                PageLayoutContext::class,
×
NEW
245
                $this->fetchPageRecordWithoutOverlay($pageId),
×
NEW
246
                $backendLayout,
×
NEW
247
                $site,
×
NEW
248
                $configuration,
×
NEW
249
                $request
×
NEW
250
            );
×
251
        } else {
252
            /** @var PageLayoutContext $context */
NEW
253
            $context = GeneralUtility::makeInstance(
×
NEW
254
                PageLayoutContext::class,
×
NEW
255
                $this->fetchPageRecordWithoutOverlay($pageId),
×
NEW
256
                $backendLayout
×
NEW
257
            );
×
NEW
258
            $configuration = $context->getDrawingConfiguration();
×
259
        }
260

NEW
261
        if (isset($language)) {
×
NEW
262
             $context = $context->cloneForLanguage($language);
×
263
        }
264

NEW
265
        if (method_exists($configuration, 'setActiveColumns')) {
×
NEW
266
            $configuration->setActiveColumns($backendLayout->getColumnPositionNumbers());
×
267
        }
268

NEW
269
        if (isset($language) && method_exists($configuration, 'setSelectedLanguageId')) {
×
NEW
270
            $configuration->setSelectedLanguageId($language->getLanguageId());
×
271
        }
272

NEW
273
        $backendLayoutRenderer = $this->createBackendLayoutRenderer($context);
×
274

NEW
275
        $backendLayoutRenderer->setContext($context);
×
276

NEW
277
        return $backendLayoutRenderer;
×
278
    }
279

280
    /**
281
     * @codeCoverageIgnore
282
     */
283
    protected function createBackendLayoutRenderer(PageLayoutContext $context): BackendLayoutRenderer
284
    {
285
        if (VersionUtility::isCoreAtLeast13()) {
286
            /** @var BackendViewFactory $backendViewFactory */
287
            $backendViewFactory = GeneralUtility::getContainer()->get(BackendViewFactory::class);
288
            /** @var RecordFactory $recordFactory */
289
            $recordFactory = GeneralUtility::makeInstance(RecordFactory::class);
290
            /** @var BackendLayoutRenderer $backendLayoutRenderer */
291
            $backendLayoutRenderer = GeneralUtility::makeInstance(
292
                BackendLayoutRenderer::class,
293
                $backendViewFactory,
294
                $recordFactory
295
            );
296
        } else {
297
            /** @var BackendViewFactory $backendViewFactory */
298
            $backendViewFactory = GeneralUtility::getContainer()->get(BackendViewFactory::class);
299
            /** @var BackendLayoutRenderer $backendLayoutRenderer */
300
            $backendLayoutRenderer = GeneralUtility::makeInstance(
301
                BackendLayoutRenderer::class,
302
                $backendViewFactory
303
            );
304
        }
305
        return $backendLayoutRenderer;
306
    }
307

308
    /**
309
     * @codeCoverageIgnore
310
     */
311
    protected function fetchWorkspaceVersionOfRecord(int $workspaceId, int $recordUid): ?array
312
    {
313
        /** @var array|false $workspaceVersion */
314
        $workspaceVersion = BackendUtility::getWorkspaceVersionOfRecord($workspaceId, 'tt_content', $recordUid);
315
        return $workspaceVersion ?: null;
316
    }
317

318
    /**
319
     * @codeCoverageIgnore
320
     * @return array|false
321
     */
322
    protected function checkAccessToPage(int $pageId)
323
    {
324
        return BackendUtility::readPageAccess($pageId, '');
325
    }
326

327
    /**
328
     * @codeCoverageIgnore
329
     */
330
    protected function fetchPageRecordWithoutOverlay(int $pageId): ?array
331
    {
332
        return BackendUtility::getRecord('pages', $pageId);
333
    }
334

335
    /**
336
     * @codeCoverageIgnore
337
     */
338
    protected function getBackendUser(): BackendUserAuthentication
339
    {
340
        return RequestResolver::getBackendUser();
341
    }
342

343
    /**
344
     * @codeCoverageIgnore
345
     */
346
    protected function getLanguageService(): LanguageService
347
    {
348
        return $GLOBALS['LANG'];
349
    }
350

351
    /**
352
     * @codeCoverageIgnore
353
     */
354
    protected function getCookie(): ?string
355
    {
356
        return $_COOKIE['fluxCollapseStates'] ?? null;
357
    }
358

359
    /**
360
     * @codeCoverageIgnore
361
     */
362
    protected function getActiveWorkspaceId(): int
363
    {
364
        return (int) ($GLOBALS['BE_USER']->workspace ?? 0);
365
    }
366
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc