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

eliashaeussler / typo3-warming / 24625156556

19 Apr 2026 08:47AM UTC coverage: 94.066% (-0.2%) from 94.229%
24625156556

push

github

eliashaeussler
[TASK] Automatically rebuild frontend assets

1728 of 1837 relevant lines covered (94.07%)

12.63 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' => 'modal.report.message.noUrlsCrawled',
84
        'warming.modal.report.button.retry' => 'modal.report.button.retry',
85
        'warming.modal.report.button.close' => 'modal.report.button.close',
86

87
        // Sites Modal
88
        'warming.modal.sites.title' => 'modal.sites.title',
89
        'warming.modal.sites.userAgent.action.successful' => 'modal.sites.userAgent.action.successful',
90
        'warming.modal.sites.button.start' => 'modal.sites.button.start',
91
    ];
92

93
    public function __construct(
41✔
94
        private CacheWarmup\Crawler\Strategy\CrawlingStrategyFactory $crawlingStrategyFactory,
95
        private Core\Page\PageRenderer $pageRenderer,
96
    ) {}
41✔
97

98
    public function process(
41✔
99
        Message\ServerRequestInterface $request,
100
        Server\RequestHandlerInterface $handler,
101
    ): Message\ResponseInterface {
102
        $this->injectLanguageLabels();
41✔
103

104
        return $this->injectExtensionConfigurationScript($request, $handler->handle($request));
41✔
105
    }
106

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

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

127
        // Early return if we're not on main route
128
        if ($route?->getPath() !== '/main') {
41✔
129
            return $response;
41✔
130
        }
131

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

137
        // Early return if response is invalid
138
        if ($response->getStatusCode() !== 200) {
41✔
139
            return $response;
×
140
        }
141

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

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

155
        return $response;
29✔
156
    }
157

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

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