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

FluidTYPO3 / vhs / 30001290505

23 Jul 2026 10:57AM UTC coverage: 70.309% (-1.7%) from 72.022%
30001290505

push

github

NamelessCoder
[TER] 8.0.0

4819 of 6854 relevant lines covered (70.31%)

6.54 hits per line

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

71.59
/Classes/ViewHelpers/Format/Json/EncodeViewHelper.php
1
<?php
2
namespace FluidTYPO3\Vhs\ViewHelpers\Format\Json;
3

4
/*
5
 * This file is part of the FluidTYPO3/Vhs project under GPLv2 or later.
6
 *
7
 * For the full copyright and license information, please read the
8
 * LICENSE.md file that was distributed with this source code.
9
 */
10

11
use FluidTYPO3\Vhs\Core\ViewHelper\AbstractViewHelper;
12
use FluidTYPO3\Vhs\Utility\ErrorUtility;
13
use TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface;
14
use TYPO3\CMS\Extbase\Reflection\ObjectAccess;
15
use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface;
16

17
/**
18
 * ### JSON Encoding ViewHelper
19
 *
20
 * Returns a string containing the JSON representation of the argument.
21
 * The argument may be any of the following types:
22
 *
23
 * - arrays, associative and traditional
24
 * - DomainObjects
25
 * - arrays containing DomainObjects
26
 * - ObjectStorage containing DomainObjects
27
 * - standard types (string, integer, boolean, float, NULL)
28
 * - DateTime including ones found as property values on DomainObjects
29
 *
30
 * Recursion protection is enabled for DomainObjects with the option to
31
 * add a special marker (any variable type above also supported here)
32
 * which is inserted where an object which would cause recursion would
33
 * be placed.
34
 *
35
 * Be specially careful when you JSON encode DomainObjects which have
36
 * recursive relations to itself using either 1:n or m:n - in this case
37
 * the one member of the converted relation will be whichever value you
38
 * specified as "recursionMarker" - or the default value, NULL. When
39
 * using the output of such conversion in JavaScript please make sure you
40
 * check the type before assuming that every member of a converted 1:n
41
 * or m:n recursive relation is in fact a JavaScript. Not doing so may
42
 * result in fatal JavaScript errors in the client browser.
43
 */
