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

FluidTYPO3 / flux / 12237686280

09 Dec 2024 02:27PM UTC coverage: 92.9% (-0.5%) from 93.421%
12237686280

push

github

NamelessCoder
[TER] 10.1.0

7013 of 7549 relevant lines covered (92.9%)

56.22 hits per line

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

96.95
/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\Utility\ExtensionManagementUtility;
29
use TYPO3\CMS\Core\Utility\GeneralUtility;
30
use TYPO3\CMS\Core\Utility\PathUtility;
31
use TYPO3\CMS\Extbase\Reflection\ObjectAccess;
32
use TYPO3\CMS\Fluid\View\TemplatePaths;
33
use TYPO3Fluid\Fluid\View\Exception\InvalidTemplateResourceException;
34
use TYPO3Fluid\Fluid\View\ViewInterface;
35

36
class AbstractProvider implements ProviderInterface
37
{
38
    const 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;
726✔
90
        $this->recordService = $recordService;
726✔
91
        $this->viewBuilder = $viewBuilder;
726✔
92
        $this->cacheService = $cacheService;
726✔
93
        $this->typoScriptService = $typoScriptService;
726✔
94
    }
95

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

126
    public function trigger(array $row, ?string $table, ?string $field, ?string $extensionKey = null): bool
127
    {
128
        $providerFieldName = $this->getFieldName($row);
60✔
129
        $providerTableName = $this->getTableName($row);
60✔
130
        $providerExtensionKey = $this->extensionKey;
60✔
131
        $contentObjectType = $this->contentObjectType;
60✔
132
        $listType = $this->listType;
60✔
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;
60✔
137
        $pluginTypeFromRecord = $row['list_type'] ?? null;
60✔
138

139
        $rowContainsPlugin = $contentTypeFromRecord === static::CONTENT_OBJECT_TYPE_LIST;
60✔
140
        $isContentRecord = $table === 'tt_content';
60✔
141
        $rowIsEmpty = (0 === count($row));
60✔
142
        $matchesContentType = $contentTypeFromRecord === $contentObjectType;
60✔
143
        $matchesPluginType = $rowContainsPlugin && $pluginTypeFromRecord === $listType;
60✔
144
        $matchesTableName = ($providerTableName === $table || !$table);
60✔
145
        $matchesFieldName = ($providerFieldName === $field || !$field);
60✔
146
        $matchesExtensionKey = ($providerExtensionKey === $extensionKey || !$extensionKey);
60✔
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
60✔
151
            && (!$isContentRecord || ($matchesContentType && ((!$rowContainsPlugin) || $matchesPluginType)));
60✔
152
        $isFallbackMatch = ($matchesTableName && $matchesFieldName && $rowIsEmpty);
60✔
153
        return ($isFullMatch || $isFallbackMatch);
60✔
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);
18✔
167
        $packageKey = str_replace('.', '\\', $packageName);
18✔
168
        $controllerName = $this->getControllerNameFromRecord($row);
18✔
169
        $action = $this->getControllerActionFromRecord($row, $forField);
18✔
170
        $expectedClassName = sprintf(static::FORM_CLASS_PATTERN, $packageKey, $controllerName, ucfirst($action));
18✔
171
        return class_exists($expectedClassName) ? $expectedClassName : null;
18✔
172
    }
173

174
    protected function getViewVariables(array $row, ?string $forField = null): array
175
    {
176
        $extensionKey = (string) $this->getExtensionKey($row, $forField);
48✔
177
        $fieldName = $forField ?? $this->getFieldName($row);
48✔
178
        $variables = [
48✔
179
            'record' => $row,
48✔
180
            'settings' => $this->typoScriptService->getSettingsForExtensionName($extensionKey),
48✔
181
            'forField' => $forField,
48✔
182
        ];
48✔
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]) && !is_array($row[$fieldName])) {
48✔
189
            $recordVariables = $this->formDataTransformer->convertFlexFormContentToArray($row[$fieldName]);
×
190
            $variables = RecursiveArrayUtility::mergeRecursiveOverrule($variables, $recordVariables);
×
191
        }
192

193
        $variables = RecursiveArrayUtility::mergeRecursiveOverrule($this->templateVariables, $variables);
48✔
194

195
        return $variables;
48✔
196
    }
197

198
    public function getForm(array $row, ?string $forField = null): ?Form
199
    {
200
        /** @var Form $form */
201
        $form = $this->form
54✔
202
            ?? $this->createCustomFormInstance($row, $forField)
54✔
203
            ?? $this->extractConfiguration($row, 'form', $forField)
54✔
204
            ?? Form::create();
34✔
205
        $form->setOption(FormOption::RECORD, $row);
54✔
206
        $form->setOption(FormOption::RECORD_TABLE, $this->getTableName($row));
54✔
207
        $form->setOption(FormOption::RECORD_FIELD, $forField ?? $this->getFieldName($row));
54✔
208
        return $form;
54✔
209
    }
