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

FluidTYPO3 / flux / 27757675993

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

push

github

NamelessCoder
[TASK] Address last phpstan warnings

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

95.33
/Classes/Provider/AbstractProvider.php
1
<?php
2
namespace FluidTYPO3\Flux\Provider;
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\ViewBuilder;
12
use FluidTYPO3\Flux\Enum\FormOption;
13
use FluidTYPO3\Flux\Form;
14
use FluidTYPO3\Flux\Form\Container\Grid;
15
use FluidTYPO3\Flux\Form\Transformation\FormDataTransformer;
16
use FluidTYPO3\Flux\Hooks\HookHandler;
17
use FluidTYPO3\Flux\Service\CacheService;
18
use FluidTYPO3\Flux\Service\TypoScriptService;
19
use FluidTYPO3\Flux\Service\WorkspacesAwareRecordService;
20
use FluidTYPO3\Flux\Utility\ExtensionNamingUtility;
21
use FluidTYPO3\Flux\Utility\MiscellaneousUtility;
22
use FluidTYPO3\Flux\Utility\RecursiveArrayUtility;
23
use FluidTYPO3\Flux\Utility\VersionUtility;
24
use FluidTYPO3\Flux\ViewHelpers\FormViewHelper;
25
use TYPO3\CMS\Core\DataHandling\DataHandler;
26
use TYPO3\CMS\Core\Messaging\FlashMessage;
27
use TYPO3\CMS\Core\Messaging\FlashMessageService;
28
use TYPO3\CMS\Core\Type\ContextualFeedbackSeverity;
29
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
30
use TYPO3\CMS\Core\Utility\GeneralUtility;
31
use TYPO3\CMS\Core\Utility\PathUtility;
32
use TYPO3\CMS\Extbase\Reflection\ObjectAccess;
33
use TYPO3Fluid\Fluid\View\Exception\InvalidTemplateResourceException;
34
use TYPO3Fluid\Fluid\View\ViewInterface;
35

