• 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

94.38
/Classes/Form/AbstractFormComponent.php
1
<?php
2
declare(strict_types=1);
3
namespace FluidTYPO3\Flux\Form;
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\Enum\ExtensionOption;
13
use FluidTYPO3\Flux\Enum\FormOption;
14
use FluidTYPO3\Flux\Form;
15
use FluidTYPO3\Flux\Form\Container\Column;
16
use FluidTYPO3\Flux\Form\Container\Container;
17
use FluidTYPO3\Flux\Form\Container\Grid;
18
use FluidTYPO3\Flux\Form\Container\Section;
19
use FluidTYPO3\Flux\Form\Container\SectionObject;
20
use FluidTYPO3\Flux\Form\Container\Sheet;
21
use FluidTYPO3\Flux\Hooks\HookHandler;
22
use FluidTYPO3\Flux\Utility\ExtensionConfigurationUtility;
23
use FluidTYPO3\Flux\Utility\ExtensionNamingUtility;
24
use FluidTYPO3\Flux\Utility\RecursiveArrayUtility;
25
use TYPO3\CMS\Core\Utility\GeneralUtility;
26
use TYPO3\CMS\Extbase\Reflection\ObjectAccess;
27

28
abstract class AbstractFormComponent implements FormInterface
29
{
30
    const NAMESPACE_FIELD = 'FluidTYPO3\\Flux\\Form\\Field';
31
    const NAMESPACE_CONTAINER = 'FluidTYPO3\\Flux\\Form\\Container';
32

33
    protected ?string $name = null;
34
    protected bool $enabled = true;
35
    protected ?string $label = null;
36
    protected ?string $description = null;
37
    protected ?string $extensionName = 'FluidTYPO3.Flux';
38
    protected ?FormInterface $parent = null;
39
    protected array $variables = [];
40
    protected bool $inherit = false;
41
    protected bool $inheritEmpty = false;
42
    protected ?string $transform = null;
43

44
    /**
45
     * If TRUE, disables LLL label usage and always returns the
46
     * raw value of $label.
47
     */
48
    protected bool $disableLocalLanguageLabels = false;
49

50
    /**
51
     * Relative (from extension $extensionName) path to locallang
52
     * file containing labels for the LLL values built by this class.
53
     */
54
    protected string $localLanguageFileRelativePath = Form::DEFAULT_LANGUAGEFILE;
55

56
    public static function create(array $settings = []): FormInterface
57
    {
58
        $className = get_called_class();
544✔
59
        /** @var FormInterface $object */
60
        $object = GeneralUtility::makeInstance($className);
544✔
61
        $object->modify($settings);
544✔
62
        return HookHandler::trigger(HookHandler::FORM_COMPONENT_CREATED, ['component' => $object])['component'];
544✔
63
    }
64

65
    /**
66
     * @param string|class-string $type
67
     * @return class-string
68
     */
69
    protected function createComponentClassName(string $type, ?string $prefix): string
70
    {
71
        /** @var class-string $className */
72
        $className = str_replace('/', '\\', $type);
1,092✔
73
        $className = class_exists($prefix . '\\' . $className) ? $prefix . '\\' . $className : $className;
1,092✔
74
        /** @var class-string $className */
75
        $className = trim($className, '\\');
1,092✔
76
        return $className;
1,092✔
77
    }
78

79
    /**
80
     * @template T
81
     * @param class-string<T> $type
82
     * @return T&FieldInterface
83
     */
84
    public function createField(string $type, string $name, ?string $label = null): FieldInterface
85
    {
86
        /** @var T&FieldInterface $component */
87
        $component = $this->createComponent(static::NAMESPACE_FIELD, $type, $name, $label);
728✔
88
        return $component;
728✔
89
    }
90

91
    /**
92
     * @template T
93
     * @param class-string<T> $type
94
     * @return T&ContainerInterface
95
     */
96
    public function createContainer(string $type, string $name, ?string $label = null): ContainerInterface
97
    {
98
        /** @var T&ContainerInterface $component */
99
        $component = $this->createComponent(static::NAMESPACE_CONTAINER, $type, $name, $label);
376✔
100
        return $component;
376✔
101
    }
102

103
    public function createComponent(
104
        ?string $namespace,
105
        string $type,
106
        string $name,
107
        ?string $label = null
108
    ): FormInterface {
109
        /** @var FormInterface $component */
110
        $component = GeneralUtility::makeInstance($this->createComponentClassName($type, $namespace));
1,092✔
111
        $component->setName($name);
1,092✔
112
        $component->setLabel($label);
1,092✔
113
        $component->setLocalLanguageFileRelativePath($this->getLocalLanguageFileRelativePath());
1,092✔
114
        $component->setDisableLocalLanguageLabels($this->getDisableLocalLanguageLabels());
1,092✔
115
        $component->setExtensionName($this->getExtensionName());
1,092✔
116
        return HookHandler::trigger(HookHandler::FORM_COMPONENT_CREATED, ['component' => $component])['component'];
1,092✔
117
    }
118

119
    public function setTransform(?string $transform): self
120
    {
121
        $this->transform = $transform;
620✔
122
        if ($transform) {
620✔
123
            $root = $this->getRoot();
350✔
124
            if ($root instanceof Form) {
350✔
125
                $root->setOption(FormOption::TRANSFORM, true);
164✔
126
            }
127
        }
128
        return $this;
620✔
129
    }
130

131
    public function getTransform(): ?string
132
    {
133
        return $this->transform;
1,764✔
134
    }
135

136
    public function setName(string $name): self
137
    {
138
        $this->name = $name;
3,464✔
139
        return $this;
3,464✔
140
    }
141

142
    public function getName(): ?string
143
    {
144
        return $this->name;
2,598✔
145
    }
146

147
    public function getEnabled(): bool
148
    {
149
        return $this->enabled;
652✔
150
    }
151

152
    public function setEnabled(bool $enabled): self
153
    {
154
        $this->enabled = (boolean) $enabled;
956✔
155
        return $this;
956✔
156
    }
157

158
    public function setExtensionName(?string $extensionName): self
159
    {
160
        $this->extensionName = $extensionName;
1,932✔
161
        return $this;
1,932✔
162
    }
163

164
    public function getExtensionName(): ?string
165
    {
166
        return $this->extensionName;
2,614✔
167
    }
168

169
    public function setDescription(?string $description): self
170
    {
171
        $this->description = $description;
148✔
172
        return $this;
148✔
173
    }
174

175
    public function getDescription(): ?string
176
    {
177
        return $this->description;
6✔
178
    }
179

180
    public function setLabel(?string $label): self
181
    {
182
        $this->label = $label;
3,422✔
183
        return $this;
3,422✔
184
    }
185

186
    public function getPath(): string
187
    {
188
        $prefix = '';
730✔
189
        if ($this instanceof Sheet) {
730✔
190
            $prefix = 'sheets';
72✔
191
        } elseif ($this instanceof Section) {
688✔
192
            $prefix = 'sections';
18✔
193
        } elseif ($this instanceof Grid) {
676✔
194
            $prefix = 'grids';
54✔
195
        } elseif ($this instanceof Column) {
652✔
196
            $prefix = 'columns';
88✔
197
        } elseif ($this instanceof SectionObject) {
594✔
198
            $prefix = 'objects';
30✔
199
        } elseif ($this instanceof Container) {
576✔
200
            $prefix = 'containers';
12✔
201
        } elseif ($this instanceof FieldInterface) {
564✔
202
            if ($this->isChildOfType('SectionObject')) {
528✔
203
                /** @var SectionObject $parent */
204
                $parent = $this->getParent();
120✔
205
                $prefix = 'objects.' . $parent->getName();
120✔
206
            } else {
207
                $prefix = 'fields';
414✔
208
            }
209
        }
210
        return trim($prefix . '.' . $this->getName(), '.');
730✔
211
    }
212

213
    public function getLabel(): ?string
214
    {
215
        return $this->resolveLocalLanguageValueOfLabel($this->label);
1,610✔
216
    }
217

218
    protected function resolveLocalLanguageValueOfLabel(?string $label, ?string $path = null): ?string
219
    {
220
        if ($this->getDisableLocalLanguageLabels()) {
1,628✔
221
            return $label;
156✔
222
        }
223

224
        $name = $this->getName();
1,472✔
225
        $extensionName = (string) $this->getExtensionName();
1,472✔
226

227
        if (empty($extensionName) && empty($label)) {
1,472✔
228
            return $name;
270✔
229
        }
230

231
        $extensionKey = ExtensionNamingUtility::getExtensionKey($extensionName);
1,202✔
232

233
        if (strpos($label ?? '', 'LLL:EXT:') === 0) {
1,202✔
234
            return $label;
36✔
235
        }
236

237
        $relativeFilePath = $this->getLocalLanguageFileRelativePath();
1,202✔
238
        $relativeFilePath = ltrim($relativeFilePath, '/');
1,202✔
239
        $filePrefix = 'LLL:EXT:' . $extensionKey . '/' . $relativeFilePath;
1,202✔
240
        if (strpos($label ?? '', 'LLL:') === 0 && strpos($label ?? '', ':') !== false) {
1,202✔
241
            // Shorthand LLL:name.of.index reference, expand
242
            [, $labelIdentifier] = explode(':', $label, 2);
150✔
243
            return $filePrefix . ':' . $labelIdentifier;
150✔
244
        } elseif (!empty($label)) {
1,052✔
245
            return $label;
334✔
246
        }
247
        if ($this instanceof Form) {
742✔
248
            return $filePrefix . ':flux.' . $this->getName();
24✔
249
        }
250
        $root = $this->getRoot();
718✔
251
        $id = $root->getName();
718✔
252
        if (empty($path)) {
718✔
253
            $path = $this->getPath();
694✔
254
        }
255
        return $filePrefix . ':' . trim('flux.' . $id . '.' . $path, '.');
718✔
256
    }
257

258
    public function setLocalLanguageFileRelativePath(string $localLanguageFileRelativePath): self
259
    {
260
        $this->localLanguageFileRelativePath = $localLanguageFileRelativePath;
1,152✔
261
        return $this;
1,152✔
262
    }
263

264
    public function getLocalLanguageFileRelativePath(): string
265
    {
266
        return $this->localLanguageFileRelativePath;
2,206✔
267
    }
268

269
    public function setDisableLocalLanguageLabels(bool $disableLocalLanguageLabels): self
270
    {
271
        $this->disableLocalLanguageLabels = (boolean) $disableLocalLanguageLabels;
1,326✔
272
        return $this;
1,326✔
273
    }
274

275
    public function getDisableLocalLanguageLabels(): bool
276
    {
277
        return $this->disableLocalLanguageLabels;
2,632✔
278
    }
279

280
    public function setParent(?FormInterface $parent): self
281
    {
282
        $this->parent = $parent;
1,940✔
283
        return $this;
1,940✔
284
    }
285

286
    public function getParent(): ?FormInterface
287
    {
288
        return $this->parent;
1,122✔
289
    }
290

291
    public function setVariables(array $variables): self
292
    {
293
        $this->variables = (array) $variables;
666✔
294
        return $this;
666✔
295
    }
296

297
    public function getVariables(): array
298
    {
299
        return $this->variables;
156✔
300
    }
301

302
    /**
303
     * @param mixed $value
304
     */
305
    public function setVariable(string $name, $value): self
306
    {
307
        $this->variables = RecursiveArrayUtility::mergeRecursiveOverrule(
342✔
308
            $this->variables,
342✔
309
            RecursiveArrayUtility::convertPathToArray($name, $value)
342✔
310
        );
342✔
311
        return $this;
342✔
312
    }
313

314
    /**
315
     * @return mixed
316
     */
317
    public function getVariable(string $name)
318
    {
319
        return ObjectAccess::getPropertyPath($this->variables, $name);
376✔
320
    }
321

322
    /**
323
     * @return ContainerInterface|FormInterface|$this
324
     */
325
    public function getRoot(): FormInterface
326
    {
327
        $parent = $this->getParent();
1,074✔
328
        if (null === $parent || $this === $parent) {
1,074✔
329
            return $this;
1,074✔
330
        }
331
        return $parent->getRoot();
570✔
332
    }
333

334
    public function isChildOfType(string $type): bool
335
    {
336
        $parent = $this->getParent();
582✔
337
        if ($parent === null) {
582✔
338
            return false;
264✔
339
        }
340
        return (static::NAMESPACE_CONTAINER . '\\' . $type === get_class($parent) || is_a($parent, $type));
318✔
341
    }
342

343
    public function setInherit(bool $inherit): self
344
    {
345
        $this->inherit = $inherit;
456✔
346
        return $this;
456✔
347
    }
348

349
    public function getInherit(): bool
350
    {
351
        return $this->inherit;
192✔
352
    }
353

354
    public function setInheritEmpty(bool $inheritEmpty): self
355
    {
356
        $this->inheritEmpty = $inheritEmpty;
450✔
357
        return $this;
450✔
358
    }
359

360
    public function getInheritEmpty(): bool
361
    {
362
        return $this->inheritEmpty;
180✔
363
    }
364

365
    /**
366
     * Modifies the current Form Component by changing any properties
367
     * that were passed in $structure. If a component supports special
368
     * indices in $structure (for example a "fields" property) then
369
     * that component may specify its own `modify()` method and manually
370
     * process each of the specially supported keywords.
371
     *
372
     * For example, the AbstractFormContainer supports passing "fields"
373
     * and each field is then attempted fetched from children. If not
374
     * found, it is created (and the structure passed to the `create()`
375
     * function which uses the same structure syntax). If it already
376
     * exists, the `modify()` method is called on that object to trigger
377
     * the recursive modification of all child components.
378
     */
379
    public function modify(array $structure): self
380
    {
381
        if (isset($structure['options']) && is_array($structure['options'])) {
1,824✔
382
            foreach ($structure['options'] as $name => $value) {
156✔
383
                $this->setVariable($name, $value);
156✔
384
            }
385
            unset($structure['options']);
156✔
386
        }
387
        foreach ($structure as $propertyName => $propertyValue) {
1,824✔
388
            $setterMethodName = 'set' . ucfirst($propertyName);
1,038✔
389
            if (method_exists($this, $setterMethodName)) {
1,038✔
390
                /** @var \ReflectionParameter|null $parameterReflection */
391
                $parameterReflection = (new \ReflectionMethod($this, $setterMethodName))->getParameters()[0] ?? null;
1,008✔
392
                if ($parameterReflection === null) {
1,008✔
393
                    continue;
×
394
                }
395
                /** @var \ReflectionNamedType $typeReflection */
396
                $typeReflection = $parameterReflection->getType();
1,008✔
397
                if ($typeReflection) {
1,008✔
398
                    switch ($typeReflection->getName()) {
1,008✔
399
                        case 'bool':
1,008✔
400
                            $propertyValue = (bool) $propertyValue;
156✔
401
                            break;
156✔
402
                        case 'int':
852✔
403
                            $propertyValue = (integer) $propertyValue;
12✔
404
                            break;
12✔
405
                        case 'array':
852✔
406
                            $propertyValue = is_array($propertyValue)
×
407
                                ? $propertyValue
×
408
                                : GeneralUtility::trimExplode(',', $propertyValue, true);
×
409
                            break;
×
410
                    }
411
                }
412

413
                $this->{$setterMethodName}($propertyValue);
1,008✔
414
            }
415
        }
416
        HookHandler::trigger(
1,824✔
417
            HookHandler::FORM_COMPONENT_MODIFIED,
1,824✔
418
            ['component' => $this, 'modififications' => $structure]
1,824✔
419
        );
1,824✔
420
        return $this;
1,824✔
421
    }
422

423
    /**
424
     * @param \SplObjectStorage|FormInterface[]|array $children
425
     */
426
    protected function buildChildren(iterable $children): array
427
    {
428
        /** @var OptionCarryingInterface $root */
429
        $root = $this->getRoot();
114✔
430

431
        $structure = [];
114✔
432
        foreach ($children as $child) {
114✔
433
            if (!$child->getEnabled() || $child instanceof FieldInterface && $child->isNative()) {
66✔
434
                continue;
×
435
            }
436
            $name = $child->getName();
66✔
437
            $configuration = $child->build();
66✔
438
            $config = $configuration['config'] ?? [];
66✔
439
            if (ExtensionConfigurationUtility::getOption(ExtensionOption::OPTION_UNIQUE_FILE_FIELD_NAMES)) {
66✔
440
                // Unique file names are enabled. IF the field is type=file or type=inline with FAL, prefix the name.
441
                $type = $config['type'] ?? null;
×
442
                if ($type === 'file' || ($type === 'inline' && $config['foreign_table'] === 'sys_file_reference')) {
×
443
                    $name = $root->getOption(FormOption::RECORD_FIELD) . '.' . $name;
×
444
                }
445
            }
446
            $structure[$name] = $configuration;
66✔
447
        }
448
        return $structure;
114✔
449
    }
450
}
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