210

211
    protected function createCustomFormInstance(array $row, ?string $forField = null): ?Form
212
    {
213
        $formClassName = $this->resolveFormClassName($row, $forField);
42✔
214
        if ($formClassName !== null && class_exists($formClassName)) {
42✔
215
            $tableName = $this->getTableName($row);
24✔
216
            $fieldName = $forField ?? $this->getFieldName($row);
24✔
217
            $id = 'row_' . $row['uid'];
24✔
218
            if ($tableName) {
24✔
219
                $id = $tableName;
12✔
220
            }
221
            if ($fieldName) {
24✔
222
                $id .= '_' . $fieldName;
12✔
223
            }
224
            return $formClassName::create(['id' => $id]);
24✔
225
        }
226
        return null;
18✔
227
    }
228

229
    public function getGrid(array $row): Grid
230
    {
231
        if ($this->grid instanceof Grid) {
114✔
232
            return $this->grid;
96✔
233
        }
234
        $form = $this->getForm($row);
18✔
235
        if ($form) {
18✔
236
            $container = $this->detectContentContainerParent($form);
12✔
237
            if ($container) {
12✔
238
                $values = $this->getFlexFormValues($row);
12✔
239
                $contentContainer = $container->getContentContainer();
12✔
240
                $persistedObjects = [];
12✔
241
                if ($contentContainer instanceof Form\Container\SectionObject) {
12✔
242
                    $persistedObjects = array_column(
12✔
243
                        (array) (ObjectAccess::getProperty($values, (string) $container->getName()) ?? []),
12✔
244
                        (string) $contentContainer->getName()
12✔
245
                    );
12✔
246
                }
247

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

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

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

321
        try {
322
            if ($configurationSectionName) {
24✔
323
                $view->renderSection($configurationSectionName, $viewVariables, false);
18✔
324
            } else {
325
                $view->assignMultiple($viewVariables);
6✔
326
                $view->render();
18✔
327
            }
328
        } catch (InvalidTemplateResourceException $exception) {
6✔
329
            $this->dispatchFlashMessageForException($exception);
6✔
330
            return null;
6✔
331
        }
332

333
        $variables = $view->getRenderingContext()->getViewHelperVariableContainer()->getAll(FormViewHelper::class, []);
18✔
334
        $cachePersistent = false;
18✔
335
        if (isset($variables['form'])) {
18✔
336
            $variables['form']->setOption(
12✔
337
                FormOption::TEMPLATE_FILE,
12✔
338
                $this->getTemplatePathAndFilename($row, $forField)
12✔
339
            );
12✔
340
            $cachePersistent = (boolean) $variables['form']->getOption(FormOption::STATIC);
12✔
341
        }
342

343
        $this->cacheService->setInCaches(
18✔
344
            $variables,
18✔
345
            $cachePersistent,
18✔
346
            $cacheKeyAll
18✔
347
        );
18✔
348

349
        $returnValue = $name ? ($variables[$name] ?? null) : $variables;
18✔
350

351
        return HookHandler::trigger(
18✔
352
            HookHandler::PROVIDER_EXTRACTED_OBJECT,
18✔
353
            [
18✔
354
                'record' => $row,
18✔
355
                'name' => $name,
18✔
356
                'value' => $returnValue
18✔
357
            ]
18✔
358
        )['value'];
18✔
359
    }
360

361
    public function setListType(string $listType): self
362
    {
363
        $this->listType = $listType;
24✔
364
        return $this;
24✔
365
    }
366

367
    public function getListType(): string
368
    {
369
        return $this->listType;
12✔
370
    }
371

372
    public function setContentObjectType(string $contentObjectType): self
373
    {
374
        $this->contentObjectType = $contentObjectType;
30✔
375
        return $this;
30✔
376
    }
377

378
    public function getContentObjectType(): string
379
    {
380
        return $this->contentObjectType;
6✔
381
    }
382

383
    public function getFieldName(array $row): ?string
384
    {
385
        return $this->fieldName;
252✔
386
    }
387

388
    public function getParentFieldName(array $row): ?string
389
    {
390
        unset($row);
6✔
391
        return $this->parentFieldName;
6✔
392
    }
393

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

400
    public function getTemplatePathAndFilename(array $row, ?string $forField = null): ?string
401
    {
402
        $templatePathAndFilename = (string) $this->templatePathAndFilename;
60✔
403
        if ($templatePathAndFilename !== '' && !PathUtility::isAbsolutePath($templatePathAndFilename)) {
60✔
404
            $templatePathAndFilename = $this->resolveAbsolutePathToFile($templatePathAndFilename);
6✔
405
        }
406
        if (true === empty($templatePathAndFilename)) {
60✔
407
            $templatePathAndFilename = null;
48✔
408
        }
409
        return HookHandler::trigger(
60✔
410
            HookHandler::PROVIDER_RESOLVED_TEMPLATE,
60✔
411
            [
60✔
412
                'template' => $templatePathAndFilename,
60✔
413
                'provider' => $this,
60✔
414
                'record' => $row
60✔
415
            ]
60✔
416
        )['template'];
60✔
417
    }
418

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

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

445
    public function getTemplateVariables(array $row): array
446
    {
447
        $variables = array_merge(
24✔
448
            $this->templateVariables,
24✔
449
            $this->getViewVariables($row)
24✔
450
        );
24✔
451
        $variables['page'] = $this->getPageValues();
24✔
452
        $variables['user'] = $GLOBALS['TSFE']->fe_user->user ?? [];
24✔
453
        if (file_exists((string) $this->getTemplatePathAndFilename($row))) {
24✔
454
            $variables['grid'] = $this->getGrid($row);
6✔
455
            $variables['form'] = $this->getForm($row);
6✔
456
        }
457
        return $variables;
24✔
458
    }
459

460
    public function getConfigurationSectionName(array $row, ?string $forField = null): ?string
461
    {
462
        unset($row, $forField);
30✔
463
        return $this->configurationSectionName;
30✔
464
    }
465

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

472
    public function getPriority(array $row): int
473
    {
474
        unset($row);
6✔
475
        return $this->priority;
6✔
476
    }
477

478
    public function getName(): string
479
    {
480
        return $this->name;
12✔
481
    }
482

483
    public function getPluginName(): ?string
484
    {
485
        return $this->pluginName;
18✔
486
    }
487

488
    public function setPluginName(?string $pluginName): self
489
    {
490
        $this->pluginName = $pluginName;
6✔
491
        return $this;
6✔
492
    }
493

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

516
        $record = $reference->datamap[$this->tableName][$id] ?? $row;
18✔
517
        $tableName = (string) $this->getTableName($record);
18✔
518
        $fieldName = $this->getFieldName($record);
18✔
519

520
        $dontProcess = (
18✔
521
            $fieldName === null
18✔
522
            || !isset($record[$fieldName])
18✔
523
            || !isset($record[$fieldName]['data'])
18✔
524
            || !is_array($record[$fieldName]['data'])
18✔
525
        );
18✔
526
        if ($dontProcess) {
18✔
527
            return false;
6✔
528
        }
529

530
        $stored = $this->recordService->getSingle($tableName, '*', $id) ?? $record;
12✔
531

532
        $removals = array_merge(
12✔
533
            $removals,
12✔
534
            $this->extractFieldNamesToClear($record, $fieldName)
12✔
535
        );
12✔
536

537
        if (!empty($removals) && !empty($stored[$fieldName])) {
12✔
538
            $stored[$fieldName] = MiscellaneousUtility::cleanFlexFormXml($stored[$fieldName], $removals);
12✔
539
            $this->recordService->update($tableName, $stored);
12✔
540
        }
541

542
        return false;
12✔
543
    }
544

545
    protected function extractFieldNamesToClear(array $record, string $fieldName): array
546
    {
547
        return $this->extractWizardTaggedFieldNames($record, $fieldName, 'clear');
18✔
548
    }
549

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

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

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

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

614
        /** @var string $table */
615
        $table = $this->getTableName($row);
30✔
616
        $recordType = $configuration['recordTypeValue'];
30✔
617

618
        // Replace or add fields as native TCA fields if defined as native=1 in the Flux form:
619
        foreach ($form->getFields() as $fieldName => $field) {
30✔
620
            if (!$field instanceof Form\FieldInterface || !$field->isNative()) {
6✔
621
                continue;
×
622
            }
623

624
            // Basic initialization: declare the TCA field's data structure and initialize it in databaseRow.
625
            $configuration['processedTca']['columns'][$fieldName] = $field->build();
6✔
626
            if (!in_array($fieldName, $configuration['columnsToProcess'], true)) {
6✔
627
                $configuration['columnsToProcess'][] = $fieldName;
6✔
628
                $configuration['databaseRow'][$fieldName] = $configuration['databaseRow'][$fieldName]
6✔
629
                    ?? $field->getDefault();
6✔
630
            }
631

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

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

662
    protected function getViewForRecord(array $row, ?string $forField = null): ViewInterface
663
    {
664
        return $this->viewBuilder->buildTemplateView(
6✔
665
            $this->getControllerExtensionKeyFromRecord($row, $forField),
6✔
666
            $this->getControllerNameFromRecord($row),
6✔
667
            $this->getControllerActionFromRecord($row, $forField),
6✔
668
            $this->getPluginName() ?? $this->getControllerNameFromRecord($row),
6✔
669
            $this->getTemplatePathAndFilename($row, $forField)
6✔
670
        );
6✔
671
    }
672

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

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

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

730
    /**
731
     * Stub: Get the extension key of the controller associated with $row
732
     */
733
    public function getControllerExtensionKeyFromRecord(array $row, ?string $forField = null): string
734
    {
735
        return $this->extensionKey;
78✔
736
    }
737

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

749
    /**
750
     * Stub: Get the name of the controller action associated with $row
751
     */
752
    public function getControllerActionFromRecord(array $row, ?string $forField = null): string
753
    {
754
        return $this->controllerAction;
90✔
755
    }
756

757
    /**
758
     * Stub: Get a compacted controller name + action name string
759
     */
760
    public function getControllerActionReferenceFromRecord(array $row, ?string $forField = null): string
761
    {
762
        return $this->getControllerNameFromRecord($row) . '->' . $this->getControllerActionFromRecord($row, $forField);
6✔
763
    }
764

765
    public function setTableName(string $tableName): self
766
    {
767
        $this->tableName = $tableName;
78✔
768
        return $this;
78✔
769
    }
770

771
    public function setFieldName(?string $fieldName): self
772
    {
773
        $this->fieldName = $fieldName;
84✔
774
        return $this;
84✔
775
    }
776

777
    public function setExtensionKey(string $extensionKey): self
778
    {
779
        $this->extensionKey = $extensionKey;
54✔
780
        return $this;
54✔
781
    }
782

783
    public function setControllerName(string $controllerName): self
784
    {
785
        $this->controllerName = $controllerName;
×
786
        return $this;
×
787
    }
788

789
    public function setControllerAction(string $controllerAction): self
790
    {
791
        $this->controllerAction = $controllerAction;
6✔
792
        return $this;
6✔
793
    }
794

795
    public function setTemplateVariables(?array $templateVariables): self
796
    {
797
        $this->templateVariables = $templateVariables ?? [];
30✔
798
        return $this;
30✔
799
    }
800

801
    public function setTemplatePathAndFilename(?string $templatePathAndFilename): self
802
    {
803
        $this->templatePathAndFilename = $templatePathAndFilename;
36✔
804
        return $this;
36✔
805
    }
806

807
    public function setTemplatePaths(?array $templatePaths): self
808
    {
809
        $this->templatePaths = $templatePaths;
24✔
810
        return $this;
24✔
811
    }
812

813
    public function setConfigurationSectionName(?string $configurationSectionName): self
814
    {
815
        $this->configurationSectionName = $configurationSectionName;
6✔
816
        return $this;
6✔
817
    }
818

819
    public function setName(string $name): self
820
    {
821
        $this->name = $name;
24✔
822
        return $this;
24✔
823
    }
824

825
    public function setForm(Form $form): self
826
    {
827
        $this->form = $form;
126✔
828
        return $this;
126✔
829
    }
830

831
    public function setGrid(Grid $grid): self
832
    {
833
        $this->grid = $grid;
120✔
834
        return $this;
120✔
835
    }
836

837
    /**
838
     * @codeCoverageIgnore
839
     */
840
    protected function dispatchFlashMessageForException(\Throwable $error): void
841
    {
842
        /** @var FlashMessage $flashMesasage */
843
        $flashMesasage = GeneralUtility::makeInstance(
844
            FlashMessage::class,
845
            $error->getMessage(),
846
            '',
847
            FlashMessage::ERROR
848
        );
849
        /** @var FlashMessageService $flashMessageService */
850
        $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
851
        $flashMesasageQueue = $flashMessageService->getMessageQueueByIdentifier();
852
        $flashMesasageQueue->enqueue($flashMesasage);
853
    }
854

855
    /**
856
     * @codeCoverageIgnore
857
     */
858
    protected function resolveAbsolutePathToFile(?string $file): ?string
859
    {
860
        return $file === null ? null : GeneralUtility::getFileAbsFileName($file);
861
    }
862

863
    /**
864
     * @param string|array $extensionKeyOrConfiguration
865
     * @codeCoverageIgnore
866
     */
867
    protected function createTemplatePaths($extensionKeyOrConfiguration): TemplatePaths
868
    {
869
        /** @var TemplatePaths $paths */
870
        $paths = GeneralUtility::makeInstance(TemplatePaths::class, $extensionKeyOrConfiguration);
871
        return $paths;
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