44
class EncodeViewHelper extends AbstractViewHelper
45
{
46
    protected static array $encounteredClasses = [];
47

48
    public function initializeArguments(): void
49
    {
50
        $this->registerArgument('value', 'mixed', 'Value to encode as JSON');
3✔
51
        $this->registerArgument(
3✔
52
            'useTraversableKeys',
3✔
53
            'boolean',
3✔
54
            'If TRUE, preserves keys from Traversables converted to arrays. Not recommended for ObjectStorages!',
3✔
55
            false,
3✔
56
            false
3✔
57
        );
3✔
58
        $this->registerArgument(
3✔
59
            'preventRecursion',
3✔
60
            'boolean',
3✔
61
            'If FALSE, allows recursion to occur which could potentially be fatal to the output unless managed',
3✔
62
            false,
3✔
63
            true
3✔
64
        );
3✔
65
        $this->registerArgument(
3✔
66
            'recursionMarker',
3✔
67
            'mixed',
3✔
68
            'String or null - inserted instead of recursive instances of objects'
3✔
69
        );
3✔
70
        $this->registerArgument(
3✔
71
            'dateTimeFormat',
3✔
72
            'string',
3✔
73
            'A date() format for DateTime values to JSON-compatible values. NULL means JS UNIXTIME (time()*1000)'
3✔
74
        );
3✔
75
        $this->registerArgument('pretty', 'bool', 'If TRUE, outputs JSON with JSON_PRETTY_PRINT', false, false);
3✔
76
    }
77

78
    /**
79
     * @return mixed
80
     */
81
    public static function renderStatic(
82
        array $arguments,
83
        \Closure $renderChildrenClosure,
84
        RenderingContextInterface $renderingContext
85
    ) {
86
        /** @var array|object $value */
87
        $value = $arguments['value'] ?? $renderChildrenClosure();
6✔
88
        $useTraversableKeys = (bool) $arguments['useTraversableKeys'];
6✔
89
        $preventRecursion = (bool) $arguments['preventRecursion'];
6✔
90
        /** @var string $recursionMarker */
91
        $recursionMarker = $arguments['recursionMarker'] ?? '**recursion**';
6✔
92
        /** @var string|null $dateTimeFormat */
93
        $dateTimeFormat = $arguments['dateTimeFormat'];
6✔
94
        $options = JSON_HEX_AMP | JSON_HEX_QUOT | JSON_HEX_APOS | JSON_HEX_TAG;
6✔
95
        if ($arguments['pretty']) {
6✔
96
            $options |= JSON_PRETTY_PRINT;
×
97
        }
98

99
        static::$encounteredClasses = [];
6✔
100
        $json = static::encodeValue(
6✔
101
            $value,
6✔
102
            $useTraversableKeys,
6✔
103
            $preventRecursion,
6✔
104
            $recursionMarker,
6✔
105
            $dateTimeFormat,
6✔
106
            $options
6✔
107
        );
6✔
108
        return $json;
6✔
109
    }
110

111
    /**
112
     * @param mixed $value
113
     * @return string|false
114
     */
115
    protected static function encodeValue(
116
        $value,
117
        bool $useTraversableKeys,
118
        bool $preventRecursion,
119
        ?string $recursionMarker,
120
        ?string $dateTimeFormat,
121
        int $options
122
    ) {
123
        if ($value instanceof \Traversable) {
6✔
124
            // Note: also converts ObjectStorage to \Vendor\Extname\Domain\Model\ObjectType[] which are each converted
125
            $value = iterator_to_array($value, $useTraversableKeys);
×
126
        } elseif ($value instanceof DomainObjectInterface) {
6✔
127
            // Convert to associative array,
128
            $value = static::recursiveDomainObjectToArray($value, $preventRecursion, $recursionMarker);
×
129
        } elseif ($value instanceof \DateTime) {
6✔
130
            $value = static::dateTimeToUnixtimeMiliseconds($value, $dateTimeFormat);
3✔
131
        }
132

133
        // process output of conversion, catching specially supported object types such as DomainObject and DateTime
134
        if (is_array($value)) {
6✔
135
            $value = static::recursiveArrayOfDomainObjectsToArray($value, $preventRecursion, $recursionMarker);
3✔
136
            $value = static::recursiveDateTimeToUnixtimeMiliseconds($value, $dateTimeFormat);
3✔
137
        }
138
        $json = json_encode($value, $options);
6✔
139
        if (JSON_ERROR_NONE !== json_last_error()) {
6✔
140
            ErrorUtility::throwViewHelperException('The provided argument cannot be converted into JSON.', 1358440181);
×
141
        }
142
        return $json;
6✔
143
    }
144

145
    /**
146
     * Converts any encountered DateTime instances to UNIXTIME timestamps
147
     * which are then multiplied by 1000 to create a JavaScript appropriate
148
     * time stamp - ready to be loaded into a Date object client-side.
149
     *
150
     * Works on already converted DomainObjects which are at this point just
151
     * associative arrays of values - which might be DateTime instances.
152
     */
153
    protected static function recursiveDateTimeToUnixtimeMiliseconds(array $array, ?string $dateTimeFormat): array
154
    {
155
        foreach ($array as $key => $possibleDateTime) {
3✔
156
            if ($possibleDateTime instanceof \DateTime) {
3✔
157
                $array[$key] = static::dateTimeToUnixtimeMiliseconds($possibleDateTime, $dateTimeFormat);
×
158
            } elseif (is_array($possibleDateTime)) {
3✔
159
                $array[$key] = static::recursiveDateTimeToUnixtimeMiliseconds($possibleDateTime, $dateTimeFormat);
×
160
            }
161
        }
162
        return $array;
3✔
163
    }
164

165
    /**
166
     * Formats a single DateTime instance to whichever value is demanded by
167
     * the format specified in $dateTimeFormat (DateTime::format syntax).
168
     * Default format is NULL a JS UNIXTIME (time()*1000) is produced.
169
     *
170
     * @return integer|string
171
     */
172
    protected static function dateTimeToUnixtimeMiliseconds(\DateTime $dateTime, ?string $dateTimeFormat)
173
    {
174
        if (null === $dateTimeFormat) {
3✔
175
            return intval($dateTime->format('U')) * 1000;
3✔
176
        }
177
        return $dateTime->format($dateTimeFormat);
×
178
    }
179

180
    /**
181
     * Convert an array of possible DomainObject instances. The argument
182
     * $possibleDomainObjects could also an associative array representation
183
     * of another DomainObject - which means each value could potentially
184
     * be another DomainObject, an ObjectStorage of DomainObjects or a simple
185
     * value type. The type is checked and another recursive call is used to
186
     * convert any nested objects.
187
     *
188
     * @param array|DomainObjectInterface[]|\Traversable[] $domainObjects
189
     * @return DomainObjectInterface[]|array[]|string[]|\Traversable[]|null[]
190
     */
191
    protected static function recursiveArrayOfDomainObjectsToArray(
192
        array $domainObjects,
193
        bool $preventRecursion,
194
        ?string $recursionMarker
195
    ): array {
196
        foreach ($domainObjects as $key => $possibleDomainObject) {
3✔
197
            if ($possibleDomainObject instanceof DomainObjectInterface) {
3✔
198
                $domainObjects[$key] = static::recursiveDomainObjectToArray(
×
199
                    $possibleDomainObject,
×
200
                    $preventRecursion,
×
201
                    $recursionMarker
×
202
                );
×
203
            } elseif ($possibleDomainObject instanceof \Traversable) {
3✔
204
                $traversableAsArray = iterator_to_array($possibleDomainObject);
×
205
                $domainObjects[$key] = static::recursiveArrayOfDomainObjectsToArray(
×
206
                    $traversableAsArray,
×
207
                    $preventRecursion,
×
208
                    $recursionMarker
×
209
                );
×
210
            }
211
        }
212
        return $domainObjects;
3✔
213
    }
214

215
    /**
216
     * Convert a single DomainObject instance first to an array, then pass
217
     * that array through recursive DomainObject detection. This will convert
218
     * any 1:1, 1:n, n:1 and m:n relations.
219
     *
220
     * @param DomainObjectInterface $domainObject
221
     * @param boolean $preventRecursion
222
     * @param string $recursionMarker
223
     * @return array|string|null
224
     */
225
    protected static function recursiveDomainObjectToArray(
226
        DomainObjectInterface $domainObject,
227
        bool $preventRecursion,
228
        ?string $recursionMarker
229
    ) {
230
        $hash = spl_object_hash($domainObject);
×
231
        if ($preventRecursion && in_array($hash, static::$encounteredClasses)) {
×
232
            return $recursionMarker;
×
233
        }
234
        /** @var array $converted */
235
        $converted = ObjectAccess::getGettableProperties($domainObject);
×
236
        static::$encounteredClasses[] = $hash;
×
237
        $converted = static::recursiveArrayOfDomainObjectsToArray($converted, $preventRecursion, $recursionMarker);
×
238
        return $converted;
×
239
    }
240
}
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