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

TYPO3-Headless / headless / 22234353452

20 Feb 2026 05:34PM UTC coverage: 73.099% (-0.2%) from 73.307%
22234353452

Pull #869

github

web-flow
Merge 89da1cc9a into a27ff50cb
Pull Request #869: [BUGFIX] Refactored unwrapping of USER_INT

30 of 31 new or added lines in 1 file covered. (96.77%)

3 existing lines in 1 file now uncovered.

1125 of 1539 relevant lines covered (73.1%)

8.2 hits per line

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

91.67
/Classes/Utility/HeadlessUserInt.php
1
<?php
2

3
/*
4
 * This file is part of the "headless" Extension for TYPO3 CMS.
5
 *
6
 * For the full copyright and license information, please read the
7
 * LICENSE.md file that was distributed with this source code.
8
 */
9

10
declare(strict_types=1);
11

12
namespace FriendsOfTYPO3\Headless\Utility;
13

14
use function json_decode;
15
use function json_encode;
16
use function json_last_error;
17
use function preg_quote;
18
use function preg_replace;
19
use function preg_replace_callback;
20
use function sprintf;
21
use function str_contains;
22
use function substr;
23

24
use function trim;
25

26
use const JSON_ERROR_NONE;
27
use const JSON_UNESCAPED_UNICODE;
28
use const PHP_VERSION_ID;
29

30
class HeadlessUserInt
31
{
32
    public const STANDARD = 'HEADLESS_INT';
33
    public const NESTED = 'NESTED_HEADLESS_INT';
34
    public const STANDARD_NULLABLE = 'HEADLESS_INT_NULL';
35
    public const NESTED_NULLABLE = 'NESTED_HEADLESS_INT_NULL';
36

37
    private const REGEX = '/(?P<quote>\\\\"|")?(?P<type>%s|%s)_START<<(?P<content>(?:[^>]|>(?!>(?P=type)_END))*+)>>(?P=type)_END(?P=quote)?/sS';
38

39
    /** @var array<string, string> */
40
    private static array $regexPatterns = [];
41

42
    public function wrap(string $content, string $type = self::STANDARD): string
43
    {
44
        return preg_replace(
2✔
45
            '/(' . preg_quote('<!--INT_SCRIPT.', '/') . '[0-9a-z]{32}' . preg_quote('-->', '/') . ')/',
2✔
46
            sprintf('%s_START<<\1>>%s_END', $type, $type),
2✔
47
            $content
2✔
48
        ) ?? $content;
2✔
49
    }
50

51
    public function hasNonCacheableContent(string $content): bool
52
    {
53
        return str_contains($content, self::STANDARD);
6✔
54
    }
55

56
    public function unwrap(string $content): string
57
    {
58
        if (str_contains($content, self::NESTED)) {
8✔
59
            $content = preg_replace_callback(
4✔
60
                $this->buildPattern(self::NESTED, self::NESTED_NULLABLE),
4✔
61
                fn(array $m) => $this->replace($m, $m['type'] === self::NESTED_NULLABLE),
4✔
62
                $content
4✔
63
            ) ?? $content;
4✔
64
        }
65

66
        return preg_replace_callback(
8✔
67
            $this->buildPattern(self::STANDARD, self::STANDARD_NULLABLE),
8✔
68
            fn(array $m) => $this->replace($m, $m['type'] === self::STANDARD_NULLABLE),
8✔
69
            $content
8✔
70
        ) ?? $content;
8✔
71
    }
72

73
    protected function buildPattern(string $primary, string $nullable): string
74
    {
75
        return self::$regexPatterns[$primary] ??= sprintf(
8✔
76
            self::REGEX,
8✔
77
            preg_quote($nullable, '/'),
8✔
78
            preg_quote($primary, '/')
8✔
79
        );
8✔
80
    }
81

82
    protected function replace(array $m, bool $isNullable): string
83
    {
84
        $hasQuotes  = $m['quote'] !== '';
8✔
85
        $rawContent = (string)$m['content'];
8✔
86

87
        if ($hasQuotes) {
8✔
88
            if ($this->isJson($rawContent)) {
6✔
89
                return $rawContent;
4✔
90
            }
91

92
            $decoded = json_decode($rawContent);
2✔
93

94
            if (empty($decoded) && $isNullable) {
2✔
95
                return 'null';
1✔
96
            }
97

98
            if ($decoded !== null) {
1✔
UNCOV
99
                return $rawContent;
×
100
            }
101

102
            return json_encode($rawContent);
1✔
103
        }
104

105
        $jsonEncoded = json_encode($rawContent);
3✔
106

107
        if ($jsonEncoded !== false && $jsonEncoded[0] === '"') {
3✔
108
            return substr($jsonEncoded, 1, -1);
3✔
109
        }
110

NEW
111
        return $jsonEncoded ?: '';
×
112
    }
113

114
    protected function isJson(string $string): bool
115
    {
116
        $string = trim($string);
6✔
117

118
        if ($string === '') {
6✔
UNCOV
119
            return false;
×
120
        }
121

122
        $first = $string[0];
6✔
123
        $last  = $string[-1];
6✔
124

125
        if (!(($first === '{' && $last === '}') || ($first === '[' && $last === ']'))) {
6✔
126
            return false;
2✔
127
        }
128

129
        if (PHP_VERSION_ID >= 80300) {
4✔
UNCOV
130
            return json_validate($string);
×
131
        }
132

133
        json_decode($string);
4✔
134
        return json_last_error() === JSON_ERROR_NONE;
4✔
135
    }
136
}
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