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

CPS-IT / handlebars-forms / 22296296773

23 Feb 2026 07:10AM UTC coverage: 0.0%. First build
22296296773

push

github

eliashaeussler
Initial commit

0 of 447 new or added lines in 21 files covered. (0.0%)

0 of 447 relevant lines covered (0.0%)

0.0 hits per line

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

0.0
/Classes/DataProcessing/ProcessFormProcessor.php
1
<?php
2

3
declare(strict_types=1);
4

5
/*
6
 * This file is part of the TYPO3 CMS extension "handlebars_forms".
7
 *
8
 * It is free software; you can redistribute it and/or modify it under
9
 * the terms of the GNU General Public License, either version 2
10
 * of the License, or any later version.
11
 *
12
 * For the full copyright and license information, please read the
13
 * LICENSE.txt file that was distributed with this source code.
14
 *
15
 * The TYPO3 project - inspiring people to share!
16
 */
17

18
namespace CPSIT\Typo3HandlebarsForms\DataProcessing;
19

20
use Psr\Http\Message;
21
use Psr\Log;
22
use Symfony\Component\DependencyInjection;
23
use TYPO3\CMS\Fluid;
24
use TYPO3\CMS\Form;
25
use TYPO3\CMS\Frontend;
26
use TYPO3Fluid\Fluid as FluidStandalone;
27

28
/**
29
 * ProcessFormProcessor
30
 *
31
 * @author Elias Häußler <e.haeussler@familie-redlich.de>
32
 * @license GPL-2.0-or-later
33
 */
