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

CPS-IT / handlebars / 19734196770

27 Nov 2025 11:06AM UTC coverage: 90.642% (-0.7%) from 91.362%
19734196770

Pull #502

github

web-flow
Merge 3d5297d45 into 372572ae6
Pull Request #502: [FEATURE] Allow various data sources in `process-variables` processor

45 of 58 new or added lines in 1 file covered. (77.59%)

1017 of 1122 relevant lines covered (90.64%)

4.95 hits per line

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

82.43
/Classes/DataProcessing/ProcessVariablesProcessor.php
1
<?php
2

3
declare(strict_types=1);
4

5
/*
6
 * This file is part of the TYPO3 CMS extension "handlebars".
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\Typo3Handlebars\DataProcessing;
19

20
use CPSIT\Typo3Handlebars\Exception;
21
use CPSIT\Typo3Handlebars\Renderer;
22
use Psr\Log;
23
use Symfony\Component\DependencyInjection;
24
use TYPO3\CMS\Core;
25
use TYPO3\CMS\Frontend;
26

27
/**
28
 * Data processor to process given variables, especially useful in combination with other data processors.
29
 *
30
 * Example (standalone):
31
 * =====================
32
 *
33
 * hero = HANDLEBARSTEMPLATE
34
 * hero {
35
 *   templateName = @hero
36
 *
37
 *   # ...
38
 *
39
 *   dataProcessing {
40
 *     10 = process-variables
41
 *     10 {
42
 *       if.isTrue.field = title
43
 *
44
 *       variables {
45
 *         header = TEXT
46
 *         header.field = title
47
 *
48
 *         teaser = TEXT
49
 *         teaser.field = teaser
50
 *         teaser.parseFunc < lib.parseFunc_RTE
51
 *
52
 *         # ...
53
 *       }
54
 *     }
55
 *   }
56
 * }
57
 *
58
 * Example (with other data processor):
59
 * ====================================
60
 *
61
 * accordion = HANDLEBARSTEMPLATE
62
 * accordion {
63
 *   templateName = @accordion
64
 *
65
 *   # ...
66
 *
67
 *   dataProcessing {
68
 *     10 = database-query
69
 *     10 {
70
 *       # ...
71
 *
72
 *       dataProcessing {
73
 *         10 = process-variables
74
 *         10 {
75
 *           table = tx_mysitepackage_domain_model_accordion_element
76
 *           as = accordionItem
77
 *           variables {
78
 *             template = @accordion-item
79
 *
80
 *             header = TEXT
81
 *             header.field = title
82
 *
83
 *             bodytext = TEXT
84
 *             bodytext.field = bodytext
85
 *             bodytext.parseFunc < lib.parseFunc_RTE
86
 *
87
 *             # ...
88
 *           }
89
 *         }
90
 *       }
91
 *     }
92
 *   }
93
 * }
94
 *
95
 * @author Elias Häußler <e.haeussler@familie-redlich.de>
96
 * @license GPL-2.0-or-later
97
 */
