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

CPS-IT / handlebars-forms / 23132838979

16 Mar 2026 07:36AM UTC coverage: 0.0%. Remained the same
23132838979

Pull #10

github

web-flow
Merge ad90b5020 into fdb00737e
Pull Request #10: [FEATURE] Convert value resolvers to context-aware content objects

0 of 88 new or added lines in 11 files covered. (0.0%)

2 existing lines in 1 file now uncovered.

0 of 696 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/ContentObject/ValidationResultsContentObject.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\ContentObject;
19

20
use CPSIT\Typo3HandlebarsForms\Domain;
21
use CPSIT\Typo3HandlebarsForms\Fluid;
22
use Psr\Http\Message;
23
use Symfony\Component\DependencyInjection;
24
use TYPO3\CMS\Extbase;
25
use TYPO3\CMS\Form;
26

27
/**
28
 * ValidationResultsContentObject
29
 *
30
 * @author Elias Häußler <e.haeussler@familie-redlich.de>
31
 * @license GPL-2.0-or-later
32
 */
33
#[DependencyInjection\Attribute\AutoconfigureTag('frontend.contentobject', ['identifier' => 'HBS_VALIDATION_RESULTS'])]
34
final class ValidationResultsContentObject extends AbstractHandlebarsFormsContentObject
35
{
36
    public function __construct(
×
37
        private readonly Fluid\ViewHelperInvoker $viewHelperInvoker,
38
    ) {}
×
39

NEW
40
    protected function resolve(array $configuration, Context\ValueResolutionContext $context): mixed
×
41
    {
NEW
42
        $outputInstruction = $configuration['output'] ?? null;
×
NEW
43
        $outputConfiguration = $configuration['output.'] ?? null;
×
NEW
44
        $renderable = $context->renderable;
×
45

46
        // Resolve form definition from renderable
47
        if ($renderable instanceof Form\Domain\Model\Renderable\AbstractRenderable) {
×
48
            $property = $renderable->getRootForm()->getIdentifier() . '.' . $renderable->getIdentifier();
×
49
        } else {
50
            $property = $renderable->getIdentifier();
×
51
        }
52

NEW
53
        $request = $context->viewModel->renderingContext->getAttribute(Message\ServerRequestInterface::class);
×
54
        $extbaseRequestParameters = $request->getAttribute('extbase');
×
55

56
        // Early return when resolver was requested outside of extbase context
57
        if (!($extbaseRequestParameters instanceof Extbase\Mvc\ExtbaseRequestParameters)) {
×
58
            return null;
×
59
        }
60

61
        $validationResults = $extbaseRequestParameters->getOriginalRequestMappingResults()->forProperty($property);
×
62

63
        // Normalize output configuration
64
        if (!is_array($outputConfiguration)) {
×
65
            $outputConfiguration = null;
×
66
        }
67

68
        // Resolve validation results by a single rendering instruction
69
        if (is_string($outputInstruction)) {
×
70
            return $this->processRenderingInstruction(
×
71
                $renderable,
×
NEW
72
                $context->viewModel,
×
73
                $validationResults,
×
74
                $outputInstruction,
×
75
                $outputConfiguration ?? [],
×
76
            );
×
77
        }
78

79
        // Resolve complex rendering configuration
80
        if (is_array($outputConfiguration)) {
×
81
            return $this->processRenderingConfiguration(
×
82
                $renderable,
×
NEW
83
                $context->viewModel,
×
84
                $validationResults,
×
85
                $outputConfiguration,
×
86
            );
×
87
        }
88

89
        // Return raw results in case no rendering instructions are specified
90
        return $validationResults;
×
91
    }
92

93
    /**
94
     * @param array<string, mixed> $configuration
95
     * @return array<string, mixed>
96
     */
97
    private function processRenderingConfiguration(
×
98
        Form\Domain\Model\Renderable\RootRenderableInterface $renderable,
99
        Domain\Renderable\ViewModel\ViewModel $viewModel,
100
        Extbase\Error\Result $result,
101
        array $configuration,
102
    ): array {
103
        $processedData = [];
×
104

105
        foreach ($configuration as $key => $value) {
×
106
            $keyWithoutDot = rtrim($key, '.');
×
107
            $keyWithDot = $keyWithoutDot . '.';
×
108

109
            if (is_array($value) && !array_key_exists($keyWithoutDot, $processedData)) {
×
110
                $processedData[$keyWithoutDot] = $this->processRenderingConfiguration(
×
111
                    $renderable,
×
112
                    $viewModel,
×
113
                    $result,
×
114
                    $value,
×
115
                );
×
116
            } elseif (is_string($value)) {
×
117
                $processedData[$keyWithoutDot] = $this->processRenderingInstruction(
×
118
                    $renderable,
×
119
                    $viewModel,
×
120
                    $result,
×
121
                    $value,
×
122
                    $configuration[$keyWithDot] ?? [],
×
123
                );
×
124
            }
125
        }
126

127
        return $processedData;
×
128
    }
129

130
    /**
131
     * @param array<string, mixed> $configuration
132
     */
133
    private function processRenderingInstruction(
×
134
        Form\Domain\Model\Renderable\RootRenderableInterface $renderable,
135
        Domain\Renderable\ViewModel\ViewModel $viewModel,
136
        Extbase\Error\Result $result,
137
        string $value,
138
        array $configuration,
139
    ): mixed {
140
        return match ($value) {
×
141
            'EACH_ERROR' => $this->processErrors($renderable, $viewModel, $result, $configuration),
×
142
            'ERROR_MESSAGE' => $this->processErrorMessage($renderable, $viewModel, $result),
×
143
            'PROPERTY' => $this->processProperty($result, $configuration),
×
144
            default => $value,
×
145
        };
×
146
    }
147

148
    /**
149
     * @param array<string, mixed> $configuration
150
     * @return ($renderable is Form\Domain\Model\Renderable\CompositeRenderableInterface|Form\Domain\Runtime\FormRuntime ? array<string, list<array<string, mixed>>> : list<array<string, mixed>>)
151
     */
152
    private function processErrors(
×
153
        Form\Domain\Model\Renderable\RootRenderableInterface $renderable,
154
        Domain\Renderable\ViewModel\ViewModel $viewModel,
155
        Extbase\Error\Result $result,
156
        array $configuration,
157
    ): array {
158
        $processedErrors = [];
×
159

160
        foreach ($result->getFlattenedErrors() as $propertyPath => $errors) {
×
161
            $currentRenderable = $renderable;
×
162

163
            if ($propertyPath !== '' && $renderable instanceof Form\Domain\Runtime\FormRuntime) {
×
164
                $currentRenderable = $renderable->getFormDefinition()->getElementByIdentifier($propertyPath);
×
165
            }
166

167
            // Skip errors if current renderable could not be resolved
168
            if ($currentRenderable === null) {
×
169
                continue;
×
170
            }
171

172
            $processedResult = [];
×
173

174
            foreach ($errors as $error) {
×
175
                $errorResult = new Extbase\Error\Result();
×
176
                $errorResult->addError($error);
×
177

178
                $processedResult[] = $this->processRenderingConfiguration(
×
179
                    $currentRenderable,
×
180
                    $viewModel,
×
181
                    $errorResult,
×
182
                    $configuration,
×
183
                );
×
184
            }
185

186
            $processedErrors[$propertyPath] = $processedResult;
×
187
        }
188

189
        if ($renderable instanceof Form\Domain\Model\Renderable\CompositeRenderableInterface
×
190
            || $renderable instanceof Form\Domain\Runtime\FormRuntime
×
191
        ) {
192
            return $processedErrors;
×
193
        }
194

195
        $firstProcessedError = reset($processedErrors);
×
196

197
        if ($firstProcessedError !== false) {
×
198
            return $firstProcessedError;
×
199
        }
200

201
        return [];
×
202
    }
203

204
    private function processErrorMessage(
×
205
        Form\Domain\Model\Renderable\RootRenderableInterface $renderable,
206
        Domain\Renderable\ViewModel\ViewModel $viewModel,
207
        Extbase\Error\Result $result,
208
    ): mixed {
209
        $error = $result->getFirstError();
×
210

211
        // Early return if no error is attached to result
212
        if ($error === false) {
×
213
            return null;
×
214
        }
215

216
        $translationResult = $this->viewHelperInvoker->invoke(
×
217
            $viewModel->renderingContext,
×
218
            Form\ViewHelpers\TranslateElementErrorViewHelper::class,
×
219
            [
×
220
                'element' => $renderable,
×
221
                'error' => $error,
×
222
            ],
×
223
        );
×
224

225
        return $translationResult->content;
×
226
    }
227

228
    /**
229
     * @param array<string, mixed> $configuration
230
     */
231
    private function processProperty(Extbase\Error\Result $result, array $configuration): mixed
×
232
    {
233
        $path = $configuration['path'] ?? null;
×
234

235
        if (!is_string($path)) {
×
236
            return null;
×
237
        }
238

239
        return Extbase\Reflection\ObjectAccess::getProperty($result, $path);
×
240
    }
241
}
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