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

eliashaeussler / typo3-warming / 14820095497

04 May 2025 10:02AM UTC coverage: 85.881% (-6.2%) from 92.067%
14820095497

Pull #849

github

eliashaeussler
[FEATURE] Enhance user experience in extension configuration module
Pull Request #849: [FEATURE] Enhance user experience in extension configuration module

46 of 148 new or added lines in 4 files covered. (31.08%)

1253 of 1459 relevant lines covered (85.88%)

8.79 hits per line

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

0.0
/Classes/DataProcessing/ExtensionConfigurationProcessor.php
1
<?php
2

3
declare(strict_types=1);
4

5
/*
6
 * This file is part of the TYPO3 CMS extension "warming".
7
 *
8
 * Copyright (C) 2021-2025 Elias Häußler <elias@haeussler.dev>
9
 *
10
 * This program is free software: you can redistribute it and/or modify
11
 * it under the terms of the GNU General Public License as published by
12
 * the Free Software Foundation, either version 2 of the License, or
13
 * (at your option) any later version.
14
 *
15
 * This program is distributed in the hope that it will be useful,
16
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18
 * GNU General Public License for more details.
19
 *
20
 * You should have received a copy of the GNU General Public License
21
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
22
 */
23

24
namespace EliasHaeussler\Typo3Warming\DataProcessing;
25

26
use EliasHaeussler\CacheWarmup;
27
use EliasHaeussler\Typo3Warming\Extension;
28
use EliasHaeussler\Typo3Warming\View;
29
use GuzzleHttp\RequestOptions;
30
use TYPO3\CMS\Backend;
31
use TYPO3\CMS\Core;
32
use TYPO3\CMS\Fluid;
33
use TYPO3\CMS\T3editor;
34

35
/**
36
 * ExtensionConfigurationProcessor
37
 *
38
 * @author Elias Häußler <elias@haeussler.dev>
39
 * @license GPL-2.0-or-later
40
 * @internal
41
 */