98
#[DependencyInjection\Attribute\AutoconfigureTag('data.processor', ['identifier' => 'process-variables'])]
99
final readonly class ProcessVariablesProcessor implements Frontend\ContentObject\DataProcessorInterface
100
{
101
    public function __construct(
5✔
102
        private Log\LoggerInterface $logger,
103
    ) {}
5✔
104

105
    /**
106
     * @param array<string, mixed> $contentObjectConfiguration
107
     * @param array<string, mixed> $processorConfiguration
108
     * @param array<string|int, mixed> $processedData
109
     * @return array<string|int, mixed>
110
     * @throws Frontend\ContentObject\Exception\ContentRenderingException
111
     * @throws Exception\ReservedVariableCannotBeUsed
112
     */
113
    public function process(
5✔
114
        Frontend\ContentObject\ContentObjectRenderer $cObj,
115
        array $contentObjectConfiguration,
116
        array $processorConfiguration,
117
        array $processedData,
118
    ): array {
119
        $data = $this->resolveDataSources($contentObjectConfiguration, $processorConfiguration, $processedData, $cObj) ?? $cObj->data;
5✔
120
        $table = $processorConfiguration['table'] ?? $processedData['table'] ?? $cObj->getCurrentTable();
5✔
121
        $variables = $processorConfiguration['variables.'] ?? null;
5✔
122
        $as = $processorConfiguration['as'] ?? null;
5✔
123

124
        // Early return if no variables to process are configured
125
        if (!\is_array($variables)) {
5✔
126
            return $processedData;
2✔
127
        }
128

129
        // Use temporary cObj for processing
130
        $cObj = clone $cObj;
3✔
131
        $cObj->start($data, $table);
3✔
132

133
        // Early return if processing should be skipped according to a configured condition
134
        if (\is_array($processorConfiguration['if.'] ?? null) && !$cObj->checkIf($processorConfiguration['if.'])) {
3✔
135
            return $processedData;
1✔
136
        }
137

138
        // Process variables with temporary cObj
139
        $processor = Renderer\Variables\VariablesProcessor::for($cObj);
2✔
140
        $processedVariables = $processor->process($variables);
2✔
141

142
        // Apply processed variables, either override processed data (if no target variable name is given)
143
        // or merge with processed data using given target variable name ("as")
144
        if ($as === null) {
2✔
145
            $processedData = $processedVariables;
1✔
146
        } else {
147
            $processedData[$as] = $processedVariables;
1✔
148
        }
149

150
        return $processedData;
2✔
151
    }
152

153
    /**
154
     * @param array<string, mixed> $contentObjectConfiguration
155
     * @param array<string, mixed> $processorConfiguration
156
     * @param array<string|int, mixed> $processedData
157
     * @return array<string|int, mixed>|null
158
     */
159
    private function resolveDataSources(
5✔
160
        array $contentObjectConfiguration,
161
        array $processorConfiguration,
162
        array $processedData,
163
        Frontend\ContentObject\ContentObjectRenderer $contentObjectRenderer,
164
    ): ?array {
165
        /** @var array<string|int, mixed>|null $dataFromConfiguration */
166
        $dataFromConfiguration = $processorConfiguration['data.'] ?? null;
5✔
167
        /** @var array<string|int, mixed>|null $dataFromProcessedData */
168
        $dataFromProcessedData = $processedData['data'] ?? null;
5✔
169
        /** @var string|array<int, string>|null $dataSources */
170
        $dataSources = $processorConfiguration['dataSource.'] ?? $processorConfiguration['dataSource'] ?? null;
5✔
171

172
        // Early return if no data sources are configured
173
        if ($dataSources === null) {
5✔
174
            return $dataFromConfiguration ?? $dataFromProcessedData;
4✔
175
        }
176

177
        // Normalize simplified data source configuration
178
        if (!is_array($dataSources)) {
1✔
179
            $dataSources = [$dataSources];
1✔
180
        }
181

182
        ksort($dataSources);
1✔
183

184
        return array_reduce(
1✔
185
            $dataSources,
1✔
186
            fn(array $carry, string $keyword) => $this->processDataSource(
1✔
187
                $carry,
1✔
188
                $keyword,
1✔
189
                $contentObjectConfiguration,
1✔
190
                $contentObjectRenderer,
1✔
191
                $processedData,
1✔
192
            ),
1✔
193
            [],
1✔
194
        );
1✔
195
    }
196

197
    /**
198
     * @param array<string|int, mixed> $processedDataSources
199
     * @param array<string|int, mixed> $contentObjectConfiguration
200
     * @param array<string|int, mixed> $processedData
201
     * @return array<string|int, mixed>
202
     */
203
    private function processDataSource(
1✔
204
        array $processedDataSources,
205
        string $dataSourceIdentifier,
206
        array $contentObjectConfiguration,
207
        Frontend\ContentObject\ContentObjectRenderer $contentObjectRenderer,
208
        array $processedData,
209
    ): array {
210
        if (str_contains($dataSourceIdentifier, ':')) {
1✔
NEW
211
            [$dataSourceIdentifier, $path] = Core\Utility\GeneralUtility::trimExplode(':', $dataSourceIdentifier, true, 2);
×
212
        } else {
213
            $path = null;
1✔
214
        }
215

216
        $dataSource = ProcessorDataSource::tryFrom($dataSourceIdentifier);
1✔
217

218
        if ($dataSource === null) {
1✔
219
            $this->logger->warning(
1✔
220
                'Invalid processor data source keyword "{source}" passed to "process-variables" data processor (while processing {table}:{uid}).',
1✔
221
                [
1✔
222
                    'source' => $dataSourceIdentifier,
1✔
223
                    'table' => $contentObjectRenderer->getCurrentTable(),
1✔
224
                    'uid' => $contentObjectRenderer->data['uid'] ?? '*unknown*',
1✔
225
                ],
1✔
226
            );
1✔
227
        }
228

229
        $data = match ($dataSource) {
1✔
230
            ProcessorDataSource::ContentObjectConfiguration => $contentObjectConfiguration,
1✔
231
            ProcessorDataSource::ContentObjectRenderer => $contentObjectRenderer->data,
1✔
232
            ProcessorDataSource::ProcessedData => $processedData,
1✔
233
            default => [],
1✔
234
        };
1✔
235

236
        // Limit data to configured path
237
        if ($path !== null) {
1✔
238
            try {
NEW
239
                $data = Core\Utility\ArrayUtility::getValueByPath($data, $path, '.');
×
NEW
240
            } catch (Core\Utility\Exception\MissingArrayPathException) {
×
NEW
241
                $this->logger->warning(
×
NEW
242
                    'Invalid path "{path}" for processor data source "{source}" passed to "process-variables" data processor (while processing {table}:{uid}).',
×
NEW
243
                    [
×
NEW
244
                        'path' => $path,
×
NEW
245
                        'source' => $dataSourceIdentifier,
×
NEW
246
                        'table' => $contentObjectRenderer->getCurrentTable(),
×
NEW
247
                        'uid' => $contentObjectRenderer->data['uid'] ?? '*unknown*',
×
NEW
248
                    ],
×
NEW
249
                );
×
250

NEW
251
                return $processedDataSources;
×
252
            }
253
        }
254

255
        Core\Utility\ArrayUtility::mergeRecursiveWithOverrule($processedDataSources, $data);
1✔
256

257
        return $processedDataSources;
1✔
258
    }
259
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc