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

eliashaeussler / typo3-warming / 18447563736

12 Oct 2025 06:03PM UTC coverage: 92.98% (-0.1%) from 93.098%
18447563736

push

github

web-flow
Merge pull request #995 from eliashaeussler/task/language-labels

36 of 38 new or added lines in 1 file covered. (94.74%)

1735 of 1866 relevant lines covered (92.98%)

12.02 hits per line

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

94.74
/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-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\Middleware;
25

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

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

53
        // Progress Modal
54
        'warming.modal.progress.title' => 'modal.progress.title',
55
        'warming.modal.progress.title.failed' => 'modal.progress.title.failed',
56
        'warming.modal.progress.title.warning' => 'modal.progress.title.warning',
57
        'warming.modal.progress.title.success' => 'modal.progress.title.success',
58
        'warming.modal.progress.title.aborted' => 'modal.progress.title.aborted',
59
        'warming.modal.progress.title.unknown' => 'modal.progress.title.unknown',
60
        'warming.modal.progress.button.report' => 'modal.progress.button.report',
61
        'warming.modal.progress.button.retry' => 'modal.progress.button.retry',
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

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

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

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

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

105
    private function injectLanguageLabels(): void
39✔
106
    {
107
        $this->pageRenderer->addInlineLanguageLabelArray(
39✔
108
            \array_map(
39✔
109
                static fn(string $key) => Configuration\Localization::translate($key),
39✔
110
                self::LANGUAGE_LABELS,
39✔
111
            ),
39✔
112
        );
39✔
113
    }
114

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

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

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

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

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

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

151
        return $response;
29✔
152
    }
153

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

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