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

FluidTYPO3 / flux / 12930151005

23 Jan 2025 01:23PM UTC coverage: 92.829% (-0.07%) from 92.901%
12930151005

Pull #2209

github

web-flow
Merge 510fe665a into cf49f7a79
Pull Request #2209: [WIP] Compatibility with TYPO3 v13

86 of 112 new or added lines in 31 files covered. (76.79%)

5 existing lines in 3 files now uncovered.

7055 of 7600 relevant lines covered (92.83%)

65.02 hits per line

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

95.91
/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): 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 (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()->fillDefaultsByPackageName(
7✔
143
            ExtensionNamingUtility::getExtensionKey($extensionKey)
7✔
144
        );
7✔
145
        $renderingContext->getTemplatePaths()->setTemplatePathAndFilename($templatePathAndFilename);
7✔
146
        return $this->renderSection('Preview', $variables, true);
7✔
147
    }
148

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

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

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

193
            $GLOBALS['TCA']['tt_content']['columns']['colPos']['config']['items'] = $tcaBackup;
16✔
194
        }
195
        return $content;
23✔
196
    }
197

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

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

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

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

221
        $backendLayout = $provider->getGrid($row)->buildBackendLayout($parentRecordUid);
8✔
222
        $layoutConfiguration = $backendLayout->getStructure();
8✔
223

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

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

237
            if (version_compare(VersionNumberUtility::getCurrentTypo3Version(), '13.4', '>=')) {
7✔
238
                $configuration = DrawingConfiguration::create($backendLayout, [], PageViewMode::LayoutView);
1✔
239

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

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

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

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

271
            $backendLayoutRenderer = $this->createBackendLayoutRenderer($context);
7✔
272

273
            $backendLayoutRenderer->setContext($context);
7✔
274

275
            return $backendLayoutRenderer;
7✔
276
        }
277

278
        $eventDispatcher = GeneralUtility::makeInstance(EventDispatcher::class);
1✔
279

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

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

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

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

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

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

326
        return $view;
1✔
327
    }
328

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

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

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

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

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

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

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

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