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

CPS-IT / handlebars-forms / 22991047448

12 Mar 2026 07:23AM UTC coverage: 0.0%. Remained the same
22991047448

push

github

eliashaeussler
[FEATURE] Resolve and apply grid column classes for each renderable

0 of 22 new or added lines in 1 file covered. (0.0%)

59 existing lines in 2 files now uncovered.

0 of 680 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 CPSIT\Typo3HandlebarsForms\Domain;
21
use DevTheorem\Handlebars;
22
use Psr\Http\Message;
23
use Psr\Log;
24
use Symfony\Component\DependencyInjection;
25
use TYPO3\CMS\Fluid;
26
use TYPO3\CMS\Form;
27
use TYPO3\CMS\Frontend;
28
use TYPO3Fluid\Fluid as FluidStandalone;
29

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

41
    /**
42
     * @param DependencyInjection\ServiceLocator<Value\ValueResolver> $valueResolvers
43
     */
44
    public function __construct(
×
45
        private Log\LoggerInterface $logger,
46
        private Fluid\Core\Rendering\RenderingContextFactory $renderingContextFactory,
47
        private Domain\Renderable\ViewModel\FormViewModelBuilder $formRenderableProcessor,
48
        #[DependencyInjection\Attribute\AutowireLocator('handlebars_forms.value_resolver', defaultIndexMethod: 'getName')]
49
        private DependencyInjection\ServiceLocator $valueResolvers,
50
    ) {}
×
51

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

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

72
            return $processedData;
×
73
        }
74

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

84
        // Render form and process renderables as part of the form's renderChildrenClosure.
85
        // Since the final rendered form content (which especially contains all relevant hidden fields)
86
        // is not yet available when processing renderables, we temporarily pass a content placeholder
87
        // for all configured CONTENT values and replace them with the real content value later.
88
        $this->formRenderableProcessor->build(
×
89
            $formRuntime,
×
90
            $renderingContext,
×
91
            function (FluidStandalone\Core\ViewHelper\TagBuilder $tagBuilder) use (
×
92
                $cObj,
×
93
                $formRuntime,
×
94
                &$processedData,
×
95
                $processorConfiguration,
×
96
                $renderingContext,
×
97
                &$tag,
×
98
            ) {
×
99
                $tag = $tagBuilder;
×
100
                $tag->setContent(self::CONTENT_PLACEHOLDER);
×
101

102
                $viewModel = new Domain\Renderable\ViewModel\ViewModel($renderingContext, null, $tag);
×
103
                $processedData = $this->processRenderable($formRuntime, $processorConfiguration, $cObj, $viewModel) ?? [];
×
104

105
                return '';
×
106
            },
×
107
        );
×
108

109
        $formContent = $tag?->getContent();
×
110

111
        // Replace content placeholder with final rendered form content
112
        if ($formContent !== null) {
×
113
            array_walk_recursive($processedData, static function (&$value) use ($formContent) {
×
114
                $isSafeString = false;
×
115

116
                if ($value instanceof Handlebars\SafeString) {
×
117
                    $isSafeString = true;
×
118
                    $value = (string)$value;
×
119
                }
120

121
                if (is_string($value)) {
×
122
                    $value = str_replace(self::CONTENT_PLACEHOLDER, $formContent, $value);
×
123
                }
124

125
                if ($isSafeString) {
×
126
                    $value = new Handlebars\SafeString($value);
×
127
                }
128
            });
×
129
        }
130

131
        return $processedData;
×
132
    }
133

134
    /**
135
     * @param array<string, mixed> $configuration
136
     * @return array<string, mixed>|null
137
     */
