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

FluidTYPO3 / flux / 17904759627

15 Sep 2025 08:47AM UTC coverage: 90.676% (-2.1%) from 92.767%
17904759627

push

github

NamelessCoder
[TASK] Set beta stability

6924 of 7636 relevant lines covered (90.68%)

9.49 hits per line

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

67.46
/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\Integration\Overrides\PageLayoutView;
16
use FluidTYPO3\Flux\Provider\ProviderInterface;
17
use FluidTYPO3\Flux\Proxy\SiteFinderProxy;
18
use FluidTYPO3\Flux\Service\WorkspacesAwareRecordService;
19
use FluidTYPO3\Flux\Utility\ExtensionNamingUtility;
20
use FluidTYPO3\Flux\Utility\RecursiveArrayUtility;
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\EventDispatcher\EventDispatcher;
30
use TYPO3\CMS\Core\Localization\LanguageService;
31
use TYPO3\CMS\Core\Utility\GeneralUtility;
32
use TYPO3\CMS\Core\Utility\VersionNumberUtility;
33
use TYPO3\CMS\Extbase\Configuration\ConfigurationManager;
34
use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface;
35
use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface;
36
use TYPO3Fluid\Fluid\View\TemplateView;
37

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

45
    protected ConfigurationManagerInterface $configurationManager;
46
    protected WorkspacesAwareRecordService $workspacesAwareRecordService;
47

48
    public function __construct(RenderingContextInterface $context = null)
49
    {
50
        parent::__construct($context);
5✔
51

52
        /** @var ConfigurationManagerInterface $configurationManager */
53
        $configurationManager = GeneralUtility::makeInstance(ConfigurationManager::class);
5✔
54
        $this->configurationManager = $configurationManager;
5✔
55

56
        /** @var WorkspacesAwareRecordService $workspacesAwareRecordService */
57
        $workspacesAwareRecordService = GeneralUtility::makeInstance(WorkspacesAwareRecordService::class);
5✔
58
        $this->workspacesAwareRecordService = $workspacesAwareRecordService;
5✔
59
    }
60

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

68
        if ($withoutGrid || PreviewOption::MODE_NONE === $mode || !is_object($form)) {
3✔
69
            return $previewContent;
1✔
70
        }
71

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

89
        $previewContent = trim($previewContent);
2✔
90

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

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

106
        return (array) $form->getOption(PreviewOption::PREVIEW);
3✔
107
    }
108

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

114
    protected function getOptionToggle(array $options = []): bool
115
    {
116
        return (boolean) ($options[PreviewOption::TOGGLE] ?? true);
5✔
117
    }
118

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

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

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

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

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

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

166
            $pageUid = $row['pid'];
4✔
167
            if (($workspaceId = $this->getBackendUser()->workspace) > 0) {
4✔
168
                $workspaceVersion = $this->fetchWorkspaceVersionOfRecord($workspaceId, $row['uid']);
1✔
169
                $pageUid = $workspaceVersion['pid'] ?? $pageUid;
1✔
170
            }
171
            $pageLayoutView = $this->getInitializedPageLayoutView($provider, $row);
4✔
172
            if ($pageLayoutView instanceof BackendLayoutRenderer) {
4✔
173
                if (version_compare(VersionNumberUtility::getCurrentTypo3Version(), '12.0', '>=')) {
2✔
174
                    $content .= $pageLayoutView->drawContent(
×
175
                        $GLOBALS['TYPO3_REQUEST'],
×
176
                        $pageLayoutView->getContext(),
×
177
                        $form->getOption(FormOption::RECORD_TABLE) === 'pages' // render unused area only for "pages"
×
178
                    );
×
179
                } else {
180
                    $content .= $pageLayoutView->drawContent(false);
2✔
181
                }
182
            } elseif (method_exists($pageLayoutView, 'start') && method_exists($pageLayoutView, 'generateList')) {
2✔
183
                $pageLayoutView->start($pageUid, 'tt_content', 0);
1✔
184
                $pageLayoutView->generateList();
1✔
185
                $content .= $pageLayoutView->HTMLcode;
1✔
186
            } else {
187
                $content .= $pageLayoutView->getTable_tt_content($row['pid']);
1✔
188
            }
189

190
            $GLOBALS['TCA']['tt_content']['columns']['colPos']['config']['items'] = $tcaBackup;
4✔
191
        }
192
        return $content;
5✔
193
    }
194

195
    protected function drawGridToggle(array $row, string $content): string
196
    {
197
        return sprintf($this->templates['gridToggle'], $row['uid'], $content);
4✔
198
    }
199

200
    /**
201
     * @return PageLayoutView|BackendLayoutRenderer
202
     */
203
    protected function getInitializedPageLayoutView(ProviderInterface $provider, array $row)
