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

eliashaeussler / typo3-warming / 23961191857

03 Apr 2026 08:32PM UTC coverage: 94.229% (-0.3%) from 94.497%
23961191857

push

github

eliashaeussler
[TASK] Automatically rebuild frontend assets

1698 of 1802 relevant lines covered (94.23%)

12.6 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.aborted.title' => 'notification.aborted.title',
44
        'warming.notification.aborted.message' => 'notification.aborted.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.aborted' => 'modal.progress.title.aborted',
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.close' => 'modal.progress.button.close',
62
        'warming.modal.progress.failedCounter' => 'modal.progress.failedCounter',
63
        'warming.modal.progress.allCounter' => 'modal.progress.allCounter',
64
        'warming.modal.progress.placeholder' => 'modal.progress.placeholder',
65

66
        // Report Modal
67
        'warming.modal.report.title' => 'modal.report.title',
68
        'warming.modal.report.panel.failed' => 'modal.report.panel.failed',
69
        'warming.modal.report.panel.failed.summary' => 'modal.report.panel.failed.summary',
70
        'warming.modal.report.panel.successful' => 'modal.report.panel.successful',
71
        'warming.modal.report.panel.successful.summary' => 'modal.report.panel.successful.summary',
72
        'warming.modal.report.panel.excluded' => 'modal.report.panel.excluded',
73
        'warming.modal.report.panel.excluded.summary' => 'modal.report.panel.excluded.summary',
74
        'warming.modal.report.panel.excluded.sitemaps' => 'modal.report.panel.excluded.sitemaps',
75
        'warming.modal.report.panel.excluded.urls' => 'modal.report.panel.excluded.urls',
76
        'warming.modal.report.action.edit' => 'modal.report.action.edit',
77
        'warming.modal.report.action.info' => 'modal.report.action.info',
78
        'warming.modal.report.action.log' => 'modal.report.action.log',
79
        'warming.modal.report.action.view' => 'modal.report.action.view',
80
        'warming.modal.report.message.requestId' => 'modal.report.message.requestId',
81
        'warming.modal.report.message.total' => 'modal.report.message.total',
82
        'warming.modal.report.message.noUrlsCrawled' => 'modal.report.message.noUrlsCrawled',
83

84
        // Sites Modal
85
        'warming.modal.sites.title' => 'modal.sites.title',
86
        'warming.modal.sites.userAgent.action.successful' => 'modal.sites.userAgent.action.successful',
87
        'warming.modal.sites.button.start' => 'modal.sites.button.start',
88
    ];
89

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

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

101
        return $this->injectExtensionConfigurationScript($request, $handler->handle($request));
41✔
102
    }
103

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

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

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

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

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

139
        // Early return on insufficient privileges (only system maintainers can access settings module)
140
        if (!($backendUser->isSystemMaintainer())) {
41✔
141
            return $response;
13✔
142
        }
143

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

152
        return $response;
29✔
153
    }
154

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

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