138
    private function processRenderable(
×
139
        Form\Domain\Model\Renderable\RootRenderableInterface $renderable,
140
        array $configuration,
141
        Frontend\ContentObject\ContentObjectRenderer $cObj,
142
        Domain\Renderable\ViewModel\ViewModel $viewModel,
143
    ): ?array {
144
        $processedData = [];
×
145

146
        // Early return on configured "if" condition evaluating to false
147
        if (!$this->checkIf($configuration, $renderable, $cObj, $viewModel)) {
×
148
            return null;
×
149
        }
150

151
        // Merge TS reference (=<) and replace configuration with merged configuration
152
        $this->mergeTypoScriptReferences($configuration, $cObj);
×
153

UNCOV
154
        foreach ($configuration as $key => $value) {
×
UNCOV
155
            $keyWithoutDot = rtrim($key, '.');
×
156
            $keyWithDot = $keyWithoutDot . '.';
×
157

158
            if (is_array($value) && !array_key_exists($keyWithoutDot, $processedData)) {
×
159
                $resolvedValue = $this->processRenderable($renderable, $value, $cObj, $viewModel);
×
160

161
                if (is_array($resolvedValue)) {
×
162
                    $processedData[$keyWithoutDot] = $resolvedValue;
×
163
                }
164
            }
165

166
            if (!is_string($value)) {
×
UNCOV
167
                continue;
×
168
            }
169

170
            $valueConfiguration = $configuration[$keyWithDot] ?? [];
×
171

172
            // Resolve configured value
173
            if ($this->valueResolvers->has($value)) {
×
UNCOV
174
                $resolvedValue = $this->valueResolvers->get($value)->resolve(
×
UNCOV
175
                    $renderable,
×
UNCOV
176
                    $viewModel,
×
177
                    new Value\ValueResolutionContext(
×
178
                        $valueConfiguration,
×
UNCOV
179
                        fn(
×
UNCOV
180
                            array $contextConfiguration,
×
181
                            ?Form\Domain\Model\Renderable\RootRenderableInterface $contextRenderable = null,
×
UNCOV
182
                            ?Domain\Renderable\ViewModel\ViewModel $contextViewModel = null,
×
UNCOV
183
                        ) => $this->processRenderable(
×
184
                            $contextRenderable ?? $renderable,
×
185
                            $contextConfiguration,
×
186
                            $cObj,
×
187
                            $contextViewModel ?? $viewModel,
×
188
                        ),
×
189
                    ),
×
190
                );
×
191
            } else {
192
                $resolvedValue = $value;
×
193
            }
194

195
            // Skip further processing if processed value is not a string (all COR related methods require a string value)
196
            if (!is_string($resolvedValue)) {
×
197
                $processedData[$keyWithoutDot] = $resolvedValue;
×
198
                continue;
×
199
            }
200

201
            // Process value with stdWrap
UNCOV
202
            if (is_array($valueConfiguration['stdWrap.'] ?? null)) {
×
203
                $resolvedValue = $cObj->stdWrap($resolvedValue, $valueConfiguration['stdWrap.']);
×
204
            }
205

206
            // Skip value if a configured "if" evaluates to false
207
            if (is_array($valueConfiguration['if.'] ?? null)) {
×
208
                $valueConfiguration['if.']['value'] ??= $resolvedValue;
×
209

UNCOV
210
                if (!$this->checkIf($valueConfiguration, $renderable, $cObj, $viewModel)) {
×
UNCOV
211
                    continue;
×
212
                }
213
            }
214

215
            // Strings can be considered safe, since the relevant escaping is already performed
216
            // in the view helpers and/or TagBuilder instances when adding attributes
UNCOV
217
            if (is_string($resolvedValue)) {
×
218
                $resolvedValue = new Handlebars\SafeString($resolvedValue);
×
219
            }
220

221
            $processedData[$keyWithoutDot] = $resolvedValue;
×
222
        }
223

UNCOV
224
        return $processedData;
×
225
    }
226

227
    /**
228
     * @param array<string, mixed> $configuration
229
     */
UNCOV
230
    private function mergeTypoScriptReferences(
×
231
        array &$configuration,
232
        Frontend\ContentObject\ContentObjectRenderer $cObj,
233
    ): void {
UNCOV
234
        $processedKeys = [];
×
235

UNCOV
236
        foreach ($configuration as $key => $value) {
×
UNCOV
237
            if (in_array($key, $processedKeys, true)) {
×
UNCOV
238
                continue;
×
239
            }
240

241
            $keyWithoutDot = rtrim($key, '.');
×
UNCOV
242
            $keyWithDot = $keyWithoutDot . '.';
×
243

UNCOV
244
            if (array_key_exists($keyWithDot, $configuration) && is_array($configuration[$keyWithDot])) {
×
UNCOV
245
                $this->mergeTypoScriptReferences($configuration[$keyWithDot], $cObj);
×
246
            }
247

248
            if (!array_key_exists($keyWithoutDot, $configuration)) {
×
UNCOV
249
                continue;
×
250
            }
251

UNCOV
252
            $mergedConfig = $cObj->mergeTSRef(
×
253
                [
×
254
                    $keyWithoutDot => $configuration[$keyWithoutDot] ?? '',
×
255
                    $keyWithDot => $configuration[$keyWithDot] ?? [],
×
256
                ],
×
257
                $keyWithoutDot,
×
258
            );
×
259

260
            $configuration[$keyWithoutDot] = $mergedConfig[$keyWithoutDot];
×
261

262
            if ($mergedConfig[$keyWithDot] !== []) {
×
UNCOV
263
                $configuration[$keyWithDot] = $mergedConfig[$keyWithDot];
×
264
            }
265

266
            $processedKeys[] = $keyWithoutDot;
×
UNCOV
267
            $processedKeys[] = $keyWithDot;
×
268
        }
269
    }
270

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

UNCOV
284
        $cObjTemp = clone $cObj;
×
285

UNCOV
286
        if (is_string($configuration['if.']['currentValue'] ?? null) && is_array($configuration['if.']['currentValue.'] ?? null)) {
×
UNCOV
287
            $processedValue = $this->processRenderable(
×
UNCOV
288
                $renderable,
×
UNCOV
289
                [
×
UNCOV
290
                    'currentValue' => $configuration['if.']['currentValue'],
×
UNCOV
291
                    'currentValue.' => $configuration['if.']['currentValue.'],
×
UNCOV
292
                ],
×
UNCOV
293
                $cObj,
×
UNCOV
294
                $viewModel,
×
UNCOV
295
            );
×
296

UNCOV
297
            $cObjTemp->setCurrentVal($processedValue['currentValue'] ?? null);
×
298

UNCOV
299
            unset($configuration['if.']['currentValue'], $configuration['if.']['currentValue.']);
×
300
        }
301

UNCOV
302
        if (!$cObjTemp->checkIf($configuration['if.'])) {
×
UNCOV
303
            return false;
×
304
        }
305

UNCOV
306
        unset($configuration['if.']);
×
307

UNCOV
308
        return true;
×
309
    }
310
}
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