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

FluidTYPO3 / flux / 17727369243

15 Sep 2025 08:47AM UTC coverage: 92.767% (-0.1%) from 92.901%
17727369243

push

github

NamelessCoder
[TER] 11.0.7

7080 of 7632 relevant lines covered (92.77%)

65.98 hits per line

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

95.86
/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);
27✔
51

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

146
    protected function renderGrid(ProviderInterface $provider, array $row, Form $form): string
147
    {
148
        $content = '';
23✔
149
        $grid = $provider->getGrid($row);
23✔
150
        if ($grid->hasChildren()) {
23✔
151
            $options = $this->getPreviewOptions($form);
16✔
152
            if ($this->getOptionToggle($options)) {
16✔
153
                $content = $this->drawGridToggle($row, $content);
16✔
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'];
16✔
161
            $GLOBALS['TCA']['tt_content']['columns']['colPos']['config']['items'] = array_merge(
16✔
162
                $GLOBALS['TCA']['tt_content']['columns']['colPos']['config']['items'],
16✔
163
                $grid->buildExtendedBackendLayoutArray($row['uid'])['__items']
16✔
164
            );
16✔
165

166
            $pageUid = $row['pid'];
16✔
167
            if (($workspaceId = $this->getBackendUser()->workspace) > 0) {
16✔
168
                $workspaceVersion = $this->fetchWorkspaceVersionOfRecord($workspaceId, $row['uid']);
4✔
169
                $pageUid = $workspaceVersion['pid'] ?? $pageUid;
4✔
170
            }
171
            $pageLayoutView = $this->getInitializedPageLayoutView($provider, $row);
16✔
172
            if ($pageLayoutView instanceof BackendLayoutRenderer) {
16✔
173
                if (version_compare(VersionNumberUtility::getCurrentTypo3Version(), '12.0', '>=')) {
8✔
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);
8✔
181
                }
182
            } elseif (method_exists($pageLayoutView, 'start') && method_exists($pageLayoutView, 'generateList')) {
8✔
183
                $pageLayoutView->start($pageUid, 'tt_content', 0);
4✔
184
                $pageLayoutView->generateList();
4✔
185
                $content .= $pageLayoutView->HTMLcode;
4✔
186
            } else {
187
                $content .= $pageLayoutView->getTable_tt_content($row['pid']);
4✔
188
            }
189

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

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

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

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

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

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

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

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

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

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

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

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

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

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

273
            return $backendLayoutRenderer;
7✔
274
        }
275

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

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

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

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

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

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

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

324
        return $view;
1✔
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