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

FluidTYPO3 / flux / 14971594147

12 May 2025 11:57AM UTC coverage: 93.21% (-0.04%) from 93.246%
14971594147

push

github

NamelessCoder
[BUGFIX] Provider variables in Configuration section even if FF data is array

Fixes an issue where some contexts in TYPO3 BE would not see the variables
coming from the Flux fields, when rendering the Configuration section. This
patch adds support for the case where FF data is an array instead of XML.

1 of 5 new or added lines in 1 file covered. (20.0%)

1 existing line in 1 file now uncovered.

7083 of 7599 relevant lines covered (93.21%)

66.29 hits per line

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

96.15
/Classes/Provider/AbstractProvider.php
1
<?php
2
declare(strict_types=1);
3
namespace FluidTYPO3\Flux\Provider;
4

5
/*
6
 * This file is part of the FluidTYPO3/Flux project under GPLv2 or later.
7
 *
8
 * For the full copyright and license information, please read the
9
 * LICENSE.md file that was distributed with this source code.
10
 */
11

12
use FluidTYPO3\Flux\Builder\ViewBuilder;
13
use FluidTYPO3\Flux\Enum\FormOption;
14
use FluidTYPO3\Flux\Form;
15
use FluidTYPO3\Flux\Form\Container\Grid;
16
use FluidTYPO3\Flux\Form\Transformation\FormDataTransformer;
17
use FluidTYPO3\Flux\Hooks\HookHandler;
18
use FluidTYPO3\Flux\Service\CacheService;
19
use FluidTYPO3\Flux\Service\TypoScriptService;
20
use FluidTYPO3\Flux\Service\WorkspacesAwareRecordService;
21
use FluidTYPO3\Flux\Utility\ExtensionNamingUtility;
22
use FluidTYPO3\Flux\Utility\MiscellaneousUtility;
23
use FluidTYPO3\Flux\Utility\RecursiveArrayUtility;
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\Core\Utility\VersionNumberUtility;
33
use TYPO3\CMS\Extbase\Reflection\ObjectAccess;
34
use TYPO3Fluid\Fluid\View\Exception\InvalidTemplateResourceException;
35
use TYPO3Fluid\Fluid\View\ViewInterface;
36

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

201
        return $variables;
56✔
202
    }
203

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

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

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

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

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

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

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

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

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

355
        $returnValue = $name ? ($variables[$name] ?? null) : $variables;
21✔
356

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

548
        return false;
14✔
549
    }
550

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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