36
class AbstractProvider implements ProviderInterface
37
{
38
    public const string FORM_CLASS_PATTERN = '%s\\Form\\%s\\%sForm';
39
    const CONTENT_OBJECT_TYPE_LIST = 'list';
40

41
    /**
42
     * Fill with the table column name which should trigger this Provider.
43
     */
44
    protected ?string $fieldName = null;
45

46
    /**
47
     * Fill with the name of the DB table which should trigger this Provider.
48
     */
49
    protected ?string $tableName = null;
50

51
    /**
52
     * Fill with the "list_type" value that should trigger this Provider.
53
     */
54
    protected string $listType = '';
55

56
    /**
57
     * Fill with the "CType" value that should trigger this Provider.
58
     */
59
    protected string $contentObjectType = '';
60

61
    protected string $name = self::class;
62
    protected ?string $parentFieldName = null;
63
    protected ?array $row = null;
64
    protected ?string $templatePathAndFilename = null;
65
    protected array $templateVariables = [];
66
    protected ?array $templatePaths = null;
67
    protected ?string $configurationSectionName = 'Configuration';
68
    protected string $extensionKey = 'FluidTYPO3.Flux';
69
    protected ?string $pluginName = null;
70
    protected ?string $controllerName = null;
71
    protected string $controllerAction = 'default';
72
    protected int $priority = 50;
73
    protected ?Form $form = null;
74
    protected ?Grid $grid = null;
75

76
    protected FormDataTransformer $formDataTransformer;
77
    protected WorkspacesAwareRecordService $recordService;
78
    protected ViewBuilder $viewBuilder;
79
    protected CacheService $cacheService;
80
    protected TypoScriptService $typoScriptService;
81

82
    public function __construct(
83
        FormDataTransformer $formDataTransformer,
84
        WorkspacesAwareRecordService $recordService,
85
        ViewBuilder $viewBuilder,
86
        CacheService $cacheService,
87
        TypoScriptService $typoScriptService
88
    ) {
89
        $this->formDataTransformer = $formDataTransformer;
384✔
90
        $this->recordService = $recordService;
384✔
91
        $this->viewBuilder = $viewBuilder;
384✔
92
        $this->cacheService = $cacheService;
384✔
93
        $this->typoScriptService = $typoScriptService;
384✔
94
    }
95

96
    public function loadSettings(array $settings): void
97
    {
98
        if (isset($settings['name'])) {
20✔
99
            $this->setName($settings['name']);
16✔
100
        }
101
        if (isset($settings['form'])) {
20✔
102
            $form = Form::create($settings['form']);
20✔
103
            if (isset($settings['extensionKey'])) {
20✔
104
                $extensionKey = $settings['extensionKey'];
4✔
105
                $extensionName = ExtensionNamingUtility::getExtensionName($extensionKey);
4✔
106
                $form->setExtensionName($extensionName);
4✔
107
            }
108
            $settings['form'] = $form;
20✔
109
        }
110
        if (isset($settings['grid'])) {
20✔
111
            $settings['grid'] = Grid::create($settings['grid']);
16✔
112
        }
113
        foreach ($settings as $name => $value) {
20✔
114
            if (property_exists($this, $name)) {
20✔
115
                $this->$name = $value;
20✔
116
            }
117
        }
118
        $fieldName = $this->getFieldName([]);
20✔
119
        if (true === isset($settings['listType'])) {
20✔
120
            $listType = $settings['listType'];
4✔
121
            $GLOBALS['TCA'][$this->tableName]['types']['list']['subtypes_addlist'][$listType] = $fieldName;
4✔
122
        }
123
        $GLOBALS['TCA'][$this->tableName]['columns'][$fieldName]['config']['type'] = 'flex';
20✔
124
    }
125

126
    public function trigger(array $row, ?string $table, ?string $field, ?string $extensionKey = null): bool
127
    {
128
        $providerFieldName = $this->getFieldName($row);
40✔
129
        $providerTableName = $this->getTableName($row);
40✔
130
        $providerExtensionKey = $this->extensionKey;
40✔
131
        $contentObjectType = $this->contentObjectType;
40✔
132
        $listType = $this->listType;
40✔
133

134
        // Content type resolving: CType *may* be an array when called from certain FormEngine contexts, such as
135
        // user functions registered via userFunc.
136
        $contentTypeFromRecord = (is_array($row['CType'] ?? null) ? $row['CType'][0] : null) ?? $row['CType'] ?? null;
40✔
137
        $pluginTypeFromRecord = $row['list_type'] ?? null;
40✔
138

139
        $rowContainsPlugin = $contentTypeFromRecord === static::CONTENT_OBJECT_TYPE_LIST;
40✔
140
        $isContentRecord = $table === 'tt_content';
40✔
141
        $rowIsEmpty = (0 === count($row));
40✔
142
        $matchesContentType = $contentTypeFromRecord === $contentObjectType;
40✔
143
        $matchesPluginType = $rowContainsPlugin && $pluginTypeFromRecord === $listType;
40✔
144
        $matchesTableName = ($providerTableName === $table || !$table);
40✔
145
        $matchesFieldName = ($providerFieldName === $field || !$field);
40✔
146
        $matchesExtensionKey = ($providerExtensionKey === $extensionKey || !$extensionKey);
40✔
147

148
        // Requirements: must always match ext-key, table and field. If record is a content record, must additionally
149
        // match either Ctype and list_type, or must match CType in record that does not have a list_type.
150
        $isFullMatch = $matchesExtensionKey && $matchesTableName && $matchesFieldName
40✔
151
            && (!$isContentRecord || ($matchesContentType && ((!$rowContainsPlugin) || $matchesPluginType)));
40✔
152
        $isFallbackMatch = ($matchesTableName && $matchesFieldName && $rowIsEmpty);
40✔
153
        return ($isFullMatch || $isFallbackMatch);
40✔
154
    }
155

156
    /**
157
     * If not-NULL is returned, the value is used as
158
     * object class name when creating a Form implementation
159
     * instance which can be returned as form instead of
160
     * reading from template or overriding the getForm() method.
161
     *
162
     * @return class-string|null
163
     */
164
    protected function resolveFormClassName(array $row, ?string $forField = null): ?string
165
    {
166
        $packageName = $this->getControllerPackageNameFromRecord($row, $forField);
12✔
167
        $packageKey = str_replace('.', '\\', $packageName);
12✔
168
        $controllerName = $this->getControllerNameFromRecord($row);
12✔
169
        $action = $this->getControllerActionFromRecord($row, $forField);
12✔
170
        $expectedClassName = sprintf(static::FORM_CLASS_PATTERN, $packageKey, $controllerName, ucfirst($action));
12✔
171
        return class_exists($expectedClassName) ? $expectedClassName : null;
12✔
172
    }
173

174
    protected function getViewVariables(array $row, ?string $forField = null): array
175
    {
176
        $extensionKey = (string) $this->getExtensionKey($row, $forField);
28✔
177
        $fieldName = $forField ?? $this->getFieldName($row);
28✔
178
        $variables = [
28✔
179
            'record' => $row,
28✔
180
            'settings' => $this->typoScriptService->getSettingsForExtensionName($extensionKey),
28✔
181
            'forField' => $forField,
28✔
182
        ];
28✔
183

184
        // Special case: when saving a new record variable $row[$fieldName] is already an array
185
        // and must not be processed by the configuration service. This has limited support from
186
        // Flux (essentially: no Form instance which means no inheritance, transformation or
187
        // form options can be dependended upon at this stage).
188
        if (isset($row[$fieldName])) {
28✔
189
            if (is_array($row[$fieldName])) {
×
190
                $xml = GeneralUtility::array2xml($row[$fieldName]);
×
191
            } else {
192
                $xml = $row[$fieldName];
×
193
            }
194
            $recordVariables = $this->formDataTransformer->convertFlexFormContentToArray($xml);
×
195
            $variables = RecursiveArrayUtility::mergeRecursiveOverrule($variables, $recordVariables);
×
196
        }
197

198
        $variables = RecursiveArrayUtility::mergeRecursiveOverrule($this->templateVariables, $variables);
28✔
199

200
        return $variables;
28✔
201
    }
202

203
    public function getForm(array $row, ?string $forField = null): ?Form
204
    {
205
        /** @var Form $form */
206
        $form = $this->form
36✔
207
            ?? $this->createCustomFormInstance($row, $forField)
36✔
208
            ?? $this->extractConfiguration($row, 'form', $forField)
36✔
209
            ?? Form::create();
20✔
210
        $form->setOption(FormOption::RECORD, $row);
36✔
211
        $form->setOption(FormOption::RECORD_TABLE, $this->getTableName($row));
36✔
212
        $form->setOption(FormOption::RECORD_FIELD, $forField ?? $this->getFieldName($row));
36✔
213
        return $form;
36✔
214
    }
215

216
    protected function createCustomFormInstance(array $row, ?string $forField = null): ?Form
217
    {
218
        $formClassName = $this->resolveFormClassName($row, $forField);
28✔
219
        if ($formClassName !== null && class_exists($formClassName)) {
28✔
220
            $tableName = $this->getTableName($row);
16✔
221
            $fieldName = $forField ?? $this->getFieldName($row);
16✔
222
            $id = 'row_' . $row['uid'];
16✔
223
            if ($tableName) {
16✔
224
                $id = $tableName;
8✔
225
            }
226
            if ($fieldName) {
16✔
227
                $id .= '_' . $fieldName;
8✔
228
            }
229
            return $formClassName::create(['id' => $id]);
16✔
230
        }
231
        return null;
12✔
232
    }
233

234
    public function getGrid(array $row): Grid
235
    {
236
        if ($this->grid instanceof Grid) {
72✔
237
            return $this->grid;
60✔
238
        }
239
        $form = $this->getForm($row);
12✔
240
        if ($form) {
12✔
241
            $container = $this->detectContentContainerParent($form);
8✔
242
            if ($container) {
8✔
243
                $values = $this->getFlexFormValues($row);
8✔
244
                $contentContainer = $container->getContentContainer();
8✔
245
                $persistedObjects = [];
8✔
246
                if ($contentContainer instanceof Form\Container\SectionObject) {
8✔
247
                    $persistedObjects = array_column(
8✔
248
                        (array) (ObjectAccess::getProperty($values, (string) $container->getName()) ?? []),
8✔
249
                        (string) $contentContainer->getName()
8✔
250
                    );
8✔
251
                }
252

253
                // Determine the mode to render, then create an ad-hoc grid.
254
                /** @var Grid $grid */
255
                $grid = Grid::create();
8✔
256
                if ($container->getGridMode() === Form\Container\Section::GRID_MODE_ROWS) {
8✔
257
                    foreach ($persistedObjects as $index => $object) {
4✔
258
                        $gridRow = $grid->createContainer(Form\Container\Row::class, 'row' . $index);
4✔
259
                        $gridColumn = $gridRow->createContainer(
4✔
260
                            Form\Container\Column::class,
4✔
261
                            'column' . $object['colPos'],
4✔
262
                            $object['label'] ?? 'Column ' . $object['colPos']
4✔
263
                        );
4✔
264
                        $gridColumn->setColumnPosition((int) $object['colPos']);
4✔
265
                    }
266
                } elseif ($container->getGridMode() === Form\Container\Section::GRID_MODE_COLUMNS) {
4✔
267
                    $gridRow = $grid->createContainer(Form\Container\Row::class, 'row');
4✔
268
                    foreach ($persistedObjects as $index => $object) {
4✔
269
                        $gridColumn = $gridRow->createContainer(
4✔
270
                            Form\Container\Column::class,
4✔
271
                            'column' . $object['colPos'],
4✔
272
                            $object['label'] ?? 'Column ' . $object['colPos']
4✔
273
                        );
4✔
274
                        $gridColumn->setColumnPosition((int) $object['colPos']);
4✔
275
                        $gridColumn->setColSpan((int) ($object['colspan'] ?? 1));
4✔
276
                    }
277
                }
278
                return $grid;
8✔
279
            }
280
        }
281
        /** @var array $grids */
282
        $grids = $this->extractConfiguration($row, 'grids');
4✔
283
        $grid = $grids['grid'] ?? Grid::create();
4✔
284
        $grid->setExtensionName($grid->getExtensionName() ?: $this->getControllerExtensionKeyFromRecord($row));
4✔
285
        return $grid;
4✔
286
    }
287

288
    protected function detectContentContainerParent(Form\ContainerInterface $container): ?Form\Container\Section
289
    {
290
        if ($container instanceof Form\Container\SectionObject && $container->isContentContainer()) {
8✔
291
            /** @var Form\Container\Section $parent */
292
            $parent = $container->getParent();
8✔
293
            return $parent;
8✔
294
        }
295
        foreach ($container->getChildren() as $child) {
8✔
296
            if ($child instanceof Form\ContainerInterface
8✔
297
                && ($detected = $this->detectContentContainerParent($child))
8✔
298
            ) {
299
                return $detected;
8✔
300
            }
301
        }
302
        return null;
×
303
    }
304

305
    /**
306
     * @return mixed|null
307
     */
308
    protected function extractConfiguration(array $row, ?string $name = null, ?string $forField = null)
309
    {
310
        $cacheKeyAll = $this->getCacheKeyForStoredVariable($row, '_all', $forField) . '_' . $forField;
20✔
311
        /** @var array $allCached */
312
        $allCached = $this->cacheService->getFromCaches($cacheKeyAll);
20✔
313
        $fromCache = $allCached[$name] ?? null;
20✔
314
        if ($fromCache) {
20✔
315
            return $fromCache;
4✔
316
        }
317
        $configurationSectionName = $this->getConfigurationSectionName($row, $forField);
16✔
318
        $viewVariables = $this->getViewVariables($row, $forField);
16✔
319
        $view = $this->getViewForRecord($row, $forField);
16✔
320
        $view->getRenderingContext()->getViewHelperVariableContainer()->addOrUpdate(
16✔
321
            FormViewHelper::class,
16✔
322
            FormViewHelper::SCOPE_VARIABLE_EXTENSIONNAME,
16✔
323
            $this->getExtensionKey($row, $forField)
16✔
324
        );
16✔
325

326
        try {
327
            if ($configurationSectionName) {
16✔
328
                $view->renderSection($configurationSectionName, $viewVariables, false);
12✔
329
            } else {
330
                $view->assignMultiple($viewVariables);
4✔
331
                $view->render();
12✔
332
            }
333
        } catch (InvalidTemplateResourceException $exception) {
4✔
334
            $this->dispatchFlashMessageForException($exception);
4✔
335
            return null;
4✔
336
        }
337

338
        $variables = $view->getRenderingContext()->getViewHelperVariableContainer()->getAll(FormViewHelper::class, []);
12✔
339
        $cachePersistent = false;
12✔
340
        if (isset($variables['form'])) {
12✔
341
            $variables['form']->setOption(
8✔
342
                FormOption::TEMPLATE_FILE,
8✔
343
                $this->getTemplatePathAndFilename($row, $forField)
8✔
344
            );
8✔
345
            $cachePersistent = (bool) $variables['form']->getOption(FormOption::STATIC);
8✔
346
        }
347

348
        $this->cacheService->setInCaches(
12✔
349
            $variables,
12✔
350
            $cachePersistent,
12✔
351
            $cacheKeyAll
12✔
352
        );
12✔
353

354
        $returnValue = $name ? ($variables[$name] ?? null) : $variables;
12✔
355

356
        return HookHandler::trigger(
12✔
357
            HookHandler::PROVIDER_EXTRACTED_OBJECT,
12✔
358
            [
12✔
359
                'record' => $row,
12✔
360
                'name' => $name,
12✔
361
                'value' => $returnValue
12✔
362
            ]
12✔
363
        )['value'];
12✔
364
    }
365

366
    public function setListType(string $listType): self
367
    {
368
        $this->listType = $listType;
16✔
369
        return $this;
16✔
370
    }
371

372
    public function getListType(): string
373
    {
374
        return $this->listType;
8✔
375
    }
376

377
    public function setContentObjectType(string $contentObjectType): self
378
    {
379
        $this->contentObjectType = $contentObjectType;
20✔
380
        return $this;
20✔
381
    }
382

383
    public function getContentObjectType(): string
384
    {
385
        return $this->contentObjectType;
4✔
386
    }
387

388
    public function getFieldName(array $row): ?string
389
    {
390
        return $this->fieldName;
164✔
391
    }
392

393
    public function getParentFieldName(array $row): ?string
394
    {
395
        unset($row);
4✔
396
        return $this->parentFieldName;
4✔
397
    }
398

399
    public function getTableName(array $row): ?string
400
    {
401
        unset($row);
160✔
402
        return $this->tableName;
160✔
403
    }
404

405
    public function getTemplatePathAndFilename(array $row, ?string $forField = null): ?string
406
    {
407
        $templatePathAndFilename = (string) $this->templatePathAndFilename;
40✔
408
        if ($templatePathAndFilename !== '' && !PathUtility::isAbsolutePath($templatePathAndFilename)) {
40✔
409
            $templatePathAndFilename = $this->resolveAbsolutePathToFile($templatePathAndFilename);
4✔
410
        }
411
        if (true === empty($templatePathAndFilename)) {
40✔
412
            $templatePathAndFilename = null;
32✔
413
        }
414
        return HookHandler::trigger(
40✔
415
            HookHandler::PROVIDER_RESOLVED_TEMPLATE,
40✔
416
            [
40✔
417
                'template' => $templatePathAndFilename,
40✔
418
                'provider' => $this,
40✔
419
                'record' => $row
40✔
420
            ]
40✔
421
        )['template'];
40✔
422
    }
423

424
    /**
425
     * Converts the contents of the provided row's Flux-enabled field,
426
     * at the same time running through the inheritance tree generated
427
     * by getInheritanceTree() in order to apply inherited values.
428
     */
429
    public function getFlexFormValues(array $row, ?string $forField = null): array
430
    {
431
        $fieldName = $forField ?? $this->getFieldName($row);
4✔
432
        $form = $this->getForm($row, $fieldName);
4✔
433
        return $this->formDataTransformer->convertFlexFormContentToArray($row[$fieldName] ?? '', $form);
4✔
434
    }
435

436
    /**
437
     * Returns the page record with localisation applied, if any
438
     * exists in database. Maintains uid and pid of the original
439
     * page if localisation is applied.
440
     */
441
    protected function getPageValues(): array
442
    {
443
        $record = $GLOBALS['TSFE']->page ?? null;
×
444
        if (!$record) {
×
445
            return [];
×
446
        }
447
        return $record;
×
448
    }
449

450
    public function getTemplateVariables(array $row): array
451
    {
452
        $variables = array_merge(
12✔
453
            $this->templateVariables,
12✔
454
            $this->getViewVariables($row)
12✔
455
        );
12✔
456
        $variables['page'] = $this->getPageValues();
12✔
457
        $variables['user'] = $GLOBALS['TSFE']->fe_user->user ?? [];
12✔
458
        if (file_exists((string) $this->getTemplatePathAndFilename($row))) {
12✔
459
            $variables['grid'] = $this->getGrid($row);
4✔
460
            $variables['form'] = $this->getForm($row);
4✔
461
        }
462
        return $variables;
12✔
463
    }
464

465
    public function getConfigurationSectionName(array $row, ?string $forField = null): ?string
466
    {
467
        unset($row, $forField);
20✔
468
        return $this->configurationSectionName;
20✔
469
    }
470

471
    public function getExtensionKey(array $row, ?string $forField = null): string
472
    {
473
        unset($row);
44✔
474
        return $this->extensionKey;
44✔
475
    }
476

477
    public function getPriority(array $row): int
478
    {
479
        unset($row);
4✔
480
        return $this->priority;
4✔
481
    }
482

483
    public function getName(): string
484
    {
485
        return $this->name;
8✔
486
    }
487

488
    public function getPluginName(): ?string
489
    {
490
        return $this->pluginName;
12✔
491
    }
492

493
    public function setPluginName(?string $pluginName): self
494
    {
495
        $this->pluginName = $pluginName;
4✔
496
        return $this;
4✔
497
    }
498

499
    /**
500
     * Post-process record data for the table that this ConfigurationProvider
501
     * is attached to.
502
     *
503
     * @param string $operation TYPO3 operation identifier, i.e. "update", "new" etc.
504
     * @param integer $id The ID of the current record (which is sometimes not included in $row)
505
     * @param array $row the record that was modified
506
     * @param DataHandler $reference A reference to the DataHandler object that modified the record
507
     * @param array $removals Allows overridden methods to pass an array of fields to remove from the stored Flux value
508
     * @return bool true to stop processing other providers, false to continue processing other providers.
509
     */
510
    public function postProcessRecord(
511
        string $operation,
512
        int $id,
513
        array $row,
514
        DataHandler $reference,
515
        array $removals = []
516
    ): bool {
517
        if (!in_array($operation, ['update', 'new'], true)) {
12✔
518
            return false;
×
519
        }
520

521
        $record = $reference->datamap[$this->tableName][$id] ?? $row;
12✔
522
        $tableName = (string) $this->getTableName($record);
12✔
523
        $fieldName = $this->getFieldName($record);
12✔
524

525
        $dontProcess = (
12✔
526
            $fieldName === null
12✔
527
            || !isset($record[$fieldName])
12✔
528
            || !isset($record[$fieldName]['data'])
12✔
529
            || !is_array($record[$fieldName]['data'])
12✔
530
        );
12✔
531
        if ($dontProcess) {
12✔
532
            return false;
4✔
533
        }
534

535
        $stored = $this->recordService->getSingle($tableName, '*', $id) ?? $record;
8✔
536

537
        $removals = array_merge(
8✔
538
            $removals,
8✔
539
            $this->extractFieldNamesToClear($record, $fieldName)
8✔
540
        );
8✔
541

542
        if (!empty($removals) && !empty($stored[$fieldName])) {
8✔
543
            $stored[$fieldName] = MiscellaneousUtility::cleanFlexFormXml($stored[$fieldName], $removals);
8✔
544
            $this->recordService->update($tableName, $stored);
8✔
545
        }
546

547
        return false;
8✔
548
    }
549

550
    protected function extractFieldNamesToClear(array $record, string $fieldName): array
551
    {
552
        return $this->extractWizardTaggedFieldNames($record, $fieldName, 'clear');
12✔
553
    }
554

555
    protected function extractFieldNamesToProtect(array $record, string $fieldName): array
556
    {
557
        return $this->extractWizardTaggedFieldNames($record, $fieldName, 'protect');
4✔
558
    }
559

560
    protected function extractWizardTaggedFieldNames(array $record, string $fieldName, string $wizardName): array
561
    {
562
        $wizardTagName = '_' . $wizardName;
12✔
563
        $tagLength = strlen($wizardTagName);
12✔
564
        $fieldNames = [];
12✔
565
        $data = $record[$fieldName]['data'] ?? [];
12✔
566
        foreach ($data as $sheetName => $sheetFields) {
12✔
567
            foreach ($sheetFields['lDEF'] as $sheetFieldName => $fieldDefinition) {
12✔
568
                if ($wizardTagName === substr($sheetFieldName, -$tagLength)) {
12✔
569
                    $fieldNames[] = $sheetFieldName;
12✔
570
                } else {
571
                    $wizardFieldName = $sheetFieldName . $wizardTagName;
12✔
572
                    if (isset($data[$sheetName]['lDEF'][$wizardFieldName]['vDEF'])) {
12✔
573
                        if ((bool) $data[$sheetName]['lDEF'][$wizardFieldName]['vDEF']) {
12✔
574
                            $fieldNames[] = $sheetFieldName;
12✔
575
                        }
576
                    }
577
                }
578
            }
579
        }
580
        return array_unique($fieldNames);
12✔
581
    }
582

583
    /**
584
     * Post-process the TCEforms DataStructure for a record associated
585
     * with this ConfigurationProvider.
586
     */
587
    public function postProcessDataStructure(array &$row, ?array &$dataStructure, array $conf): void
588
    {
589
        $form = $this->getForm($row, $conf['fieldName'] ?? null);
8✔
590
        if ($dataStructure !== null && $form !== null) {
8✔
591
            // Last minute set the field name. Ensures the field name matches the one given with the DS identifier,
592
            // even if the Form instance was resolved from another field (e.g. inherited Form in page template).
593
            $form->setOption(
8✔
594
                FormOption::RECORD_FIELD,
8✔
595
                $conf['fieldName'] ?? $form->getOption(FormOption::RECORD_FIELD)
8✔
596
            );
8✔
597
            $dataStructure = array_replace_recursive($dataStructure, $form->build());
8✔
598
        }
599
    }
600

601
    /**
602
     * Processes the table configuration (TCA) for the table associated
603
     * with this Provider, as determined by the trigger() method. Gets
604
     * passed an instance of the record being edited/created along with
605
     * the current configuration array - and must return a complete copy
606
     * of the configuration array manipulated to the Provider's needs.
607
     *
608
     * @param array $row The record being edited/created
609
     * @param array $configuration Current TCA configuration
610
     * @return array The large FormEngine configuration array - see FormEngine documentation!
611
     */
612
    public function processTableConfiguration(array $row, array $configuration): array
613
    {
614
        $form = $this->getForm($row);
20✔
615
        if (!$form) {
20✔
616
            return $configuration;
×
617
        }
618

619
        /** @var string $table */
620
        $table = $this->getTableName($row);
20✔
621
        $recordType = $configuration['recordTypeValue'];
20✔
622

623
        // Replace or add fields as native TCA fields if defined as native=1 in the Flux form:
624
        foreach ($form->getFields() as $fieldName => $field) {
20✔
625
            if (!$field instanceof Form\FieldInterface || !$field->isNative()) {
4✔
626
                continue;
×
627
            }
628

629
            // Basic initialization: declare the TCA field's data structure and initialize it in databaseRow.
630
            $configuration['processedTca']['columns'][$fieldName] = $field->build();
4✔
631
            if (!in_array($fieldName, $configuration['columnsToProcess'], true)) {
4✔
632
                $configuration['columnsToProcess'][] = $fieldName;
4✔
633
                $configuration['databaseRow'][$fieldName] = $configuration['databaseRow'][$fieldName]
4✔
634
                    ?? $field->getDefault();
4✔
635
            }
636

637
            // Handle potential positioning instructions.
638
            $positionOption = $field->getPosition();
4✔
639
            if (!empty($positionOption)) {
4✔
640
                $insertFieldDefinition = $fieldName;
4✔
641
                if (strpos($positionOption, ' ') !== false) {
4✔
642
                    [$position, $sheet] = explode(' ', $positionOption, 2);
4✔
643
                    $insertFieldDefinition = '--div--;' . $sheet . ',' . $fieldName;
4✔
644
                } else {
645
                    $position = $positionOption;
×
646
                }
647
                ExtensionManagementUtility::addToAllTCAtypes($table, $insertFieldDefinition, $recordType, $position);
4✔
648
                $configuration['processedTca']['types'][$recordType]['showitem']
4✔
649
                    = $GLOBALS['TCA'][$table]['types'][$recordType]['showitem'];
4✔
650
            }
651
        }
652

653
        // Remove any fields listed in the "hideNativeFields" Flux form option
654
        /** @var string|array $hideFieldsOption */
655
        $hideFieldsOption = $form->getOption(FormOption::HIDE_NATIVE_FIELDS);
20✔
656
        if (!empty($hideFieldsOption)) {
20✔
657
            $hideFields = is_array($hideFieldsOption)
4✔
658
                ? $hideFieldsOption
×
659
                : GeneralUtility::trimExplode(',', $hideFieldsOption, true);
4✔
660
            foreach ($hideFields as $hideField) {
4✔
661
                unset($configuration['processedTca']['columns'][$hideField]);
4✔
662
            }
663
        }
664
        return $configuration;
20✔
665
    }
666

667
    protected function getViewForRecord(array $row, ?string $forField = null): ViewInterface
668
    {
669
        return $this->viewBuilder->buildTemplateView(
4✔
670
            $this->getControllerExtensionKeyFromRecord($row, $forField),
4✔
671
            $this->getControllerNameFromRecord($row),
4✔
672
            $this->getControllerActionFromRecord($row, $forField),
4✔
673
            $this->getPluginName() ?? $this->getControllerNameFromRecord($row),
4✔
674
            $this->getTemplatePathAndFilename($row, $forField)
4✔
675
        );
4✔
676
    }
677

678
    /**
679
     * Get preview chunks - header and content - as
680
     * [string $headerContent, string $previewContent, boolean $continueRendering]
681
     *
682
     * Default implementation renders the Preview section from the template
683
     * file that the actual Provider returns for $row, using paths also
684
     * determined by $row. Example: fluidcontent's Provider returns files
685
     * and paths based on selected "Fluid Content type" and inherits and
686
     * uses this method to render a Preview from the template file in the
687
     * specific path. This default implementation expects the TYPO3 core
688
     * to render the default header, so it returns NULL as $headerContent.
689
     */
690
    public function getPreview(array $row): array
691
    {
692
        $previewContent = $this->viewBuilder->buildPreviewView(
4✔
693
            $this->getControllerExtensionKeyFromRecord($row),
4✔
694
            $this->getControllerNameFromRecord($row),
4✔
695
            $this->getControllerActionFromRecord($row),
4✔
696
            $this->getPluginName() ?? $this->getControllerNameFromRecord($row),
4✔
697
            $this->getTemplatePathAndFilename($row)
4✔
698
        )->getPreview($this, $row);
4✔
699
        return [null, $previewContent, empty($previewContent)];
4✔
700
    }
701

702
    protected function getCacheKeyForStoredVariable(array $row, string $variable, ?string $forField = null): string
703
    {
704
        return implode(
24✔
705
            '-',
24✔
706
            [
24✔
707
                'flux',
24✔
708
                'storedvariable',
24✔
709
                $this->getTableName($row),
24✔
710
                $forField ?? $this->getFieldName($row),
24✔
711
                $row['uid'] ?? 0,
24✔
712
                $this->getControllerExtensionKeyFromRecord($row, $forField),
24✔
713
                $this->getControllerActionFromRecord($row, $forField),
24✔
714
                $variable
24✔
715
            ]
24✔
716
        );
24✔
717
    }
718

719
    /**
720
     * Stub: override this to return a controller name associated with $row.
721
     * Default strategy: return base name of Provider class minus the "Provider" suffix.
722
     */
723
    public function getControllerNameFromRecord(array $row): string
724
    {
725
        if (!empty($this->controllerName)) {
28✔
726
            return $this->controllerName;
4✔
727
        }
728
        $class = get_class($this);
24✔
729
        $separator = false !== strpos($class, '\\') ? '\\' : '_';
24✔
730
        $parts = explode($separator, $class);
24✔
731
        $base = end($parts);
24✔
732
        return substr($base, 0, -8);
24✔
733
    }
734

735
    /**
736
     * Stub: Get the extension key of the controller associated with $row
737
     */
738
    public function getControllerExtensionKeyFromRecord(array $row, ?string $forField = null): string
739
    {
740
        return $this->extensionKey;
50✔
741
    }
742

743
    /**
744
     * Stub: Get the package name of the controller associated with $row
745
     */
746
    public function getControllerPackageNameFromRecord(array $row, ?string $forField = null): string
747
    {
748
        $extensionKey = $this->getControllerExtensionKeyFromRecord($row, $forField);
12✔
749
        $extensionName = ExtensionNamingUtility::getExtensionName($extensionKey);
12✔
750
        $vendor = ExtensionNamingUtility::getVendorName($extensionKey);
12✔
751
        return null !== $vendor ? $vendor . '.' . $extensionName : $extensionName;
12✔
752
    }
753

754
    /**
755
     * Stub: Get the name of the controller action associated with $row
756
     */
757
    public function getControllerActionFromRecord(array $row, ?string $forField = null): string
758
    {
759
        return $this->controllerAction;
60✔
760
    }
761

762
    /**
763
     * Stub: Get a compacted controller name + action name string
764
     */
765
    public function getControllerActionReferenceFromRecord(array $row, ?string $forField = null): string
766
    {
767
        return $this->getControllerNameFromRecord($row) . '->' . $this->getControllerActionFromRecord($row, $forField);
4✔
768
    }
769

770
    public function setTableName(string $tableName): self
771
    {
772
        $this->tableName = $tableName;
52✔
773
        return $this;
52✔
774
    }
775

776
    public function setFieldName(?string $fieldName): self
777
    {
778
        $this->fieldName = $fieldName;
56✔
779
        return $this;
56✔
780
    }
781

782
    public function setExtensionKey(string $extensionKey): self
783
    {
784
        $this->extensionKey = $extensionKey;
36✔
785
        return $this;
36✔
786
    }
787

788
    public function setControllerName(string $controllerName): self
789
    {
790
        $this->controllerName = $controllerName;
×
791
        return $this;
×
792
    }
793

794
    public function setControllerAction(string $controllerAction): self
795
    {
796
        $this->controllerAction = $controllerAction;
4✔
797
        return $this;
4✔
798
    }
799

800
    public function setTemplateVariables(?array $templateVariables): self
801
    {
802
        $this->templateVariables = $templateVariables ?? [];
20✔
803
        return $this;
20✔
804
    }
805

806
    public function setTemplatePathAndFilename(?string $templatePathAndFilename): self
807
    {
808
        $this->templatePathAndFilename = $templatePathAndFilename;
24✔
809
        return $this;
24✔
810
    }
811

812
    public function setTemplatePaths(?array $templatePaths): self
813
    {
814
        $this->templatePaths = $templatePaths;
16✔
815
        return $this;
16✔
816
    }
817

818
    public function setConfigurationSectionName(?string $configurationSectionName): self
819
    {
820
        $this->configurationSectionName = $configurationSectionName;
4✔
821
        return $this;
4✔
822
    }
823

824
    public function setName(string $name): self
825
    {
826
        $this->name = $name;
16✔
827
        return $this;
16✔
828
    }
829

830
    public function setForm(Form $form): self
831
    {
832
        $this->form = $form;
84✔
833
        return $this;
84✔
834
    }
835

836
    public function setGrid(Grid $grid): self
837
    {
838
        $this->grid = $grid;
76✔
839
        return $this;
76✔
840
    }
841

842
    /**
843
     * @codeCoverageIgnore
844
     */
845
    protected function dispatchFlashMessageForException(\Throwable $error): void
846
    {
847
        if (VersionUtility::isCoreAtLeast13()) {
848
            $level = ContextualFeedbackSeverity::ERROR;
849
        } else {
850
            $level = FlashMessage::ERROR;
851
        }
852
        /** @var FlashMessage $flashMesasage */
853
        $flashMesasage = GeneralUtility::makeInstance(
854
            FlashMessage::class,
855
            $error->getMessage(),
856
            '',
857
            $level
858
        );
859
        /** @var FlashMessageService $flashMessageService */
860
        $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
861
        $flashMesasageQueue = $flashMessageService->getMessageQueueByIdentifier();
862
        $flashMesasageQueue->enqueue($flashMesasage);
863
    }
864

865
    /**
866
     * @codeCoverageIgnore
867
     */
868
    protected function resolveAbsolutePathToFile(?string $file): ?string
869
    {
870
        return $file === null ? null : GeneralUtility::getFileAbsFileName($file);
871
    }
872
}
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