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

eliashaeussler / typo3-warming / 25168936300

30 Apr 2026 01:45PM UTC coverage: 94.003% (-0.06%) from 94.066%
25168936300

push

github

web-flow
[TASK] Update stylelint-copyright to v3.10.1

| datasource | package             | from   | to     |
| ---------- | ------------------- | ------ | ------ |
| npm        | stylelint-copyright | 3.10.0 | 3.10.1 |

1740 of 1851 relevant lines covered (94.0%)

13.01 hits per line

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

95.0
/Classes/Middleware/ScriptInjectionMiddleware.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-2026 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\Middleware;
25

26
use EliasHaeussler\CacheWarmup;
27
use EliasHaeussler\Typo3Warming\Utility;
28
use Psr\Http\Message;
29
use Psr\Http\Server;
30
use TYPO3\CMS\Backend;
31
use TYPO3\CMS\Core;
32

33
/**
34
 * ScriptInjectionMiddleware
35
 *
36
 * @author Elias Häußler <elias@haeussler.dev>
37
 * @license GPL-2.0-or-later
38
 */
39
final readonly class ScriptInjectionMiddleware implements Server\MiddlewareInterface
40
{
41
    private const LANGUAGE_LABELS = [
42
        // Notification
43
        'warming.notification.cancelled.title' => 'notification.cancelled.title',
44
        'warming.notification.cancelled.message' => 'notification.cancelled.message',
45
        'warming.notification.error.title' => 'notification.error.title',
46
        'warming.notification.error.message' => 'notification.error.message',
47
        'warming.notification.action.showReport' => 'notification.action.showReport',
48
        'warming.notification.action.retry' => 'notification.action.retry',
49
        'warming.notification.noSitesSelected.title' => 'notification.noSitesSelected.title',
50
        'warming.notification.noSitesSelected.message' => 'notification.noSitesSelected.message',
51

52
        // Progress Modal
53
        'warming.modal.progress.title' => 'modal.progress.title',
54
        'warming.modal.progress.title.failed' => 'modal.progress.title.failed',
55
        'warming.modal.progress.title.warning' => 'modal.progress.title.warning',
56
        'warming.modal.progress.title.success' => 'modal.progress.title.success',
57
        'warming.modal.progress.title.cancelled' => 'modal.progress.title.cancelled',
58
        'warming.modal.progress.title.unknown' => 'modal.progress.title.unknown',
59
        'warming.modal.progress.button.report' => 'modal.progress.button.report',
60
        'warming.modal.progress.button.retry' => 'modal.progress.button.retry',
61
        'warming.modal.progress.button.cancel' => 'modal.progress.button.cancel',
62
        'warming.modal.progress.button.close' => 'modal.progress.button.close',
63
        'warming.modal.progress.failedCounter' => 'modal.progress.failedCounter',
64
        'warming.modal.progress.allCounter' => 'modal.progress.allCounter',
65
        'warming.modal.progress.placeholder' => 'modal.progress.placeholder',
66

67
        // Report Modal
68
        'warming.modal.report.title' => 'modal.report.title',
69
        'warming.modal.report.panel.failed' => 'modal.report.panel.failed',
70
        'warming.modal.report.panel.failed.summary' => 'modal.report.panel.failed.summary',
71
        'warming.modal.report.panel.successful' => 'modal.report.panel.successful',
72
        'warming.modal.report.panel.successful.summary' => 'modal.report.panel.successful.summary',
73
        'warming.modal.report.panel.excluded' => 'modal.report.panel.excluded',
74
        'warming.modal.report.panel.excluded.summary' => 'modal.report.panel.excluded.summary',
75
        'warming.modal.report.panel.excluded.sitemaps' => 'modal.report.panel.excluded.sitemaps',
76
        'warming.modal.report.panel.excluded.urls' => 'modal.report.panel.excluded.urls',
77
        'warming.modal.report.action.edit' => 'modal.report.action.edit',
78
        'warming.modal.report.action.info' => 'modal.report.action.info',
79
        'warming.modal.report.action.log' => 'modal.report.action.log',
80
        'warming.modal.report.action.view' => 'modal.report.action.view',
81
        'warming.modal.report.message.requestId' => 'modal.report.message.requestId',
82
        'warming.modal.report.message.total' => 'modal.report.message.total',
83
        'warming.modal.report.message.noUrlsCrawled.header' => 'modal.report.message.noUrlsCrawled.header',
84
        'warming.modal.report.message.noUrlsCrawled.message' => 'modal.report.message.noUrlsCrawled.message',
85
        'warming.modal.report.message.incomplete.header' => 'modal.report.message.incomplete.header',
86
        'warming.modal.report.message.incomplete.message' => 'modal.report.message.incomplete.message',
87
        'warming.modal.report.button.retry' => 'modal.report.button.retry',
88
        'warming.modal.report.button.close' => 'modal.report.button.close',
89

90
        // Sites Modal
91
        'warming.modal.sites.title' => 'modal.sites.title',
92
        'warming.modal.sites.userAgent.action.successful' => 'modal.sites.userAgent.action.successful',
93
        'warming.modal.sites.button.start' => 'modal.sites.button.start',
94
    ];
95

96
    public function __construct(
42✔
97
        private CacheWarmup\Crawler\Strategy\CrawlingStrategyFactory $crawlingStrategyFactory,
98
        private Core\Page\PageRenderer $pageRenderer,
99
    ) {}
42✔
100

101
    public function process(
42✔
102
        Message\ServerRequestInterface $request,
103
        Server\RequestHandlerInterface $handler,
104
    ): Message\ResponseInterface {
105
        $this->injectLanguageLabels();
42✔
106

107
        return $this->injectExtensionConfigurationScript($request, $handler->handle($request));
42✔
108
    }
109

110
    private function injectLanguageLabels(): void
42✔
111
    {
112
        $this->pageRenderer->addInlineLanguageLabelArray(
42✔
113
            \array_map(
42✔
114
                static fn(string $key) => Utility\BackendUtility::getLanguageService()->sL(
42✔
115
                    'LLL:EXT:warming/Resources/Private/Language/locallang.xlf:' . $key,
42✔
116
                ),
42✔
117
                self::LANGUAGE_LABELS,
42✔
118
            ),
42✔
119
        );
42✔
120
    }
121

122
    private function injectExtensionConfigurationScript(
42✔
123
        Message\ServerRequestInterface $request,
124
        Message\ResponseInterface $response,
125
    ): Message\ResponseInterface {
126
        /** @var Backend\Routing\Route|null $route */
127
        $route = $request->getAttribute('route');
42✔
128
        $backendUser = Utility\BackendUtility::getBackendUser();
42✔
129

130
        // Early return if we're not on main route
131
        if ($route?->getPath() !== '/main') {
42✔
132
            return $response;
42✔
133
        }
134

135
        // Early return if EXT:install is not loaded and extension settings module is not available
136
        if (!Core\Utility\ExtensionManagementUtility::isLoaded('install')) {
42✔
137
            return $response;
×
138
        }
139

140
        // Early return if response is invalid
141
        if ($response->getStatusCode() !== 200) {
42✔
142
            return $response;
×
143
        }
144

145
        // Early return on insufficient privileges (only system maintainers can access settings module)
146
        if ($backendUser?->isSystemMaintainer() !== true) {
42✔
147
            return $response;
13✔
148
        }
149

150
        // Inject scripts into <head>
151
        $body = $response->getBody();
30✔
152
        $contents = (string)$body;
30✔
153
        $body->rewind();
30✔
154
        $body->write(
30✔
155
            str_replace('</head>', $this->renderScriptTag($request) . '</head>', $contents),
30✔
156
        );
30✔
157

158
        return $response;
30✔
159
    }
160

161
    private function renderScriptTag(Message\ServerRequestInterface $request): string
30✔
162
    {
163
        /** @var Core\Security\ContentSecurityPolicy\ConsumableNonce $nonce */
164
        $nonce = $request->getAttribute('nonce');
30✔
165
        $nonceValue = $nonce->consume();
30✔
166
        $strategies = json_encode($this->crawlingStrategyFactory->getAll());
30✔
167

168
        return <<<JS
30✔
169
<script async nonce="{$nonceValue}" id="tx-warming-script-inject">
30✔
170
import('@eliashaeussler/typo3-warming/extension-configuration.js').then(({default: extensionConfiguration}) => {
171
    extensionConfiguration.initializeModalListener('{$nonceValue}', {$strategies});
30✔
172
});
173
</script>
174
JS;
30✔
175
    }
176
}
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