• 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

79.74
/Classes/Controller/AbstractFluxController.php
1
<?php
2
namespace FluidTYPO3\Flux\Controller;
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\Builder\RenderingContextBuilder;
12
use FluidTYPO3\Flux\Builder\RequestBuilder;
13
use FluidTYPO3\Flux\Builder\ViewBuilder;
14
use FluidTYPO3\Flux\Hooks\HookHandler;
15
use FluidTYPO3\Flux\Integration\NormalizedData\DataAccessTrait;
16
use FluidTYPO3\Flux\Integration\Resolver;
17
use FluidTYPO3\Flux\Provider\Interfaces\ControllerProviderInterface;
18
use FluidTYPO3\Flux\Provider\Interfaces\DataStructureProviderInterface;
19
use FluidTYPO3\Flux\Provider\Interfaces\FluidProviderInterface;
20
use FluidTYPO3\Flux\Provider\ProviderResolver;
21
use FluidTYPO3\Flux\Service\TypoScriptService;
22
use FluidTYPO3\Flux\Service\WorkspacesAwareRecordService;
23
use FluidTYPO3\Flux\Utility\ContentObjectFetcher;
24
use FluidTYPO3\Flux\Utility\ExtensionNamingUtility;
25
use FluidTYPO3\Flux\Utility\RecursiveArrayUtility;
26
use FluidTYPO3\Flux\Utility\RequestResolver;
27
use FluidTYPO3\Flux\Utility\VersionUtility;
28
use FluidTYPO3\Flux\ViewHelpers\FormViewHelper;
29
use Psr\Http\Message\ResponseInterface;
30
use Psr\Http\Message\ServerRequestInterface;
31
use TYPO3\CMS\Core\Utility\GeneralUtility;
32
use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface;
33
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
34
use TYPO3\CMS\Extbase\Mvc\Controller\Arguments;
35
use TYPO3\CMS\Extbase\Mvc\Controller\ControllerInterface;
36
use TYPO3\CMS\Extbase\Mvc\Exception\StopActionException;
37
use TYPO3\CMS\Extbase\Mvc\Request;
38
use TYPO3\CMS\Fluid\View\FluidViewAdapter;
39
use TYPO3\CMS\Fluid\View\TemplateView;
40
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
41
use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
42
use TYPO3Fluid\Fluid\Core\ViewHelper\ViewHelperVariableContainer;
43

44
/**
45
 * Abstract Flux-enabled controller
46
 *
47
 * Extends a traditional ActionController with new services and methods
48
 * to ease interaction with Flux forms. Is not required as subclass for
49
 * Controllers rendering records associated with Flux - all it does is
50
 * ease the interaction by providing a common API.
51
 */
