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

CPS-IT / handlebars / 15461925183

05 Jun 2025 08:11AM UTC coverage: 90.937% (-1.9%) from 92.804%
15461925183

push

github

web-flow
Merge pull request #434 from CPS-IT/feature/typo3-v13

[!!!][FEATURE] Add support for TYPO3 v13.4, drop support for TYPO3 v12.4

69 of 86 new or added lines in 9 files covered. (80.23%)

2 existing lines in 1 file now uncovered.

883 of 971 relevant lines covered (90.94%)

5.52 hits per line

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

95.24
/Classes/Frontend/ContentObject/HandlebarsTemplateContentObject.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 Fr\Typo3Handlebars\Frontend\ContentObject;
19

20
use Fr\Typo3Handlebars\Exception;
21
use Fr\Typo3Handlebars\Renderer;
22
use TYPO3\CMS\Core;
23
use TYPO3\CMS\Frontend;
24

25
/**
26
 * HandlebarsTemplateContentObject
27
 *
28
 * @author Elias Häußler <e.haeussler@familie-redlich.de>
29
 * @license GPL-2.0-or-later
30
 */
31
final class HandlebarsTemplateContentObject extends Frontend\ContentObject\AbstractContentObject
32
{
33
    public function __construct(
19✔
34
        private readonly Frontend\ContentObject\ContentDataProcessor $contentDataProcessor,
35
        private readonly Renderer\Template\Path\ContentObjectPathProvider $pathProvider,
36
        private readonly Renderer\Renderer $renderer,
37
        private readonly Core\TypoScript\TypoScriptService $typoScriptService,
38
    ) {}
19✔
39

40
    /**
41
     * @param array<string, mixed> $conf
42
     */
43
    public function render($conf = []): string
19✔
44
    {
45
        /* @phpstan-ignore function.alreadyNarrowedType */
46
        if (!\is_array($conf)) {
19✔
47
            $conf = [];
×
48
        }
49

50
        // Create handlebars view
51
        $view = $this->createView($conf);
19✔
52

53
        // Resolve template paths
54
        /** @var array<string, mixed> $templatePaths */
55
        $templatePaths = $this->typoScriptService->convertTypoScriptArrayToPlainArray(
19✔
56
            array_intersect_key(
19✔
57
                $conf,
19✔
58
                [
19✔
59
                    'partialRootPath' => true,
19✔
60
                    'partialRootPaths.' => true,
19✔
61
                    'templateRootPath' => true,
19✔
62
                    'templateRootPaths.' => true,
19✔
63
                ],
19✔
64
            ),
19✔
65
        );
19✔
66

67
        // Populate template paths for availability in subsequent renderings
68
        $this->pathProvider->push($templatePaths);
19✔
69

70
        $view->assignMultiple($this->resolveVariables($conf));
19✔
71

72
        $this->renderPageAssetsIntoPageRenderer($conf);
19✔
73

74
        try {
75
            $content = $this->renderer->render($view);
19✔
76
        } finally {
77
            // Remove current content object rendering from path provider stack
78
            $this->pathProvider->pop();
19✔
79
        }
80

81
        if (isset($conf['stdWrap.'])) {
14✔
82
            return $this->cObj?->stdWrap($content, $conf['stdWrap.']) ?? $content;
1✔
83
        }
84

85
        return $content;
13✔
86
    }
87

88
    /**
89
     * @param array<string, mixed> $config
90
     */
91
    private function createView(array $config): Renderer\Template\View\HandlebarsView
19✔
92
    {
93
        $format = $this->cObj?->stdWrapValue('format', $config, null);
19✔
94
        $view = new Renderer\Template\View\HandlebarsView();
19✔
95

96
        if (is_string($format)) {
19✔
97
            $view->setFormat($format);
2✔
98
        }
99

100
        if (isset($config['templateName']) || isset($config['templateName.'])) {
19✔
101
            return $view->setTemplatePath(
2✔
102
                (string)$this->cObj?->stdWrapValue('templateName', $config),
2✔
103
            );
2✔
104
        }
105

106
        if (isset($config['template']) || isset($config['template.'])) {
17✔
107
            return $view->setTemplateSource(
13✔
108
                (string)$this->cObj?->stdWrapValue('template', $config),
13✔
109
            );
13✔
110
        }
111

112
        if (isset($config['file']) || isset($config['file.'])) {
4✔
113
            return $view->setTemplatePath(
2✔
114
                (string)$this->cObj?->stdWrapValue('file', $config),
2✔
115
            );
2✔
116
        }
117

118
        return $view;
2✔
119
    }
120

121
    /**
122
     * @param array<string, mixed> $config
123
     * @return array<string, mixed>
124
     */
125
    private function resolveVariables(array $config): array
19✔
126
    {
127
        // Process content object variables and simple variables
128
        if (\is_array($config['variables.'] ?? null)) {
19✔
129
            $variables = $this->processVariables($config['variables.']);
4✔
130
        } else {
131
            $variables = $this->getContentObjectVariables($config);
15✔
132
        }
133

134
        // Process variables with configured data processors
135
        if ($this->cObj !== null) {
19✔
136
            $variables = $this->contentDataProcessor->process($this->cObj, $config, $variables);
19✔
137
        }
138

139
        // Make settings available as variables
140
        if (isset($config['settings.'])) {
19✔
141
            $variables['settings'] = $this->typoScriptService->convertTypoScriptArrayToPlainArray($config['settings.']);
1✔
142
        }
143

144
        return $variables;
19✔
145
    }
146

147
    /**
148
     * @param array<string, mixed> $variables
149
     * @return array<string, mixed>
150
     */
151
    private function processVariables(array $variables): array
4✔
152
    {
153
        $contentObjectRenderer = $this->getContentObjectRenderer();
4✔
154
        $variablesToProcess = [];
4✔
155
        $simpleVariables = [];
4✔
156

157
        foreach ($variables as $name => $value) {
4✔
158
            if (isset($variablesToProcess[$name])) {
4✔
159
                continue;
2✔
160
            }
161

162
            // Use sanitized variable name for simple variables
163
            $sanitizedName = \rtrim($name, '.');
4✔
164

165
            // Apply variable as simple variable if it's a complex structure (such as objects)
166
            if (!is_string($value) && !\is_array($value)) {
4✔
167
                $simpleVariables[$sanitizedName] = $value;
×
168

169
                continue;
×
170
            }
171

172
            // Register variable for further processing if an appropriate content object is available
173
            // or if variable is a reference to another variable (will be resolved later)
174
            if (is_string($value) &&
4✔
175
                ($contentObjectRenderer->getContentObject($value) !== null || str_starts_with($value, '<'))
4✔
176
            ) {
177
                $cObjConfName = $name . '.';
3✔
178
                $variablesToProcess[$name] = $value;
3✔
179

180
                if (isset($variables[$cObjConfName])) {
3✔
181
                    $variablesToProcess[$cObjConfName] = $variables[$cObjConfName];
2✔
182
                }
183

184
                continue;
3✔
185
            }
186

187
            // Apply variable as simple variable if it's a simple construct
188
            // (including arrays, which will be processed recursively as they may contain content objects)
189
            if (\is_array($value)) {
2✔
190
                $simpleVariables[$sanitizedName] = $this->processVariables($value);
2✔
191

192
                unset($simpleVariables[$sanitizedName]['data']);
2✔
193
                unset($simpleVariables[$sanitizedName]['current']);
2✔
194
            } else {
195
                $simpleVariables[$sanitizedName] = $value;
1✔
196
            }
197
        }
198

199
        // Process content object variables
200
        $processedVariables = $this->getContentObjectVariables(['variables.' => $variablesToProcess]);
4✔
201

202
        // Merged processed content object variables with simple variables
203
        Core\Utility\ArrayUtility::mergeRecursiveWithOverrule($processedVariables, $simpleVariables);
4✔
204

205
        return $processedVariables;
4✔
206
    }
207

208
    /**
209
     * @param array<string, mixed> $conf
210
     * @return array<string, mixed>
211
     * @throws Exception\ReservedVariableCannotBeUsed
212
     * @see https://github.com/TYPO3/typo3/blob/v13.4.13/typo3/sysext/frontend/Classes/ContentObject/FluidTemplateContentObject.php#L228
213
     */
214
    private function getContentObjectVariables(array $conf): array
19✔
215
    {
216
        if ($this->cObj === null) {
19✔
NEW
217
            return [];
×
218
        }
219

220
        $variables = [];
19✔
221
        $reservedVariables = ['data', 'current'];
19✔
222
        $variablesToProcess = (array)($conf['variables.'] ?? []);
19✔
223

224
        foreach ($variablesToProcess as $variableName => $cObjType) {
19✔
225
            if (is_array($cObjType)) {
3✔
226
                continue;
2✔
227
            }
228

229
            if (!in_array($variableName, $reservedVariables, true)) {
3✔
230
                $cObjConf = $variablesToProcess[$variableName . '.'] ?? [];
3✔
231
                $variables[$variableName] = $this->cObj->cObjGetSingle($cObjType, $cObjConf, 'variables.' . $variableName);
3✔
232
            } else {
NEW
233
                throw new Exception\ReservedVariableCannotBeUsed($variableName);
×
234
            }
235
        }
236

237
        $variables['data'] = $this->cObj->data;
19✔
238
        $variables['current'] = $this->cObj->data[$this->cObj->currentValKey] ?? null;
19✔
239

240
        return $variables;
19✔
241
    }
242

243
    /**
244
     * @param array<string, mixed> $config
245
     */
246
    private function renderPageAssetsIntoPageRenderer(array $config): void
19✔
247
    {
248
        if (is_string($config['headerAssets'] ?? null) && is_array($config['headerAssets.'] ?? null)) {
19✔
249
            $headerAssets = $this->cObj?->cObjGetSingle($config['headerAssets'], $config['headerAssets.']) ?? '';
1✔
250
        } else {
251
            $headerAssets = '';
19✔
252
        }
253

254
        if (is_string($config['footerAssets'] ?? null) && is_array($config['footerAssets.'] ?? null)) {
19✔
255
            $footerAssets = $this->cObj?->cObjGetSingle($config['footerAssets'], $config['footerAssets.']) ?? '';
1✔
256
        } else {
257
            $footerAssets = '';
19✔
258
        }
259

260
        if (\trim($headerAssets) !== '') {
19✔
261
            $this->getPageRenderer()->addHeaderData($headerAssets);
1✔
262
        }
263

264
        if (\trim($footerAssets) !== '') {
19✔
265
            $this->getPageRenderer()->addFooterData($footerAssets);
1✔
266
        }
267
    }
268
}
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