204
    {
205
        $pageId = (int) $row['pid'];
1✔
206
        $pageRecord = $this->workspacesAwareRecordService->getSingle('pages', '*', $pageId);
1✔
207
        $moduleData = $this->getBackendUser()->getModuleData('web_layout', '');
1✔
208
        $showHiddenRecords = (int) ($moduleData['tt_content_showHidden'] ?? 1);
1✔
209

210
        // For all elements to be shown in draft workspaces & to also show hidden elements by default if user hasn't
211
        // disabled the option analog behavior to the PageLayoutController at the end of menuConfig()
212
        if ($this->getActiveWorkspaceId() !== 0 || !$showHiddenRecords) {
1✔
213
            $moduleData['tt_content_showHidden'] = 1;
×
214
        }
215

216
        $parentRecordUid = ($row['l18n_parent'] ?? 0) > 0 ? $row['l18n_parent'] : ($row['t3ver_oid'] ?: $row['uid']);
1✔
217

218
        $backendLayout = $provider->getGrid($row)->buildBackendLayout($parentRecordUid);
1✔
219
        $layoutConfiguration = $backendLayout->getStructure();
1✔
220

221
        /** @var Features $features */
222
        $features = GeneralUtility::makeInstance(Features::class);
1✔
223
        $fluidBasedLayoutFeatureEnabled = $features->isFeatureEnabled('fluidBasedPageModule');
1✔
224

225
        if ($fluidBasedLayoutFeatureEnabled) {
1✔
226
            /** @var SiteFinderProxy $siteFinder */
227
            $siteFinder = GeneralUtility::makeInstance(SiteFinderProxy::class);
1✔
228
            $site = $siteFinder->getSiteByPageId($pageId);
1✔
229
            $language = null;
1✔
230
            if ($row['sys_language_uid'] >= 0) {
1✔
231
                $language = $site->getLanguageById((int) $row['sys_language_uid']);
1✔
232
            }
233

234
            if (version_compare(VersionNumberUtility::getCurrentTypo3Version(), '13.4', '>=')) {
1✔
235
                $configuration = DrawingConfiguration::create($backendLayout, [], PageViewMode::LayoutView);
×
236
                $configuration->setSelectedLanguageId($language->getLanguageId());
×
237

238
                /** @var PageLayoutContext $context */
239
                $context = GeneralUtility::makeInstance(
×
240
                    PageLayoutContext::class,
×
241
                    $this->fetchPageRecordWithoutOverlay($pageId),
×
242
                    $backendLayout,
×
243
                    $site,
×
244
                    $configuration,
×
245
                    $GLOBALS['TYPO3_REQUEST']
×
246
                );
×
247
            } else {
248
                /** @var PageLayoutContext $context */
249
                $context = GeneralUtility::makeInstance(
1✔
250
                    PageLayoutContext::class,
1✔
251
                    $this->fetchPageRecordWithoutOverlay($pageId),
1✔
252
                    $backendLayout
1✔
253
                );
1✔
254
                $configuration = $context->getDrawingConfiguration();
1✔
255
            }
256

257
            if (isset($language)) {
1✔
258
                 $context = $context->cloneForLanguage($language);
1✔
259
            }
260

261
            if (method_exists($configuration, 'setActiveColumns')) {
1✔
262
                $configuration->setActiveColumns($backendLayout->getColumnPositionNumbers());
1✔
263
            }
264

265
            if (isset($language) && method_exists($configuration, 'setSelectedLanguageUid')) {
1✔
266
                $configuration->setSelectedLanguageId($language->getLanguageId());
×
267
            }
268

269
            $backendLayoutRenderer = $this->createBackendLayoutRenderer($context);
1✔
270

271
            $backendLayoutRenderer->setContext($context);
1✔
272

273
            return $backendLayoutRenderer;
1✔
274
        }
275

276
        $eventDispatcher = GeneralUtility::makeInstance(EventDispatcher::class);
×
277

278
        /** @var PageLayoutView $view */
279
        $view = GeneralUtility::makeInstance(PageLayoutView::class, $eventDispatcher);
×
280
        $view->setProvider($provider);
×
281
        $view->setRecord($row);
×
282

283
        $contentTypeLabels = [];
×
284
        foreach ($GLOBALS['TCA']['tt_content']['columns']['CType']['config']['items'] as $val) {
×
285
            $contentTypeLabels[$val[1]] = $this->getLanguageService()->sL($val[0]);
×
286
        }
287
        $itemLabels = [];
×
288
        foreach ($GLOBALS['TCA']['tt_content']['columns'] as $name => $val) {
×
289
            $itemLabels[$name] = ($val['label'] ?? false) ? $this->getLanguageService()->sL($val['label']) : '';
×
290
        }
291

292
        array_push(
×
293
            $GLOBALS['TCA']['tt_content']['columns']['colPos']['config']['items'],
×
294
            ...($layoutConfiguration['__items'] ?? [])
×
295
        );
×
296

297
        $columnsAsCSV = implode(',', $layoutConfiguration['__colPosList'] ?? []);
×
298

299
        $view->script = 'db_layout.php';
×
300
        $view->showIcon = 1;
×
301
        $view->setLMargin = 0;
×
302
        $view->doEdit = 1;
×
303
        $view->no_noWrap = 1;
×
304
        $view->ext_CALC_PERMS = $this->getBackendUser()->calcPerms($pageRecord);
×
305
        $view->id = $row['pid'];
×
306
        $view->table = 'tt_content';
×
307
        $view->tableList = 'tt_content';
×
308
        $view->currentTable = 'tt_content';
×
309
        $view->tt_contentConfig['showCommands'] = 1;
×
310
        $view->tt_contentConfig['showInfo'] = 1;
×
311
        $view->tt_contentConfig['single'] = 0;
×
312
        $view->nextThree = 1;
×
313
        $view->tt_contentConfig['sys_language_uid'] = (int) $row['sys_language_uid'];
×
314
        $view->tt_contentConfig['showHidden'] = $showHiddenRecords;
×
315
        $view->tt_contentConfig['activeCols'] = $columnsAsCSV;
×
316
        $view->tt_contentConfig['cols'] = $columnsAsCSV;
×
317
        $view->CType_labels = $contentTypeLabels;
×
318
        $view->itemLabels = $itemLabels;
×
319

320
        if (($pageInfo = $this->checkAccessToPage($pageId))) {
×
321
            $view->setPageinfo($pageInfo);
×
322
        }
323

324
        return $view;
×
325
    }
326

327
    /**
328
     * @codeCoverageIgnore
329
     */
330
    protected function createBackendLayoutRenderer(PageLayoutContext $context): BackendLayoutRenderer
331
    {
332
        if (version_compare(VersionNumberUtility::getCurrentTypo3Version(), '13.4', '>=')) {
333
            /** @var BackendViewFactory $backendViewFactory */
334
            $backendViewFactory = GeneralUtility::getContainer()->get(BackendViewFactory::class);
335
            /** @var RecordFactory $recordFactory */
336
            $recordFactory = GeneralUtility::makeInstance(RecordFactory::class);
337
            /** @var BackendLayoutRenderer $backendLayoutRenderer */
338
            $backendLayoutRenderer = GeneralUtility::makeInstance(
339
                BackendLayoutRenderer::class,
340
                $backendViewFactory,
341
                $recordFactory
342
            );
343
        } elseif (version_compare(VersionNumberUtility::getCurrentTypo3Version(), '12.0', '>=')) {
344
            /** @var BackendViewFactory $backendViewFactory */
345
            $backendViewFactory = GeneralUtility::getContainer()->get(BackendViewFactory::class);
346
            /** @var BackendLayoutRenderer $backendLayoutRenderer */
347
            $backendLayoutRenderer = GeneralUtility::makeInstance(
348
                BackendLayoutRenderer::class,
349
                $backendViewFactory
350
            );
351
        } else {
352
            /** @var BackendLayoutRenderer $backendLayoutRenderer */
353
            $backendLayoutRenderer = GeneralUtility::makeInstance(BackendLayoutRenderer::class, $context);
354
        }
355
        return $backendLayoutRenderer;
356
    }
357

358
    /**
359
     * @codeCoverageIgnore
360
     */
361
    protected function fetchWorkspaceVersionOfRecord(int $workspaceId, int $recordUid): ?array
362
    {
363
        /** @var array|false $workspaceVersion */
364
        $workspaceVersion = BackendUtility::getWorkspaceVersionOfRecord($workspaceId, 'tt_content', $recordUid);
365
        return $workspaceVersion ?: null;
366
    }
367

368
    /**
369
     * @codeCoverageIgnore
370
     * @return array|false
371
     */
372
    protected function checkAccessToPage(int $pageId)
373
    {
374
        return BackendUtility::readPageAccess($pageId, '');
375
    }
376

377
    /**
378
     * @codeCoverageIgnore
379
     */
380
    protected function fetchPageRecordWithoutOverlay(int $pageId): ?array
381
    {
382
        return BackendUtility::getRecord('pages', $pageId);
383
    }
384

385
    /**
386
     * @codeCoverageIgnore
387
     */
388
    protected function getBackendUser(): BackendUserAuthentication
389
    {
390
        return $GLOBALS['BE_USER'];
391
    }
392

393
    /**
394
     * @codeCoverageIgnore
395
     */
396
    protected function getLanguageService(): LanguageService
397
    {
398
        return $GLOBALS['LANG'];
399
    }
400

401
    /**
402
     * @codeCoverageIgnore
403
     */
404
    protected function getCookie(): ?string
405
    {
406
        return $_COOKIE['fluxCollapseStates'] ?? null;
407
    }
408

409
    /**
410
     * @codeCoverageIgnore
411
     */
412
    protected function getActiveWorkspaceId(): int
413
    {
414
        return (integer) ($GLOBALS['BE_USER']->workspace ?? 0);
415
    }
416
}
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