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

CPS-IT / handlebars-forms / 22299447345

23 Feb 2026 09:07AM UTC coverage: 0.0%. Remained the same
22299447345

push

github

eliashaeussler
[BUGFIX] Pass correct ProcessedRenderable when processing navigation

0 of 1 new or added line in 1 file 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
     */
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,
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
     */
59
    public function process(
×
60
        Frontend\ContentObject\ContentObjectRenderer $cObj,
61
        array $contentObjectConfiguration,
62
        array $processorConfiguration,
63
        array $processedData,
64
    ): array {
65
        $formRuntime = $contentObjectConfiguration['variables.']['form'] ?? null;
×
66

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

73
            return $processedData;
×
74
        }
75

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

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

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

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

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

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

114
        return $processedData;
×
115
    }
116

117
    /**
118
     * @param array<string, mixed> $configuration
119
     * @return array<string, mixed>|null
120
     */
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 {
127
        $processedData = [];
×
128

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

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

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

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

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

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

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

170
            // Post-process navigation
171
            if (is_array($processedValue) && Value\NavigationValueProcessor::getName() === $value) {
×
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)
176
            if (!is_string($processedValue)) {
×
177
                $processedData[$keyWithoutDot] = $processedValue;
×
178
                continue;
×
179
            }
180

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

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

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

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

198
        return $processedData;
×
199
    }
200

201
    /**
202
     * @param array<string, array<string, mixed>> $configuration
203
     * @return list<array<string, mixed>>
204
     */
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 {
211
        $processedElements = [];
×
212

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

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

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

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

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

244
        return $processedElements;
×
245
    }
246

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

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

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

271
        return $processedElements;
×
272
    }
273

274
    /**
275
     * @param array<string, mixed> $configuration
276
     */
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 {
283
        if (!is_array($configuration['if.'] ?? null)) {
×
284
            return true;
×
285
        }
286

287
        $cObjTemp = clone $cObj;
×
288

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

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

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

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

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

311
        return true;
×
312
    }
313

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

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