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

CPS-IT / handlebars / 23363698435

20 Mar 2026 09:40PM UTC coverage: 89.861% (-0.4%) from 90.285%
23363698435

Pull #543

github

web-flow
Merge 223a31852 into 86597a598
Pull Request #543: [TASK] Update codebase to match PHPStan max level

139 of 157 new or added lines in 43 files covered. (88.54%)

26 existing lines in 11 files now uncovered.

1356 of 1509 relevant lines covered (89.86%)

6.82 hits per line

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

98.18
/Classes/Renderer/Variables/VariablesProcessor.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\Renderer\Variables;
19

20
use CPSIT\Typo3Handlebars\Exception;
21
use TYPO3\CMS\Core;
22
use TYPO3\CMS\Frontend;
23

24
/**
25
 * VariablesProcessor
26
 *
27
 * @author Elias Häußler <e.haeussler@familie-redlich.de>
28
 * @license GPL-2.0-or-later
29
 * @internal
30
 */
31
final readonly class VariablesProcessor
32
{
33
    private function __construct(
9✔
34
        private Frontend\ContentObject\ContentObjectRenderer $contentObjectRenderer,
35
    ) {}
9✔
36

37
    public static function for(Frontend\ContentObject\ContentObjectRenderer $contentObjectRenderer): self
9✔
38
    {
39
        return new self($contentObjectRenderer);
9✔
40
    }
41

42
    /**
43
     * @param array<string|int, mixed> $variables
44
     * @return array<string|int, mixed>
45
     * @throws Exception\ReservedVariableCannotBeUsed
46
     * @throws Frontend\ContentObject\Exception\ContentRenderingException
47
     */
48
    public function process(array $variables): array
9✔
49
    {
50
        $variablesToProcess = [];
9✔
51
        $simpleVariables = [];
9✔
52

53
        foreach ($variables as $name => $value) {
9✔
54
            if (isset($variablesToProcess[$name])) {
9✔
55
                continue;
5✔
56
            }
57

58
            // Use sanitized variable name for simple variables
59
            $sanitizedName = rtrim((string)$name, '.');
9✔
60

61
            // Apply variable as simple variable if it's a complex structure (such as objects)
62
            if (!is_string($value) && !is_array($value)) {
9✔
63
                $simpleVariables[$sanitizedName] = $value;
1✔
64

65
                continue;
1✔
66
            }
67

68
            // Register variable for further processing if an appropriate content object is available
69
            // or if variable is a reference to another variable (will be resolved later). The whitespace
70
            // after left angle bracket is intended to avoid treating static text like <foo> as reference.
71
            // Since all refernces are written like =< foo, we can safely assume a combination of a left
72
            // angle bracket followed by a whitespace is a reference to be resolved.
73
            if (is_string($value) &&
9✔
74
                ($this->contentObjectRenderer->getContentObject($value) !== null || str_starts_with($value, '< '))
9✔
75
            ) {
76
                $cObjConfName = $name . '.';
6✔
77
                $variablesToProcess[$name] = $value;
6✔
78

79
                if (isset($variables[$cObjConfName])) {
6✔
80
                    $variablesToProcess[$cObjConfName] = $variables[$cObjConfName];
5✔
81
                }
82

83
                continue;
6✔
84
            }
85

86
            // Apply variable as simple variable if it's a simple construct
87
            // (including arrays, which will be processed recursively as they may contain content objects)
88
            if (!is_array($value)) {
6✔
89
                $simpleVariables[$sanitizedName] = $value;
2✔
90
            } elseif (!$this->shouldRemoveVariable($value)) {
5✔
91
                unset($value['removeIf.']);
4✔
92
                $simpleVariables[$sanitizedName] = $this->process($value);
4✔
93
            }
94
        }
95

96
        // Return only simple variables if no variables need to be processed
97
        if ($variablesToProcess === []) {
9✔
98
            return $simpleVariables;
6✔
99
        }
100

101
        // Process content object variables
102
        $processedVariables = $this->getContentObjectVariables($variablesToProcess);
6✔
103

104
        // Merged processed content object variables with simple variables
105
        Core\Utility\ArrayUtility::mergeRecursiveWithOverrule($processedVariables, $simpleVariables);
5✔
106

107
        return $processedVariables;
5✔
108
    }
109

110
    /**
111
     * @param array<string|int, mixed> $variables
112
     * @return array<string|int, mixed>
113
     * @throws Exception\ReservedVariableCannotBeUsed
114
     * @see https://github.com/TYPO3/typo3/blob/v13.4.13/typo3/sysext/frontend/Classes/ContentObject/FluidTemplateContentObject.php#L228
115
     */
116
    private function getContentObjectVariables(array $variables): array
6✔
117
    {
118
        $processedVariables = [];
6✔
119
        $reservedVariables = ['data', 'current'];
6✔
120

121
        foreach ($variables as $variableName => $cObjType) {
6✔
122
            if (!is_string($cObjType)) {
6✔
123
                continue;
4✔
124
            }
125

126
            if (in_array($variableName, $reservedVariables, true)) {
6✔
127
                throw new Exception\ReservedVariableCannotBeUsed($variableName);
1✔
128
            }
129

130
            $cObjConf = $variables[$variableName . '.'] ?? [];
5✔
131

132
            // Normalize content object configuration
133
            if (!is_array($cObjConf)) {
5✔
NEW
UNCOV
134
                $cObjConf = [];
×
135
            }
136

137
            // Process value
138
            $value = $this->contentObjectRenderer->cObjGetSingle($cObjType, $cObjConf, 'variables.' . $variableName);
5✔
139

140
            // Check if value should *not* be applied after processing
141
            $removeVariable = $this->shouldRemoveVariable($cObjConf, $value);
5✔
142

143
            // Apply value if not empty or no *empty toggle* is set
144
            if (!$removeVariable && trim($value) !== '') {
5✔
145
                $processedVariables[$variableName] = $value;
4✔
146
            }
147
        }
148

149
        return $processedVariables;
5✔
150
    }
151

152
    /**
153
     * @param array<mixed> $configuration
154
     */
155
    private function shouldRemoveVariable(array $configuration, ?string $value = null): bool
7✔
156
    {
157
        $removeCondition = $configuration['removeIf.'] ?? null;
7✔
158

159
        // Early return on missing or insufficient remove condition
160
        if (!is_array($removeCondition)) {
7✔
161
            return false;
6✔
162
        }
163

164
        // Use processed value as current value
165
        $currentValue = $this->contentObjectRenderer->getCurrentVal();
3✔
166
        $this->contentObjectRenderer->setCurrentVal($value);
3✔
167

168
        try {
169
            return $this->contentObjectRenderer->checkIf($removeCondition);
3✔
170
        } finally {
171
            // Restore original current value
172
            $this->contentObjectRenderer->setCurrentVal($currentValue);
3✔
173
        }
174
    }
175
}
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