34
#[DependencyInjection\Attribute\AutoconfigureTag('data.processor', ['identifier' => 'process-form'])]
35
final readonly class ProcessFormProcessor implements Frontend\ContentObject\DataProcessorInterface
36
{
37
    private const CONTENT_PLACEHOLDER = '###FORM_CONTENT###';
38

39
    /**
40
     * @param iterable<Renderable\RenderableProcessor> $renderableProcessors
41
     * @param DependencyInjection\ServiceLocator<Value\ValueProcessor> $valueProcessors
42
     */
NEW
43
    public function __construct(
×
44
        private Log\LoggerInterface $logger,
45
        private Fluid\Core\Rendering\RenderingContextFactory $renderingContextFactory,
46
        private Renderable\FormRenderableProcessor $formRenderableProcessor,
47
        #[DependencyInjection\Attribute\AutowireIterator('handlebars_forms.renderable_processor', exclude: Renderable\FormRenderableProcessor::class)]
48
        private iterable $renderableProcessors,
49
        #[DependencyInjection\Attribute\AutowireLocator('handlebars_forms.value_processor', defaultIndexMethod: 'getName')]
50
        private DependencyInjection\ServiceLocator $valueProcessors,
NEW
51
    ) {}
×
52

53
    /**
54
     * @param array<string, mixed> $contentObjectConfiguration
55
     * @param array<string, mixed> $processorConfiguration
56
     * @param array<string, mixed> $processedData
57
     * @return array<string, mixed>
58
     */
NEW
59
    public function process(
×
60
        Frontend\ContentObject\ContentObjectRenderer $cObj,
61
        array $contentObjectConfiguration,
62
        array $processorConfiguration,
63
        array $processedData,
64
    ): array {
NEW
65
        $formRuntime = $contentObjectConfiguration['variables.']['form'] ?? null;
×
66

NEW
67
        if (!($formRuntime instanceof Form\Domain\Runtime\FormRuntime)) {
×
NEW
68
            $this->logger->error(
×
NEW
69
                'Form runtime is not available when trying to process form with plugin uid "{uid}".',
×
NEW
70
                ['uid' => $cObj->data['uid'] ?? '(unknown)'],
×
NEW
71
            );
×
72

NEW
73
            return $processedData;
×
74
        }
75

76
        // Create and prepare Fluid rendering context
NEW
77
        $renderingContext = $this->renderingContextFactory->create();
×
NEW
78
        $renderingContext->setAttribute(Message\ServerRequestInterface::class, $formRuntime->getRequest());
×
NEW
79
        $renderingContext->getViewHelperVariableContainer()->addOrUpdate(
×
NEW
80
            Form\ViewHelpers\RenderRenderableViewHelper::class,
×
NEW
81
            'formRuntime',
×
NEW
82
            $formRuntime,
×
NEW
83
        );
×
84

NEW
85
        $this->formRenderableProcessor->process(
×
NEW
86
            $formRuntime,
×
NEW
87
            $renderingContext,
×
NEW
88
            function (FluidStandalone\Core\ViewHelper\TagBuilder $tagBuilder) use (
×
NEW
89
                $cObj,
×
NEW
90
                $formRuntime,
×
NEW
91
                &$processedData,
×
NEW
92
                $processorConfiguration,
×
NEW
93
                $renderingContext,
×
NEW
94
                &$tag,
×
NEW
95
            ) {
×
NEW
96
                $tag = $tagBuilder;
×
NEW
97
                $tag->setContent(self::CONTENT_PLACEHOLDER);
×
98

NEW
99
                $processedRenderable = new Renderable\ProcessedRenderable($renderingContext, null, $tag);
×
NEW
100
                $processedData = $this->processRenderable($processorConfiguration, $formRuntime, $cObj, $processedRenderable) ?? [];
×
101

NEW
102
                return '';
×
NEW
103
            },
×
NEW
104
        );
×
105

NEW
106
        $inputFields = $tag?->getContent() ?? '';
×
107

NEW
108
        array_walk_recursive($processedData, static function (&$value) use ($inputFields) {
×
NEW
109
            if (is_string($value)) {
×
NEW
110
                $value = str_replace(self::CONTENT_PLACEHOLDER, $inputFields, $value);
×
111
            }
NEW
112
        });
×
113

NEW
114
        return $processedData;
×
115
    }
116

117
    /**
118
     * @param array<string, mixed> $configuration
119
     * @return array<string, mixed>|null
120
     */
NEW
121
    private function processRenderable(
×
122
        array $configuration,
123
        Form\Domain\Model\Renderable\RootRenderableInterface $renderable,
124
        Frontend\ContentObject\ContentObjectRenderer $cObj,
125
        Renderable\ProcessedRenderable $processedRenderable,
126
    ): ?array {
NEW
127
        $processedData = [];
×
128

129
        // Early return on configured "if" condition evaluating to false
NEW
130
        if (!$this->checkIf($configuration, $renderable, $cObj, $processedRenderable)) {
×
NEW
131
            return null;
×
132
        }
133

NEW
134
        foreach ($configuration as $key => $value) {
×
NEW
135
            $keyWithoutDot = rtrim($key, '.');
×
NEW
136
            $keyWithDot = $keyWithoutDot . '.';
×
137

NEW
138
            if (is_array($value) && !array_key_exists($keyWithoutDot, $processedData)) {
×
NEW
139
                $processedValue = $this->processRenderable($value, $renderable, $cObj, $processedRenderable);
×
140

NEW
141
                if (is_array($processedValue)) {
×
NEW
142
                    $processedData[$keyWithoutDot] = $processedValue;
×
143
                }
144
            }
145

NEW
146
            if (!is_string($value)) {
×
NEW
147
                continue;
×
148
            }
149

NEW
150
            $valueConfiguration = $configuration[$keyWithDot] ?? [];
×
151

152
            // Process configured value
NEW
153
            if ($this->valueProcessors->has($value)) {
×
NEW
154
                $processedValue = $this->valueProcessors->get($value)->process(
×
NEW
155
                    $renderable,
×
NEW
156
                    $processedRenderable,
×
NEW
157
                    $valueConfiguration,
×
NEW
158
                );
×
NEW
159
            } elseif ($value === 'RENDERABLES') {
×
NEW
160
                $processedValue = $this->processRenderables(
×
NEW
161
                    $valueConfiguration,
×
NEW
162
                    $cObj,
×
NEW
163
                    $renderable,
×
NEW
164
                    $processedRenderable->renderingContext,
×
NEW
165
                );
×
166
            } else {
NEW
167
                $processedValue = $value;
×
168
            }
169

170
            // Post-process navigation
NEW
171
            if (is_array($processedValue) && Value\NavigationValueProcessor::getName() === $value) {
×
NEW
172
                $processedValue = $this->processNavigation($processedValue, $cObj, $processedRenderable);
×
173
            }
174

175
            // Skip further processing if processed value is not a string (all COR related methods require a string value)
NEW
176
            if (!is_string($processedValue)) {
×
NEW
177
                $processedData[$keyWithoutDot] = $processedValue;
×
NEW
178
                continue;
×
179
            }
180

181
            // Process value with stdWrap
NEW
182
            if (is_array($valueConfiguration['stdWrap.'] ?? null)) {
×
NEW
183
                $processedValue = $cObj->stdWrap($processedValue, $valueConfiguration['stdWrap.']);
×
184
            }
185

186
            // Skip value if a configured "if" evaluates to false
NEW
187
            if (is_array($valueConfiguration['if.'] ?? null)) {
×
NEW
188
                $valueConfiguration['if.']['value'] ??= $processedValue;
×
189

NEW
190
                if (!$cObj->checkIf($valueConfiguration['if.'])) {
×
NEW
191
                    continue;
×
192
                }
193
            }
194

NEW
195
            $processedData[$keyWithoutDot] = $processedValue;
×
196
        }
197

NEW
198
        return $processedData;
×
199
    }
200

201
    /**
202
     * @param array<string, array<string, mixed>> $configuration
203
     * @return list<array<string, mixed>>
204
     */
NEW
205
    private function processRenderables(
×
206
        array $configuration,
207
        Frontend\ContentObject\ContentObjectRenderer $cObj,
208
        Form\Domain\Model\Renderable\RootRenderableInterface $rootRenderable,
209
        Fluid\Core\Rendering\RenderingContext $renderingContext,
210
    ): array {
NEW
211
        $processedElements = [];
×
212

NEW
213
        if ($rootRenderable instanceof Form\Domain\Runtime\FormRuntime) {
×
NEW
214
            $renderables = $rootRenderable->getCurrentPage()?->getRenderablesRecursively() ?? [];
×
NEW
215
        } elseif ($rootRenderable instanceof Form\Domain\Model\Renderable\CompositeRenderableInterface) {
×
NEW
216
            $renderables = $rootRenderable->getRenderablesRecursively();
×
217
        } else {
218
            // We cannot process non-composite renderables here
NEW
219
            return [];
×
220
        }
221

NEW
222
        foreach ($renderables as $renderable) {
×
NEW
223
            $typeConfiguration = $configuration[$renderable->getType() . '.'] ?? null;
×
224

NEW
225
            if (is_array($typeConfiguration)) {
×
NEW
226
                $processedRenderable = $this->buildFormProperties($renderable, $renderingContext);
×
227
            } else {
NEW
228
                $typeConfiguration = [];
×
NEW
229
                $processedRenderable = new Renderable\ProcessedRenderable($renderingContext, null);
×
230
            }
231

NEW
232
            $processedElement = $this->processRenderable(
×
NEW
233
                $typeConfiguration,
×
NEW
234
                $renderable,
×
NEW
235
                $cObj,
×
NEW
236
                $processedRenderable,
×
NEW
237
            );
×
238

NEW
239
            if ($processedElement !== null) {
×
NEW
240
                $processedElements[] = $processedElement;
×
241
            }
242
        }
243

NEW
244
        return $processedElements;
×
245
    }
246

247
    /**
248
     * @param list<Value\NavigationElement> $navigationElements
249
     * @return list<array<string, mixed>>
250
     */
NEW
251
    private function processNavigation(
×
252
        array $navigationElements,
253
        Frontend\ContentObject\ContentObjectRenderer $cObj,
254
        Renderable\ProcessedRenderable $processedRenderable,
255
    ): array {
NEW
256
        $processedElements = [];
×
257

NEW
258
        foreach ($navigationElements as $navigationElement) {
×
NEW
259
            $processedElement = $this->processRenderable(
×
NEW
260
                $navigationElement->configuration,
×
NEW
261
                $navigationElement->renderable,
×
NEW
262
                $cObj,
×
NEW
263
                $processedRenderable,
×
NEW
264
            );
×
265

NEW
266
            if ($processedElement !== null) {
×
NEW
267
                $processedElements[] = $processedElement;
×
268
            }
269
        }
270

NEW
271
        return $processedElements;
×
272
    }
273

274
    /**
275
     * @param array<string, mixed> $configuration
276
     */
NEW
277
    private function checkIf(
×
278
        array &$configuration,
279
        Form\Domain\Model\Renderable\RootRenderableInterface $renderable,
280
        Frontend\ContentObject\ContentObjectRenderer $cObj,
281
        Renderable\ProcessedRenderable $processedRenderable,
282
    ): bool {
NEW
283
        if (!is_array($configuration['if.'] ?? null)) {
×
NEW
284
            return true;
×
285
        }
286

NEW
287
        $cObjTemp = clone $cObj;
×
288

NEW
289
        if (is_string($configuration['if.']['value'] ?? null) && is_array($configuration['if.']['value.'] ?? null)) {
×
NEW
290
            $processedValue = $this->processRenderable(
×
NEW
291
                [
×
NEW
292
                    'value' => $configuration['if.']['value'],
×
NEW
293
                    'value.' => $configuration['if.']['value.'],
×
NEW
294
                ],
×
NEW
295
                $renderable,
×
NEW
296
                $cObj,
×
NEW
297
                $processedRenderable,
×
NEW
298
            );
×
299

NEW
300
            $cObjTemp->setCurrentVal($processedValue['value'] ?? null);
×
301

NEW
302
            unset($configuration['if.']['value'], $configuration['if.']['value.']);
×
303
        }
304

NEW
305
        if (!$cObjTemp->checkIf($configuration['if.'])) {
×
NEW
306
            return false;
×
307
        }
308

NEW
309
        unset($configuration['if.']);
×
310

NEW
311
        return true;
×
312
    }
313

NEW
314
    private function buildFormProperties(
×
315
        Form\Domain\Model\Renderable\RootRenderableInterface $renderable,
316
        Fluid\Core\Rendering\RenderingContext $renderingContext,
317
    ): Renderable\ProcessedRenderable {
NEW
318
        foreach ($this->renderableProcessors as $renderableProcessor) {
×
NEW
319
            if ($renderableProcessor->supports($renderable)) {
×
NEW
320
                return $renderableProcessor->process($renderable, $renderingContext);
×
321
            }
322
        }
323

NEW
324
        return new Renderable\ProcessedRenderable($renderingContext, null);
×
325
    }
326
}
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