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

eliashaeussler / typo3-warming / 15422445901

03 Jun 2025 04:13PM UTC coverage: 90.617% (-0.4%) from 91.04%
15422445901

push

github

github-actions[bot]
[TASK] Automatically rebuild frontend assets

1352 of 1492 relevant lines covered (90.62%)

10.07 hits per line

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

76.79
/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
 * @phpstan-type ExtensionConfiguration array{fieldName: string, fieldValue: string|null}
43
 */
44
final class ExtensionConfigurationProcessor
45
{
46
    private const EXPECTED_INTERFACES = [
47
        'crawler' => CacheWarmup\Crawler\Crawler::class,
48
        'verboseCrawler' => CacheWarmup\Crawler\VerboseCrawler::class,
49
    ];
50
    private const TAG_LIST_VALIDATIONS = [
51
        'exclude' => 'tx_warming_validate_exclude_pattern',
52
    ];
53

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

60
    private readonly CacheWarmup\Crawler\Strategy\CrawlingStrategyFactory $crawlingStrategyFactory;
61
    private readonly Core\Configuration\ExtensionConfiguration $extensionConfiguration;
62
    private readonly View\TemplateRenderer $templateRenderer;
63

64
    public function __construct()
6✔
65
    {
66
        // DI is not possible here because we're in context of the failsafe container
67
        $this->crawlingStrategyFactory = new CacheWarmup\Crawler\Strategy\CrawlingStrategyFactory();
6✔
68
        $this->extensionConfiguration = Core\Utility\GeneralUtility::makeInstance(Core\Configuration\ExtensionConfiguration::class);
6✔
69
        $this->templateRenderer = new View\TemplateRenderer(
6✔
70
            Core\Utility\GeneralUtility::makeInstance(Fluid\Core\Rendering\RenderingContextFactory::class)
6✔
71
        );
6✔
72
    }
73

74
    /**
75
     * @param ExtensionConfiguration $params
76
     */
77
    public function processJson(array $params): string
6✔
78
    {
79
        $fieldName = $params['fieldName'];
6✔
80
        $variables = [
6✔
81
            'fieldName' => $fieldName,
6✔
82
            'fieldValue' => $this->extensionConfiguration->get(Extension::KEY, $fieldName),
6✔
83
            'codeEditorLoaded' => self::$codeEditorLoaded,
6✔
84
        ];
6✔
85

86
        // Set flag to avoid loading code editor multiple times
87
        self::$codeEditorLoaded = true;
6✔
88

89
        if (class_exists(T3editor\T3editor::class)) {
6✔
90
            $this->resolveLegacyT3EditorVariables($fieldName, $variables);
×
91
        } elseif (class_exists(Backend\CodeEditor\CodeEditor::class)) {
6✔
92
            $this->resolveCodeEditorVariables($fieldName, $variables);
6✔
93
        }
94

95
        return $this->templateRenderer->render('ExtensionConfiguration/JsonValue', $variables);
6✔
96
    }
97

98
    /**
99
     * @param ExtensionConfiguration $params
100
     */
101
    public function processCrawlerFqcn(array $params): string
6✔
102
    {
103
        $fieldName = $params['fieldName'];
6✔
104
        $fieldValue = $this->extensionConfiguration->get(Extension::KEY, $fieldName);
6✔
105

106
        return $this->templateRenderer->render('ExtensionConfiguration/CrawlerFqcnValue', [
6✔
107
            'fieldName' => $fieldName,
6✔
108
            'fieldValue' => $fieldValue,
6✔
109
            'expectedInterface' => self::EXPECTED_INTERFACES[$fieldName] ?? null,
6✔
110
        ]);
6✔
111
    }
112

113
    /**
114
     * @param ExtensionConfiguration $params
115
     */
116
    public function processCrawlingStrategy(array $params): string
6✔
117
    {
118
        $fieldName = $params['fieldName'];
6✔
119
        $fieldValue = trim((string)$this->extensionConfiguration->get(Extension::KEY, $fieldName));
6✔
120
        $strategies = $this->crawlingStrategyFactory->getAll();
6✔
121

122
        // Make sure at least currently selected strategy is selectable in install tool
123
        // (in backend context, all available strategies are injected using JavaScript)
124
        if ($fieldValue !== '' && !in_array($fieldValue, $strategies, true)) {
6✔
125
            $strategies[] = $fieldValue;
×
126
        }
127

128
        return $this->templateRenderer->render('ExtensionConfiguration/CrawlingStrategyValue', [
6✔
129
            'fieldName' => $fieldName,
6✔
130
            'fieldValue' => $fieldValue,
6✔
131
            'strategies' => $strategies,
6✔
132
        ]);
6✔
133
    }
134

135
    /**
136
     * @param ExtensionConfiguration $params
137
     */
138
    public function processTagList(array $params): string
6✔
139
    {
140
        $fieldName = $params['fieldName'];
6✔
141
        $fieldValue = $this->extensionConfiguration->get(Extension::KEY, $fieldName);
6✔
142

143
        return $this->templateRenderer->render('ExtensionConfiguration/TagListValue', [
6✔
144
            'fieldName' => $fieldName,
6✔
145
            'fieldValue' => $fieldValue,
6✔
146
            'validation' => self::TAG_LIST_VALIDATIONS[$fieldName] ?? null,
6✔
147
        ]);
6✔
148
    }
149

150
    /**
151
     * @param array<string, mixed> $variables
152
     *
153
     * @todo Remove once support for TYPO3 v12 is dropped.
154
     */
155
    private function resolveLegacyT3EditorVariables(string $fieldName, array &$variables): void
×
156
    {
157
        /* @phpstan-ignore class.notFound */
158
        $t3editor = Core\Utility\GeneralUtility::makeInstance(T3editor\T3editor::class);
×
159
        /* @phpstan-ignore class.notFound */
160
        $t3editor->registerConfiguration();
×
161

162
        /* @phpstan-ignore class.notFound */
163
        $addonRegistry = Core\Utility\GeneralUtility::makeInstance(T3editor\Registry\AddonRegistry::class);
×
164
        $addons = [];
×
165

166
        /* @phpstan-ignore class.notFound */
167
        $modeRegistry = Core\Utility\GeneralUtility::makeInstance(T3editor\Registry\ModeRegistry::class);
×
168
        /* @phpstan-ignore class.notFound */
169
        $mode = $modeRegistry->getByFileExtension('json')->getModule();
×
170

171
        /* @phpstan-ignore class.notFound */
172
        foreach ($addonRegistry->getAddons() as $addon) {
×
173
            $module = $addon->getModule();
×
174
            if ($module !== null) {
×
175
                $addons[] = $module;
×
176
            }
177
        }
178

179
        $jsonSchema = $this->buildJsonSchemaForField($fieldName);
×
180

181
        if ($jsonSchema !== null) {
×
182
            $addons[] = Core\Page\JavaScriptModuleInstruction::create(
×
183
                '@eliashaeussler/typo3-warming/backend/extension-configuration.js',
×
184
            )->invoke('jsonSchema', $jsonSchema);
×
185
        }
186

187
        $variables['codeEditor'] = [
×
188
            'mode' => Core\Utility\GeneralUtility::jsonEncodeForHtmlAttribute($mode, false),
×
189
            'addons' => Core\Utility\GeneralUtility::jsonEncodeForHtmlAttribute($addons, false),
×
190
            'jsModule' => '@typo3/t3editor/element/code-mirror-element.js',
×
191
        ];
×
192
    }
193

194
    /**
195
     * @param array<string, mixed> $variables
196
     */
197
    private function resolveCodeEditorVariables(string $fieldName, array &$variables): void
6✔
198
    {
199
        $codeEditor = Core\Utility\GeneralUtility::makeInstance(Backend\CodeEditor\CodeEditor::class);
6✔
200
        $codeEditor->registerConfiguration();
6✔
201

202
        $addonRegistry = Core\Utility\GeneralUtility::makeInstance(Backend\CodeEditor\Registry\AddonRegistry::class);
6✔
203
        $addons = [];
6✔
204

205
        $modeRegistry = Core\Utility\GeneralUtility::makeInstance(Backend\CodeEditor\Registry\ModeRegistry::class);
6✔
206
        $mode = $modeRegistry->getByFileExtension('json')->getModule();
6✔
207

208
        foreach ($addonRegistry->getAddons() as $addon) {
6✔
209
            $module = $addon->getModule();
6✔
210
            if ($module !== null) {
6✔
211
                $addons[] = $module;
6✔
212
            }
213
        }
214

215
        $jsonSchema = $this->buildJsonSchemaForField($fieldName);
6✔
216

217
        if ($jsonSchema !== null) {
6✔
218
            $addons[] = Core\Page\JavaScriptModuleInstruction::create(
6✔
219
                '@eliashaeussler/typo3-warming/backend/extension-configuration.js',
6✔
220
            )->invoke('jsonSchema', $jsonSchema);
6✔
221
        }
222

223
        $variables['codeEditor'] = [
6✔
224
            'mode' => Core\Utility\GeneralUtility::jsonEncodeForHtmlAttribute($mode, false),
6✔
225
            'addons' => Core\Utility\GeneralUtility::jsonEncodeForHtmlAttribute($addons, false),
6✔
226
            'jsModule' => '@typo3/backend/code-editor/element/code-mirror-element.js',
6✔
227
        ];
6✔
228
    }
229

230
    private function buildJsonSchemaForField(string $fieldName): ?string
6✔
231
    {
232
        $filename = sprintf('EXT:warming/Resources/Private/JsonSchema/%s.json', $fieldName);
6✔
233
        $filename = Core\Utility\GeneralUtility::getFileAbsFileName($filename);
6✔
234

235
        if ($filename === '' || !file_exists($filename)) {
6✔
236
            return null;
×
237
        }
238

239
        try {
240
            $json = json_decode((string)file_get_contents($filename), true, 10, JSON_THROW_ON_ERROR);
6✔
241
            $json['definitions']['requestOptions'] = $this->createGuzzleRequestOptionsSchema();
6✔
242

243
            return json_encode($json, JSON_THROW_ON_ERROR);
6✔
244
        } catch (\JsonException) {
×
245
            return null;
×
246
        }
247
    }
248

249
    /**
250
     * @return array{type: 'object', properties: array<string, array{}>, additionalProperties: false}
251
     */
252
    private function createGuzzleRequestOptionsSchema(): array
6✔
253
    {
254
        if (self::$guzzleRequestOptionsSchema !== null) {
6✔
255
            return self::$guzzleRequestOptionsSchema;
6✔
256
        }
257

258
        $reflection = new \ReflectionClass(RequestOptions::class);
6✔
259
        $constantReflections = $reflection->getReflectionConstants();
6✔
260
        $schema = [
6✔
261
            'type' => 'object',
6✔
262
            'properties' => [],
6✔
263
            'additionalProperties' => false,
6✔
264
        ];
6✔
265

266
        foreach ($constantReflections as $constantReflection) {
6✔
267
            $schema['properties'][$constantReflection->getValue()] = [];
6✔
268
        }
269

270
        ksort($schema['properties']);
6✔
271

272
        return self::$guzzleRequestOptionsSchema = $schema;
6✔
273
    }
274
}
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