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

FluidTYPO3 / flux / 14971640792

12 May 2025 12:00PM UTC coverage: 93.21% (-0.04%) from 93.246%
14971640792

push

github

NamelessCoder
[TER] 11.0.3

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✔
190
            if (is_array($row[$fieldName])) {
×
191
                $xml = GeneralUtility::array2xml($row[$fieldName]);
×
192
            } else {
193
                $xml = $row[$fieldName];
×
194
            }
195
            $recordVariables = $this->formDataTransformer->convertFlexFormContentToArray($xml);
×
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