52
abstract class AbstractFluxController extends ActionController
53
{
54
    use DataAccessTrait;
55

56
    protected string $extensionName = 'FluidTYPO3.Flux';
57
    protected ?string $fluxRecordField = 'pi_flexform';
58
    protected ?string $fluxTableName = 'tt_content';
59
    protected array $data = [];
60
    private ?array $record = null;
61

62
    protected RenderingContextBuilder $renderingContextBuilder;
63
    protected RequestBuilder $requestBuilder;
64
    protected WorkspacesAwareRecordService $recordService;
65
    protected TypoScriptService $typoScriptService;
66
    protected ProviderResolver $providerResolver;
67
    protected Resolver $resolver;
68
    protected ViewBuilder $viewBuilder;
69
    protected ?ControllerProviderInterface $provider = null;
70

71
    public function __construct(
72
        RenderingContextBuilder $renderingContextBuilder,
73
        RequestBuilder $requestBuilder,
74
        WorkspacesAwareRecordService $recordService,
75
        TypoScriptService $typoScriptService,
76
        ProviderResolver $providerResolver,
77
        Resolver $resolver,
78
        ViewBuilder $viewBuilder
79
    ) {
80
        $this->renderingContextBuilder = $renderingContextBuilder;
80✔
81
        $this->requestBuilder = $requestBuilder;
80✔
82
        $this->recordService = $recordService;
80✔
83
        $this->typoScriptService = $typoScriptService;
80✔
84
        $this->providerResolver = $providerResolver;
80✔
85
        $this->resolver = $resolver;
80✔
86
        $this->viewBuilder = $viewBuilder;
80✔
87

88
        /** @var Arguments $arguments */
89
        $arguments = GeneralUtility::makeInstance(Arguments::class);
80✔
90
        $this->arguments = $arguments;
80✔
91

92
        /** @var Request $request */
93
        $request = $requestBuilder->buildRequestFor(
80✔
94
            $this->extensionName,
80✔
95
            'Dummy',
80✔
96
            'render',
80✔
97
            '',
80✔
98
            [],
80✔
99
        );
80✔
100
        $this->request = $request;
80✔
101
    }
102

103
    protected function initializeSettings(): void
104
    {
105
        if ($this->provider === null) {
4✔
106
            return;
×
107
        }
108
        $row = $this->getRecord();
4✔
109
        $extensionKey = $this->provider->getControllerExtensionKeyFromRecord($row);
4✔
110
        $extensionName = ExtensionNamingUtility::getExtensionName($extensionKey);
4✔
111
        $pluginName = $this->request->getPluginName();
4✔
112
        $this->settings = RecursiveArrayUtility::merge(
4✔
113
            (array) $this->configurationManager->getConfiguration(
4✔
114
                ConfigurationManagerInterface::CONFIGURATION_TYPE_SETTINGS,
4✔
115
                $extensionName,
4✔
116
                $pluginName
4✔
117
            ),
4✔
118
            (array) $this->settings
4✔
119
        );
4✔
120

121
        if ($this->provider instanceof DataStructureProviderInterface) {
4✔
122
            $this->data = $this->provider->getFlexFormValues($row);
4✔
123
        }
124

125
        $overrides = HookHandler::trigger(
4✔
126
            HookHandler::CONTROLLER_SETTINGS_INITIALIZED,
4✔
127
            [
4✔
128
                'settings' => $this->settings,
4✔
129
                'data' => $this->data,
4✔
130
                'record' => $row,
4✔
131
                'provider' => $this->provider,
4✔
132
                'controller' => $this,
4✔
133
                'request' => $this->request,
4✔
134
                'extensionKey' => $extensionKey
4✔
135
            ]
4✔
136
        );
4✔
137
        $this->data = $overrides['data'];
4✔
138
        $this->settings = $overrides['settings'];
4✔
139
        $this->provider = $overrides['provider'];
4✔
140
    }
141

142
    protected function initializeOverriddenSettings(): void
143
    {
144
        if ($this->provider === null) {
16✔
145
            return;
×
146
        }
147
        $row = $this->getRecord();
16✔
148
        $extensionKey = $this->provider->getControllerExtensionKeyFromRecord($row);
16✔
149
        $extensionKey = ExtensionNamingUtility::getExtensionKey($extensionKey);
16✔
150
        if (is_array($this->data['settings'] ?? null)) {
16✔
151
            // a "settings." array is defined in the flexform configuration - extract it, use as "settings" in template
152
            // as well as the internal $this->settings array as per expected Extbase behavior.
153
            $this->settings = RecursiveArrayUtility::merge($this->settings, $this->data['settings'] ?? []);
14✔
154
        }
155
        if ($this->settings['useTypoScript'] ?? false) {
16✔
156
            // an override shared by all Flux enabled controllers: setting plugin.tx_EXTKEY.settings.useTypoScript = 1
157
            // will read the "settings" array from that location instead - thus excluding variables from the flexform
158
            // which are still available as $this->data but no longer available automatically in the template.
159
            $this->settings = $this->typoScriptService->getSettingsForExtensionName($extensionKey);
10✔
160
        }
161
    }
162

163
    protected function initializeProvider(): void
164
    {
165
        $row = $this->getRecord();
12✔
166
        $table = (string) $this->getFluxTableName();
12✔
167
        $field = $this->getFluxRecordField();
12✔
168
        $provider = $this->providerResolver->resolvePrimaryConfigurationProvider(
12✔
169
            $table,
12✔
170
            $field,
12✔
171
            $row,
12✔
172
            null,
12✔
173
            [ControllerProviderInterface::class]
12✔
174
        );
12✔
175
        if ($provider === null) {
12✔
176
            throw new \RuntimeException(
4✔
177
                'Unable to resolve a ConfigurationProvider, but controller indicates it is a Flux-enabled ' .
4✔
178
                'Controller - this is a grave error and indicates that EXT: ' . $this->extensionName . ' itself is ' .
4✔
179
                'broken - or that EXT:' . $this->extensionName . ' has been overridden by another implementation ' .
4✔
180
                'which is broken. The controller that caused this error was ' . get_class($this) . '".',
4✔
181
                1377458581
4✔
182
            );
4✔
183
        }
184
        $this->provider = $provider;
8✔
185
    }
186

187
    protected function initializeViewVariables(TemplateView|FluidViewAdapter $view): void
188
    {
189
        $contentObject = $this->getContentObject();
4✔
190
        $row = $this->getRecord();
4✔
191

192
        $view->assign('contentObject', $contentObject);
4✔
193
        $view->assign('data', $contentObject instanceof ContentObjectRenderer ? $contentObject->data : null);
4✔
194
        if ($this->provider instanceof FluidProviderInterface) {
4✔
195
            $view->assignMultiple($this->provider->getTemplateVariables($row));
4✔
196
        }
197
        $view->assignMultiple($this->data);
4✔
198
        $view->assign('settings', $this->settings);
4✔
199
        $view->assign('provider', $this->provider);
4✔
200
        $view->assign('record', $row);
4✔
201

202
        HookHandler::trigger(
4✔
203
            HookHandler::CONTROLLER_VARIABLES_ASSIGNED,
4✔
204
            [
4✔
205
                'view' => $view,
4✔
206
                'record' => $row,
4✔
207
                'settings' => $this->settings,
4✔
208
                'provider' => $this->provider,
4✔
209
                'contentObject' => $contentObject,
4✔
210
            ]
4✔
211
        );
4✔
212
    }
213

214
    protected function initializeViewHelperVariableContainer(
215
        ViewHelperVariableContainer $viewHelperVariableContainer
216
    ): void {
217
        $viewHelperVariableContainer->add(FormViewHelper::class, 'provider', $this->provider);
4✔
218
        $viewHelperVariableContainer->add(
4✔
219
            FormViewHelper::class,
4✔
220
            'extensionName',
4✔
221
            $this->request->getControllerExtensionKey()
4✔
222
        );
4✔
223
        $viewHelperVariableContainer->add(
4✔
224
            FormViewHelper::class,
4✔
225
            'pluginName',
4✔
226
            $this->request->getPluginName()
4✔
227
        );
4✔
228
        $viewHelperVariableContainer->add(FormViewHelper::class, 'record', $this->getRecord());
4✔
229
    }
230

231
    protected function initializeAction(): void
232
    {
233
        $this->initializeProvider();
4✔
234
        $this->initializeSettings();
4✔
235
        $this->initializeOverriddenSettings();
4✔
236
    }
237

238
    protected function initializeView(): void
239
    {
UNCOV
240
        if (!$this->provider instanceof ControllerProviderInterface) {
×
241
            throw new \RuntimeException(
×
242
                get_class($this) . ' cannot handle record; no ControllerProviderInterface could be resolved',
×
243
                1672082347
×
244
            );
×
245
        }
246

UNCOV
247
        $record = $this->getRecord();
×
UNCOV
248
        $extensionKey = ExtensionNamingUtility::getExtensionKey(
×
UNCOV
249
            $this->provider->getControllerExtensionKeyFromRecord($record)
×
UNCOV
250
        );
×
251

UNCOV
252
        $templatePathAndFilename = null;
×
UNCOV
253
        if ($this->provider instanceof FluidProviderInterface) {
×
UNCOV
254
            $templatePathAndFilename = $this->provider->getTemplatePathAndFilename($record);
×
255
        }
256

257
        /** @var TemplateView|FluidViewAdapter $view */
UNCOV
258
        $view = $this->viewBuilder->buildTemplateView(
×
UNCOV
259
            $extensionKey,
×
UNCOV
260
            $this->resolver->resolveControllerNameFromControllerClassName(get_class($this)),
×
UNCOV
261
            $this->provider->getControllerActionFromRecord($record),
×
UNCOV
262
            $this->provider->getPluginName() ?? $this->provider->getControllerNameFromRecord($record),
×
UNCOV
263
            $templatePathAndFilename,
×
UNCOV
264
            $this->request
×
UNCOV
265
        );
×
266

UNCOV
267
        $renderingContext = $view->getRenderingContext();
×
268

UNCOV
269
        $this->initializeViewVariables($view);
×
UNCOV
270
        $this->initializeViewHelperVariableContainer($renderingContext->getViewHelperVariableContainer());
×
UNCOV
271
        HookHandler::trigger(
×
UNCOV
272
            HookHandler::CONTROLLER_VIEW_INITIALIZED,
×
UNCOV
273
            [
×
UNCOV
274
                'view' => $view,
×
UNCOV
275
                'request' => $this->request,
×
UNCOV
276
                'provider' => $this->provider,
×
UNCOV
277
                'controller' => $this,
×
UNCOV
278
                'extensionKey' => $extensionKey
×
UNCOV
279
            ]
×
UNCOV
280
        );
×
NEW
281
        $this->view = $view;
×
282
    }
283

284
    /**
285
     * Default action, proxy for "render". Added in order to
286
     * capture requests which use the Fluid-native "default"
287
     * action name when no specific action name is set in the
288
     * request. The "default" action is also returned by
289
     * vanilla Provider instances when registering them for
290
     * content object types or other ad-hoc registrations.
291
     */
292
    public function defaultAction(): ResponseInterface
293
    {
294
        return $this->renderAction();
4✔
295
    }
296

297
    /**
298
     * Render content
299
     */
300
    public function renderAction(): ResponseInterface
301
    {
302
        if (!$this->provider instanceof ControllerProviderInterface) {
8✔
303
            throw new \RuntimeException(
4✔
304
                get_class($this) . ' cannot handle record; no ControllerProviderInterface could be resolved',
4✔
305
                1672082347
4✔
306
            );
4✔
307
        }
308
        $row = $this->getRecord();
4✔
309
        $extensionKey = $this->provider->getControllerExtensionKeyFromRecord($row);
4✔
310
        $extensionSignature = ExtensionNamingUtility::getExtensionSignature($extensionKey);
4✔
311
        $pluginName = $this->request->getPluginName();
4✔
312
        $pluginSignature = strtolower('tx_' . $extensionSignature . '_' . $pluginName);
4✔
313
        $controllerExtensionKey = $this->provider->getControllerExtensionKeyFromRecord($row);
4✔
314
        $requestActionName = $this->resolveOverriddenFluxControllerActionNameFromRequestParameters($pluginSignature);
4✔
315
        $controllerActionName = $this->provider->getControllerActionFromRecord($row);
4✔
316
        $actualActionName = null !== $requestActionName ? $requestActionName : $controllerActionName;
4✔
317
        $controllerName = $this->request->getControllerName();
4✔
318

319
        return $this->performSubRendering(
4✔
320
            $controllerExtensionKey,
4✔
321
            $controllerName,
4✔
322
            $actualActionName,
4✔
323
            $pluginName,
4✔
324
            $pluginSignature
4✔
325
        );
4✔
326
    }
327

328
    protected function resolveOverriddenFluxControllerActionNameFromRequestParameters(string $pluginSignature): ?string
329
    {
330
        return $this->getServerRequest()->getQueryParams()[$pluginSignature]['action'] ?? null;
4✔
331
    }
332

333
    protected function performSubRendering(
334
        string $extensionName,
335
        string $controllerName,
336
        string $actionName,
337
        string $pluginName,
338
        string $pluginSignature
339
    ): ResponseInterface {
340
        $shouldRelay = $this->hasSubControllerActionOnForeignController($extensionName, $controllerName, $actionName);
12✔
341
        $foreignControllerClass = null;
12✔
342
        $content = null;
12✔
343
        if (!$shouldRelay) {
12✔
344
            if ($this->provider instanceof FluidProviderInterface) {
8✔
345
                $templatePathAndFilename = $this->provider->getTemplatePathAndFilename($this->getRecord());
4✔
346
                $vendorLessExtensionName = ExtensionNamingUtility::getExtensionName($extensionName);
4✔
347
                /** @var TemplateView $view */
348
                $view = $this->view;
4✔
349
                $renderingContext = $view->getRenderingContext();
4✔
350
                $paths = $renderingContext->getTemplatePaths();
4✔
351

352
                if (method_exists($this->request, 'setControllerExtensionName')) {
4✔
UNCOV
353
                    $this->request->setControllerExtensionName($vendorLessExtensionName);
×
354
                }
355

356
                if (method_exists($this->request, 'withControllerExtensionName')) {
4✔
357
                    $this->request = $this->request->withControllerExtensionName($vendorLessExtensionName);
4✔
358
                }
359

360
                $this->configurationManager->setConfiguration(
4✔
361
                    array_merge(
4✔
362
                        (array) $this->configurationManager->getConfiguration(
4✔
363
                            ConfigurationManagerInterface::CONFIGURATION_TYPE_FULL_TYPOSCRIPT,
4✔
364
                            $vendorLessExtensionName
4✔
365
                        ),
4✔
366
                        [
4✔
367
                            'extensionName' => $vendorLessExtensionName,
4✔
368
                        ]
4✔
369
                    )
4✔
370
                );
4✔
371
                $paths->setTemplatePathAndFilename((string) $templatePathAndFilename);
4✔
372
            }
373
            $content = $this->view->render();
8✔
374
        } else {
375
            $foreignControllerClass = $this->resolver->resolveFluxControllerClassNameByExtensionKeyAndControllerName(
4✔
376
                $extensionName,
4✔
377
                $controllerName
4✔
378
            );
4✔
379
            $content = $this->callSubControllerAction(
4✔
380
                $extensionName,
4✔
381
                $foreignControllerClass ?? static::class,
4✔
382
                $actionName,
4✔
383
                $pluginName,
4✔
384
                $pluginSignature
4✔
385
            );
4✔
386
        }
387
        $content = HookHandler::trigger(
12✔
388
            HookHandler::CONTROLLER_AFTER_RENDERING,
12✔
389
            [
12✔
390
                'view' => $this->view,
12✔
391
                'content' => $content,
12✔
392
                'request' => $this->request,
12✔
393
                'extensionName' => $extensionName,
12✔
394
                'controllerClassName' => $foreignControllerClass,
12✔
395
                'controllerActionName' => $actionName
12✔
396
            ]
12✔
397
        )['content'];
12✔
398

399
        return $content instanceof ResponseInterface ? $content : $this->htmlResponse($content);
12✔
400
    }
401

402
    protected function hasSubControllerActionOnForeignController(
403
        string $extensionName,
404
        string $controllerName,
405
        string $actionName
406
    ): bool {
407
        $potentialControllerClassName = $this->resolver->resolveFluxControllerClassNameByExtensionKeyAndControllerName(
4✔
408
            $extensionName,
4✔
409
            $controllerName
4✔
410
        );
4✔
411
        if ($potentialControllerClassName === null) {
4✔
412
            return false;
4✔
413
        }
414
        $isNotThis = get_class($this) !== $potentialControllerClassName;
×
415
        $isValidController = class_exists($potentialControllerClassName);
×
416
        return ($isNotThis && $isValidController
×
417
            && method_exists($potentialControllerClassName, $actionName . 'Action'));
×
418
    }
419

420
    /**
421
     * @param class-string $controllerClassName
422
     * @return ResponseInterface|null
423
     */
424
    protected function callSubControllerAction(
425
        string $extensionName,
426
        string $controllerClassName,
427
        string $controllerActionName,
428
        string $pluginName,
429
        string $pluginSignature
430
    ): ?ResponseInterface {
431
        $serverRequest = $this->getServerRequest();
4✔
432
        $arguments = $serverRequest->getQueryParams()[$pluginSignature] ?? [];
4✔
433
        $arguments = array_merge($arguments, ((array) $serverRequest->getParsedBody())[$pluginSignature] ?? []);
4✔
434

435
        $request = $this->requestBuilder->buildRequestFor(
4✔
436
            $extensionName,
4✔
437
            $this->resolver->resolveControllerNameFromControllerClassName(
4✔
438
                $controllerClassName
4✔
439
            ),
4✔
440
            $controllerActionName,
4✔
441
            $pluginName,
4✔
442
            $arguments,
4✔
443
            $serverRequest->getAttributes()
4✔
444
        );
4✔
445

446
        /** @var ControllerInterface $potentialControllerInstance */
447
        $potentialControllerInstance = GeneralUtility::makeInstance($controllerClassName);
4✔
448

449
        $response = null;
4✔
450
        try {
451
            HookHandler::trigger(
4✔
452
                HookHandler::CONTROLLER_BEFORE_REQUEST,
4✔
453
                [
4✔
454
                    'request' => $this->request,
4✔
455
                    'extensionName' => $extensionName,
4✔
456
                    'controllerClassName' => $controllerClassName,
4✔
457
                    'controllerActionName' => $controllerActionName
4✔
458
                ]
4✔
459
            );
4✔
460

461
            $response = $potentialControllerInstance->processRequest($request);
4✔
UNCOV
462
        } catch (StopActionException $error) {
×
463
            // intentionally left blank
464
        }
465
        HookHandler::trigger(
4✔
466
            HookHandler::CONTROLLER_AFTER_REQUEST,
4✔
467
            [
4✔
468
                'request' => $this->request,
4✔
469
                'response' => $response,
4✔
470
                'extensionName' => $extensionName,
4✔
471
                'controllerClassName' => $controllerClassName,
4✔
472
                'controllerActionName' => $controllerActionName
4✔
473
            ]
4✔
474
        );
4✔
475

476
        return $response;
4✔
477
    }
478

479
    /**
480
     * Get the data stored in a record's Flux-enabled field,
481
     * i.e. the variables of the Flux template as configured in this
482
     * particular record.
483
     */
484
    protected function getData(): array
485
    {
486
        return $this->data;
4✔
487
    }
488

489
    protected function getFluxRecordField(): ?string
490
    {
491
        return $this->fluxRecordField;
8✔
492
    }
493

494
    protected function getFluxTableName(): ?string
495
    {
496
        return $this->fluxTableName;
12✔
497
    }
498

499
    public function getRecord(): array
500
    {
501
        if ($this->record !== null) {
12✔
502
            return $this->record;
4✔
503
        }
504

505
        $contentObject = $this->getContentObject();
12✔
506
        if ($contentObject === null) {
12✔
507
            throw new \UnexpectedValueException(
4✔
508
                "Record of table " . $this->getFluxTableName() . ' not found',
4✔
509
                1666538343
4✔
510
            );
4✔
511
        }
512

513
        $tsfe = null;
8✔
514
        if (VersionUtility::isCoreBelow14()) {
8✔
515
            $tsfe = $contentObject->getTypoScriptFrontendController();
6✔
516
            if ($tsfe === null) {
6✔
NEW
517
                throw new \UnexpectedValueException(
×
NEW
518
                    "Record of table " . $this->getFluxTableName() . ' not found',
×
NEW
519
                    1729864782
×
NEW
520
                );
×
521
            }
522
        }
523

524
        if (!empty($contentObject->data) && $this->fluxTableName === $contentObject->getCurrentTable()) {
8✔
525
            // Possible short-cut: ContentObjectRenderer->data may be non-empty, but isn't completely trustworthy since
526
            // the data array may actually belong to a page record while rendering a *_INT content record. So we check
527
            // that the data is non-empty AND that the controller's table name matches the ContentObjectRenderer's
528
            // stored value for the table name currently being processed, which DOES contain the right table name.
529
            return $this->record = $contentObject->data;
×
530
        }
531

532
        [$table, $recordUid] = GeneralUtility::trimExplode(
8✔
533
            ':',
8✔
534
            (string) ($tsfe && $tsfe->currentRecord ? $tsfe->currentRecord : $contentObject->currentRecord)
8✔
535
        );
8✔
536
        $record = $this->recordService->getSingle($table, '*', (int) $recordUid);
8✔
537
        if ($record === null) {
8✔
538
            throw new \UnexpectedValueException(
×
539
                "Record of table " . $this->getFluxTableName() . ' not found',
×
540
                1729864698
×
541
            );
×
542
        }
543

544
        if ($record['_LOCALIZED_UID'] ?? false) {
8✔
545
            $record = array_merge(
×
546
                $record,
×
547
                $this->recordService->getSingle(
×
548
                    (string) $this->getFluxTableName(),
×
549
                    '*',
×
550
                    $record['_LOCALIZED_UID']
×
551
                ) ?? $record
×
552
            );
×
553
        }
554
        return $this->record = $record;
8✔
555
    }
556

557
    protected function getContentObject(): ?ContentObjectRenderer
558
    {
NEW
559
        return ContentObjectFetcher::resolve($this->configurationManager, $this->getServerRequest());
×
560
    }
561

562
    protected function getServerRequest(): ServerRequestInterface
563
    {
564
        if ($this->request instanceof ServerRequestInterface) {
×
565
            return $this->request;
×
566
        }
NEW
567
        return RequestResolver::getRequest();
×
568
    }
569
}
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