42
final class ExtensionConfigurationProcessor
43
{
44
    private const EXPECTED_INTERFACES = [
45
        'crawler' => CacheWarmup\Crawler\Crawler::class,
46
        'verboseCrawler' => CacheWarmup\Crawler\VerboseCrawler::class,
47
    ];
48
    private const TAG_LIST_VALIDATIONS = [
49
        'exclude' => 'tx_warming_validate_exclude_pattern',
50
    ];
51

52
    /**
53
     * @var array{type: 'object', properties: array<string, array{}>, additionalProperties: false}|null
54
     */
55
    private static ?array $guzzleRequestOptionsSchema = null;
56
    private static bool $codeEditorLoaded = false;
57

58
    private readonly Core\Configuration\ExtensionConfiguration $extensionConfiguration;
59
    private readonly View\TemplateRenderer $templateRenderer;
60

NEW
61
    public function __construct()
×
62
    {
63
        // DI is not possible here because we're in context of the failsafe container
NEW
64
        $this->extensionConfiguration = Core\Utility\GeneralUtility::makeInstance(Core\Configuration\ExtensionConfiguration::class);
×
NEW
65
        $this->templateRenderer = new View\TemplateRenderer(
×
NEW
66
            Core\Utility\GeneralUtility::makeInstance(Fluid\Core\Rendering\RenderingContextFactory::class)
×
NEW
67
        );
×
68
    }
69

70
    /**
71
     * @param array{fieldName: string, fieldValue: string|null} $params
72
     */
NEW
73
    public function processJson(array $params): string
×
74
    {
NEW
75
        $fieldName = $params['fieldName'];
×
NEW
76
        $variables = [
×
NEW
77
            'fieldName' => $fieldName,
×
NEW
78
            'fieldValue' => $this->extensionConfiguration->get(Extension::KEY, $fieldName),
×
NEW
79
            'codeEditorLoaded' => self::$codeEditorLoaded,
×
NEW
80
        ];
×
81

82
        // Set flag to avoid loading code editor multiple times
NEW
83
        self::$codeEditorLoaded = true;
×
84

NEW
85
        if (class_exists(T3editor\T3editor::class)) {
×
NEW
86
            $this->resolveLegacyT3EditorVariables($fieldName, $variables);
×
NEW
87
        } elseif (class_exists(Backend\CodeEditor\CodeEditor::class)) {
×
NEW
88
            $this->resolveCodeEditorVariables($fieldName, $variables);
×
89
        }
90

NEW
91
        return $this->templateRenderer->render('ExtensionConfiguration/JsonValue', $variables);
×
92
    }
93

94
    /**
95
     * @param array{fieldName: string, fieldValue: string|null} $params
96
     */
NEW
97
    public function processCrawlerFqcn(array $params): string
×
98
    {
NEW
99
        $fieldName = $params['fieldName'];
×
NEW
100
        $fieldValue = $this->extensionConfiguration->get(Extension::KEY, $fieldName);
×
101

NEW
102
        return $this->templateRenderer->render('ExtensionConfiguration/CrawlerFqcnValue', [
×
NEW
103
            'fieldName' => $fieldName,
×
NEW
104
            'fieldValue' => $fieldValue,
×
NEW
105
            'expectedInterface' => self::EXPECTED_INTERFACES[$fieldName] ?? null,
×
NEW
106
        ]);
×
107
    }
108

109
    /**
110
     * @param array{fieldName: string, fieldValue: string|null} $params
111
     */
NEW
112
    public function processTagList(array $params): string
×
113
    {
NEW
114
        $fieldName = $params['fieldName'];
×
NEW
115
        $fieldValue = $this->extensionConfiguration->get(Extension::KEY, $fieldName);
×
116

NEW
117
        return $this->templateRenderer->render('ExtensionConfiguration/TagListValue', [
×
NEW
118
            'fieldName' => $fieldName,
×
NEW
119
            'fieldValue' => $fieldValue,
×
NEW
120
            'validation' => self::TAG_LIST_VALIDATIONS[$fieldName] ?? null,
×
NEW
121
        ]);
×
122
    }
123

124
    /**
125
     * @param array<string, mixed> $variables
126
     *
127
     * @todo Remove once support for TYPO3 v12 is dropped.
128
     */
NEW
129
    private function resolveLegacyT3EditorVariables(string $fieldName, array &$variables): void
×
130
    {
131
        /* @phpstan-ignore class.notFound */
NEW
132
        $t3editor = Core\Utility\GeneralUtility::makeInstance(T3editor\T3editor::class);
×
133
        /* @phpstan-ignore class.notFound */
NEW
134
        $t3editor->registerConfiguration();
×
135

136
        /* @phpstan-ignore class.notFound */
NEW
137
        $addonRegistry = Core\Utility\GeneralUtility::makeInstance(T3editor\Registry\AddonRegistry::class);
×
NEW
138
        $addons = [];
×
139

140
        /* @phpstan-ignore class.notFound */
NEW
141
        $modeRegistry = Core\Utility\GeneralUtility::makeInstance(T3editor\Registry\ModeRegistry::class);
×
142
        /* @phpstan-ignore class.notFound */
NEW
143
        $mode = $modeRegistry->getByFileExtension('json')->getModule();
×
144

145
        /* @phpstan-ignore class.notFound */
NEW
146
        foreach ($addonRegistry->getAddons() as $addon) {
×
NEW
147
            $module = $addon->getModule();
×
NEW
148
            if ($module !== null) {
×
NEW
149
                $addons[] = $module;
×
150
            }
151
        }
152

NEW
153
        $jsonSchema = $this->buildJsonSchemaForField($fieldName);
×
154

NEW
155
        if ($jsonSchema !== null) {
×
NEW
156
            $addons[] = Core\Page\JavaScriptModuleInstruction::create(
×
NEW
157
                '@eliashaeussler/typo3-warming/backend/extension-configuration.js',
×
NEW
158
            )->invoke('jsonSchema', $jsonSchema);
×
159
        }
160

NEW
161
        $variables['codeEditor'] = [
×
NEW
162
            'mode' => Core\Utility\GeneralUtility::jsonEncodeForHtmlAttribute($mode, false),
×
NEW
163
            'addons' => Core\Utility\GeneralUtility::jsonEncodeForHtmlAttribute($addons, false),
×
NEW
164
            'jsModule' => '@typo3/t3editor/element/code-mirror-element.js',
×
NEW
165
        ];
×
166
    }
167

168
    /**
169
     * @param array<string, mixed> $variables
170
     */
NEW
171
    private function resolveCodeEditorVariables(string $fieldName, array &$variables): void
×
172
    {
NEW
173
        $codeEditor = Core\Utility\GeneralUtility::makeInstance(Backend\CodeEditor\CodeEditor::class);
×
NEW
174
        $codeEditor->registerConfiguration();
×
175

NEW
176
        $addonRegistry = Core\Utility\GeneralUtility::makeInstance(Backend\CodeEditor\Registry\AddonRegistry::class);
×
NEW
177
        $addons = [];
×
178

NEW
179
        $modeRegistry = Core\Utility\GeneralUtility::makeInstance(Backend\CodeEditor\Registry\ModeRegistry::class);
×
NEW
180
        $mode = $modeRegistry->getByFileExtension('json')->getModule();
×
181

NEW
182
        foreach ($addonRegistry->getAddons() as $addon) {
×
NEW
183
            $module = $addon->getModule();
×
NEW
184
            if ($module !== null) {
×
NEW
185
                $addons[] = $module;
×
186
            }
187
        }
188

NEW
189
        $jsonSchema = $this->buildJsonSchemaForField($fieldName);
×
190

NEW
191
        if ($jsonSchema !== null) {
×
NEW
192
            $addons[] = Core\Page\JavaScriptModuleInstruction::create(
×
NEW
193
                '@eliashaeussler/typo3-warming/backend/extension-configuration.js',
×
NEW
194
            )->invoke('jsonSchema', $jsonSchema);
×
195
        }
196

NEW
197
        $variables['codeEditor'] = [
×
NEW
198
            'mode' => Core\Utility\GeneralUtility::jsonEncodeForHtmlAttribute($mode, false),
×
NEW
199
            'addons' => Core\Utility\GeneralUtility::jsonEncodeForHtmlAttribute($addons, false),
×
NEW
200
            'jsModule' => '@typo3/backend/code-editor/element/code-mirror-element.js',
×
NEW
201
        ];
×
202
    }
203

NEW
204
    private function buildJsonSchemaForField(string $fieldName): ?string
×
205
    {
NEW
206
        $filename = sprintf('EXT:warming/Resources/Private/JsonSchema/%s.json', $fieldName);
×
NEW
207
        $filename = Core\Utility\GeneralUtility::getFileAbsFileName($filename);
×
208

NEW
209
        if ($filename === '' || !file_exists($filename)) {
×
NEW
210
            return null;
×
211
        }
212

213
        try {
NEW
214
            $json = json_decode((string)file_get_contents($filename), true, 10, JSON_THROW_ON_ERROR);
×
NEW
215
            $json['definitions']['requestOptions'] = $this->createGuzzleRequestOptionsSchema();
×
216

NEW
217
            return json_encode($json, JSON_THROW_ON_ERROR);
×
NEW
218
        } catch (\JsonException) {
×
NEW
219
            return null;
×
220
        }
221
    }
222

223
    /**
224
     * @return array{type: 'object', properties: array<string, array{}>, additionalProperties: false}
225
     */
NEW
226
    private function createGuzzleRequestOptionsSchema(): array
×
227
    {
NEW
228
        if (self::$guzzleRequestOptionsSchema !== null) {
×
NEW
229
            return self::$guzzleRequestOptionsSchema;
×
230
        }
231

NEW
232
        $reflection = new \ReflectionClass(RequestOptions::class);
×
NEW
233
        $constantReflections = $reflection->getReflectionConstants();
×
NEW
234
        $schema = [
×
NEW
235
            'type' => 'object',
×
NEW
236
            'properties' => [],
×
NEW
237
            'additionalProperties' => false,
×
NEW
238
        ];
×
239

NEW
240
        foreach ($constantReflections as $constantReflection) {
×
NEW
241
            $schema['properties'][$constantReflection->getValue()] = [];
×
242
        }
243

NEW
244
        ksort($schema['properties']);
×
245

NEW
246
        return self::$guzzleRequestOptionsSchema = $schema;
×
247
    }
248
}
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

© 2025 Coveralls, Inc