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

eliashaeussler / typo3-warming / 14431906128

13 Apr 2025 05:54PM UTC coverage: 91.301% (-0.2%) from 91.486%
14431906128

Pull #839

github

eliashaeussler
[!!!][TASK] Convert `AccessUtility` to `WarmupPermissionGuard`
Pull Request #839: [!!!][TASK] Convert `AccessUtility` to `WarmupPermissionGuard`

55 of 60 new or added lines in 4 files covered. (91.67%)

5 existing lines in 2 files now uncovered.

1123 of 1230 relevant lines covered (91.3%)

8.8 hits per line

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

96.59
/Classes/Backend/ContextMenu/ItemProviders/CacheWarmupProvider.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\Backend\ContextMenu\ItemProviders;
25

26
use EliasHaeussler\Typo3SitemapLocator;
27
use EliasHaeussler\Typo3Warming\Configuration;
28
use EliasHaeussler\Typo3Warming\Security;
29
use TYPO3\CMS\Backend;
30
use TYPO3\CMS\Core;
31

32
/**
33
 * CacheWarmupProvider
34
 *
35
 * @author Elias Häußler <elias@haeussler.dev>
36
 * @license GPL-2.0-or-later
37
 */
38
final class CacheWarmupProvider extends Backend\ContextMenu\ItemProviders\PageProvider
39
{
40
    private const ITEM_MODE_PAGE = 'cacheWarmupPage';
41
    private const ITEM_MODE_SITE = 'cacheWarmupSite';
42

43
    /**
44
     * @var array<string, array{
45
     *     type: string
46
     * }|array{
47
     *     type: string,
48
     *     label: string,
49
     *     iconIdentifier: string,
50
     *     callbackAction: string,
51
     *     childItems?: array<string, array{
52
     *         label?: string,
53
     *         iconIdentifier?: string,
54
     *         callbackAction?: string
55
     *     }>
56
     * }>
57
     */
58
    protected $itemsConfiguration = [
59
        'cacheWarmupDivider' => [
60
            'type' => 'divider',
61
        ],
62
        self::ITEM_MODE_PAGE => [
63
            'type' => 'item',
64
            'label' => 'LLL:EXT:warming/Resources/Private/Language/locallang.xlf:contextMenu.item.cacheWarmup',
65
            'iconIdentifier' => 'cache-warmup-page',
66
            'callbackAction' => 'warmupPageCache',
67
        ],
68
        self::ITEM_MODE_SITE => [
69
            'type' => 'item',
70
            'label' => 'LLL:EXT:warming/Resources/Private/Language/locallang.xlf:contextMenu.item.cacheWarmupAll',
71
            'iconIdentifier' => 'cache-warmup-site',
72
            'callbackAction' => 'warmupSiteCache',
73
        ],
74
    ];
75

76
    public function __construct(
11✔
77
        private readonly Typo3SitemapLocator\Sitemap\SitemapLocator $sitemapLocator,
78
        private readonly Core\Site\SiteFinder $siteFinder,
79
        private readonly Configuration\Configuration $configuration,
80
        private readonly Security\WarmupPermissionGuard $accessGuard,
81
    ) {
82
        parent::__construct();
11✔
83
    }
84

85
    protected function canRender(string $itemName, string $type): bool
11✔
86
    {
87
        // Early return if cache warmup from page tree is disabled globally
88
        if (!$this->configuration->isEnabledInPageTree()) {
11✔
89
            return false;
1✔
90
        }
91

92
        // Pseudo items (such as dividers) are always renderable
93
        if ($type !== 'item') {
10✔
94
            return true;
10✔
95
        }
96

97
        // Non-supported doktypes are never renderable
98
        $doktype = (int)($this->record['doktype'] ?? null);
10✔
99
        if ($doktype <= 0 || !\in_array($doktype, $this->configuration->getSupportedDoktypes(), true)) {
10✔
UNCOV
100
            return false;
×
101
        }
102

103
        // Language items in sub-menus are already filtered
104
        if (str_contains($itemName, '_lang_')) {
10✔
105
            return true;
7✔
106
        }
107

108
        if (\in_array($itemName, $this->disabledItems, true)) {
10✔
109
            return false;
1✔
110
        }
111

112
        // Root page cannot be used for cache warmup since it is not accessible in Frontend
113
        if ($this->isRoot()) {
10✔
UNCOV
114
            return false;
×
115
        }
116

117
        // Running cache warmup in "site" mode (= using XML sitemap) is only valid for root pages
118
        if ($itemName === self::ITEM_MODE_SITE) {
10✔
119
            return $this->canWarmupCachesOfSite();
10✔
120
        }
121

122
        return $this->accessGuard->canWarmupCacheOfPage((int)$this->identifier);
10✔
123
    }
124

125
    /**
126
     * @param array<string, array<string, mixed>> $items
127
     * @return array<string, array<string, mixed>>
128
     */
129
    public function addItems(array $items): array
11✔
130
    {
131
        $this->initialize();
11✔
132

133
        $localItems = $this->prepareItems($this->itemsConfiguration);
11✔
134
        $items += $localItems;
11✔
135

136
        return $items;
11✔
137
    }
138

139
    protected function initialize(): void
11✔
140
    {
141
        parent::initialize();
11✔
142
        $this->initSubMenus();
11✔
143
    }
144

145
    public function getPriority(): int
11✔
146
    {
147
        return 45;
11✔
148
    }
149

150
    private function initSubMenus(): void
11✔
151
    {
152
        $site = $this->getCurrentSite();
11✔
153

154
        // Early return if site cannot be resolved
155
        if ($site === null) {
11✔
156
            return;
1✔
157
        }
158

159
        foreach ($this->itemsConfiguration as $itemName => &$configuration) {
10✔
160
            // Skip pseudo types and non-renderable items
161
            $type = $configuration['type'];
10✔
162
            if ($type !== 'item' || !$this->canRender($itemName, $type)) {
10✔
163
                continue;
164
            }
165

166
            // Get all languages of current site that are available
167
            // for the current Backend user
168
            $languages = $site->getAvailableLanguages($this->backendUser);
8✔
169

170
            // Remove sites where no XML sitemap is available
171
            if ($itemName === self::ITEM_MODE_SITE) {
8✔
172
                $languages = array_filter(
6✔
173
                    $languages,
6✔
174
                    fn(Core\Site\Entity\SiteLanguage $siteLanguage): bool => $this->canWarmupCachesOfSite($siteLanguage)
6✔
175
                );
6✔
176
            } else {
177
                $languages = array_filter(
8✔
178
                    $languages,
8✔
179
                    fn(Core\Site\Entity\SiteLanguage $siteLanguage): bool => $this->accessGuard->canWarmupCacheOfPage(
8✔
180
                        (int)$this->identifier,
8✔
181
                        $siteLanguage->getLanguageId(),
8✔
182
                    )
8✔
183
                );
8✔
184
            }
185

186
            // Ignore item if no languages are available
187
            if ($languages === []) {
8✔
188
                $this->disabledItems[] = $itemName;
1✔
189
                continue;
1✔
190
            }
191

192
            // Treat current item as submenu
193
            $configuration['type'] = 'submenu';
7✔
194
            $configuration['childItems'] = [];
7✔
195

196
            // Add each site language as child element of the current item
197
            foreach ($languages as $language) {
7✔
198
                $configuration['childItems'][$itemName . '_lang_' . $language->getLanguageId()] = [
7✔
199
                    'label' => $language->getTitle(),
7✔
200
                    'iconIdentifier' => $language->getFlagIdentifier(),
7✔
201
                    'callbackAction' => $configuration['callbackAction'] ?? null,
7✔
202
                ];
7✔
203
            }
204

205
            // Callback action is not required on the parent item
206
            unset($configuration['callbackAction']);
7✔
207
        }
208
    }
209

210
    /**
211
     * @return array<string, mixed>
212
     */
213
    protected function getAdditionalAttributes(string $itemName): array
10✔
214
    {
215
        $attributes = [
10✔
216
            'data-callback-module' => '@eliashaeussler/typo3-warming/backend/context-menu-action',
10✔
217
        ];
10✔
218

219
        // Early return if current item is not part of a submenu
220
        // within the configured context menu items
221
        if (!str_contains($itemName, '_lang_')) {
10✔
222
            return $attributes;
10✔
223
        }
224

225
        [$parentItem, $languageId] = explode('_lang_', $itemName);
7✔
226

227
        // Add site identifier as data attribute
228
        if ($parentItem === self::ITEM_MODE_SITE) {
7✔
229
            $attributes['data-site-identifier'] = $this->getCurrentSite()?->getIdentifier();
5✔
230
        }
231

232
        // Add language ID as data attribute
233
        $attributes['data-language-id'] = (int)$languageId;
7✔
234

235
        return $attributes;
7✔
236
    }
237

238
    private function canWarmupCachesOfSite(?Core\Site\Entity\SiteLanguage $siteLanguage = null): bool
10✔
239
    {
240
        $site = $this->getCurrentSite();
10✔
241
        $languageId = $siteLanguage?->getLanguageId();
10✔
242

243
        if ($site === null ||
10✔
244
            $site->getRootPageId() !== (int)$this->identifier ||
9✔
245
            !$this->accessGuard->canWarmupCacheOfSite($site, $languageId)
8✔
246
        ) {
247
            return false;
3✔
248
        }
249

250
        // Check if any sitemap exists
251
        try {
252
            foreach ($this->sitemapLocator->locateBySite($site, $siteLanguage) as $sitemap) {
7✔
253
                if ($this->sitemapLocator->isValidSitemap($sitemap)) {
7✔
254
                    return true;
6✔
255
                }
256
            }
UNCOV
257
        } catch (\Exception) {
×
258
            // Unable to locate any sitemaps
259
        }
260

261
        return false;
1✔
262
    }
263

264
    private function getCurrentSite(): ?Core\Site\Entity\Site
11✔
265
    {
266
        try {
267
            return $this->siteFinder->getSiteByPageId((int)$this->identifier);
11✔
268
        } catch (Core\Exception\SiteNotFoundException) {
1✔
269
            return null;
1✔
270
        }
271
    }
272
}
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