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

FluidTYPO3 / flux / 12930151005

23 Jan 2025 01:23PM UTC coverage: 92.829% (-0.07%) from 92.901%
12930151005

Pull #2209

github

web-flow
Merge 510fe665a into cf49f7a79
Pull Request #2209: [WIP] Compatibility with TYPO3 v13

86 of 112 new or added lines in 31 files covered. (76.79%)

5 existing lines in 3 files now uncovered.

7055 of 7600 relevant lines covered (92.83%)

65.02 hits per line

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

82.58
/Classes/Service/PageService.php
1
<?php
2
namespace FluidTYPO3\Flux\Service;
3

4
/*
5
 * This file is part of the FluidTYPO3/Flux project under GPLv2 or later.
6
 *
7
 * For the full copyright and license information, please read the
8
 * LICENSE.md file that was distributed with this source code.
9
 */
10

11
use FluidTYPO3\Flux\Content\TypeDefinition\FluidFileBased\DropInContentTypeDefinition;
12
use FluidTYPO3\Flux\Core;
13
use FluidTYPO3\Flux\Enum\ExtensionOption;
14
use FluidTYPO3\Flux\Enum\FormOption;
15
use FluidTYPO3\Flux\Form;
16
use FluidTYPO3\Flux\Provider\PageProvider;
17
use FluidTYPO3\Flux\Proxy\TemplatePathsProxy;
18
use FluidTYPO3\Flux\Utility\ExtensionConfigurationUtility;
19
use FluidTYPO3\Flux\Utility\ExtensionNamingUtility;
20
use FluidTYPO3\Flux\ViewHelpers\FormViewHelper;
21
use Psr\Log\LoggerAwareInterface;
22
use Psr\Log\LoggerAwareTrait;
23
use Psr\Log\LoggerInterface;
24
use Symfony\Component\Finder\Finder;
25
use TYPO3\CMS\Core\Cache\CacheManager;
26
use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface;
27
use TYPO3\CMS\Core\SingletonInterface;
28
use TYPO3\CMS\Core\Utility\GeneralUtility;
29
use TYPO3\CMS\Core\Utility\RootlineUtility;
30
use TYPO3\CMS\Fluid\View\TemplatePaths;
31
use TYPO3\CMS\Fluid\View\TemplateView;
32
use TYPO3Fluid\Fluid\Component\Error\ChildNotFoundException;
33
use TYPO3Fluid\Fluid\View\Exception\InvalidSectionException;
34
use TYPO3Fluid\Fluid\View\ViewInterface;
35

36
/**
37
 * Page Service
38
 *
39
 * Service for interacting with Pages - gets content elements and page configuration
40
 * options.
41
 */
42
class PageService implements SingletonInterface, LoggerAwareInterface
43
{
44
    use LoggerAwareTrait;
45

46
    protected WorkspacesAwareRecordService $workspacesAwareRecordService;
47
    protected FrontendInterface $runtimeCache;
48

49
    public function __construct(
50
        WorkspacesAwareRecordService $recordService,
51
        CacheManager $cacheManager
52
    ) {
53
        $this->workspacesAwareRecordService = $recordService;
×
54
        $this->runtimeCache = $cacheManager->getCache('runtime');
×
55
    }
56

57
    /**
58
     * Process RootLine to find first usable, configured Fluid Page Template.
59
     * WARNING: do NOT use the output of this feature to overwrite $row - the
60
     * record returned may or may not be the same record as defined in $id.
61
     *
62
     * @api
63
     */
64
    public function getPageTemplateConfiguration(int $pageUid, bool $pageUidIsParentUid = false): ?array
65
    {
66
        $pageUid = (integer) $pageUid;
35✔
67
        if (!$pageUid) {
35✔
68
            return null;
7✔
69
        }
70
        $cacheId = 'flux-template-configuration-' . $pageUid;
28✔
71
        /** @var array|null $fromCache */
72
        $fromCache = $this->runtimeCache->get($cacheId);
28✔
73
        if ($fromCache) {
28✔
74
            return $fromCache;
×
75
        }
76

77
        $resolvedMainTemplateIdentity = null;
28✔
78
        $resolvedSubTemplateIdentity = null;
28✔
79
        $recordDefiningMain = null;
28✔
80
        $recordDefiningSub = null;
28✔
81
        $rootLine = $this->getRootLine($pageUid);
28✔
82

83
        // Initialize with possibly-empty values and loop root line
84
        // to fill values as they are detected.
85
        foreach ($rootLine as $page) {
28✔
86
            $rootLinePageUid = (integer) ($page['uid'] ?? 0);
28✔
87
            $mainFieldValue = $page[PageProvider::FIELD_ACTION_MAIN] ?? null;
28✔
88
            $subFieldValue = $page[PageProvider::FIELD_ACTION_SUB] ?? null;
28✔
89
            $resolvedMainTemplateIdentity = is_array($mainFieldValue) ? $mainFieldValue[0] : $mainFieldValue;
28✔
90
            $resolvedSubTemplateIdentity = is_array($subFieldValue) ? $subFieldValue[0] : $subFieldValue;
28✔
91
            $containsSubDefinition = (strpos($subFieldValue ?? '', '->') !== false);
28✔
92
            $isCandidate = $pageUidIsParentUid ? true : $rootLinePageUid !== $pageUid;
28✔
93
            if ($containsSubDefinition && $isCandidate) {
28✔
94
                $resolvedSubTemplateIdentity = $subFieldValue;
14✔
95
                $recordDefiningSub = $page;
14✔
96
                if (empty($resolvedMainTemplateIdentity)) {
14✔
97
                    // Conditions met: current page is not $pageUid, original page did not
98
                    // contain a "this page" layout, current rootline page has "sub" selection.
99
                    // Then, set our "this page" value to use the "sub" selection that was detected.
100
                    $resolvedMainTemplateIdentity = $resolvedSubTemplateIdentity;
7✔
101
                    $recordDefiningMain = $page;
7✔
102
                }
103
                break;
14✔
104
            }
105
        };
106
        if (empty($resolvedMainTemplateIdentity) && empty($resolvedSubTemplateIdentity)) {
28✔
107
            // Neither directly configured "this page" nor inherited "sub" contains a valid value;
108
            // no configuration was detected at all.
109
            return null;
14✔
110
        }
111
        $configuration = [
14✔
112
            'tx_fed_page_controller_action' => $resolvedMainTemplateIdentity,
14✔
113
            'tx_fed_page_controller_action_sub' => $resolvedSubTemplateIdentity,
14✔
114
            'record_main' => $recordDefiningMain,
14✔
115
            'record_sub' => $recordDefiningSub,
14✔
116
        ];
14✔
117
        $this->runtimeCache->set($cacheId, $configuration);
14✔
118
        return $configuration;
14✔
119
    }
120

121
    /**
122
     * Get a usable page configuration flexform from rootline
123
     *
124
     * @api
125
     */
126
    public function getPageFlexFormSource(int $pageUid): ?string
127
    {
128
        $pageUid = (integer) $pageUid;
14✔
129
        if (!$pageUid) {
14✔
130
            return null;
7✔
131
        }
132
        $fieldList = 'uid,pid,t3ver_oid,tx_fed_page_flexform';
7✔
133
        $page = $this->workspacesAwareRecordService->getSingle('pages', $fieldList, $pageUid);
7✔
134
        while ($page !== null && 0 !== (integer) $page['uid'] && empty($page['tx_fed_page_flexform'])) {
7✔
135
            $resolveParentPageUid = (integer) (0 > $page['pid'] ? $page['t3ver_oid'] : $page['pid']);
7✔
136
            $page = $this->workspacesAwareRecordService->getSingle('pages', $fieldList, $resolveParentPageUid);
7✔
137
        }
138
        return $page['tx_fed_page_flexform'] ?? null;
7✔
139
    }
140

141
    public function getPageConfiguration(?string $extensionName = null): array
142
    {
143
        if (null !== $extensionName && true === empty($extensionName)) {
49✔
144
            // Note: a NULL extensionName means "fetch ALL defined collections" whereas
145
            // an empty value that is not null indicates an incorrect caller. Instead
146
            // of returning ALL paths here, an empty array is the proper return value.
147
            // However, dispatch a debug message to inform integrators of the problem.
148
            if ($this->logger instanceof LoggerInterface) {
14✔
149
                $this->logger->log(
×
150
                    'notice',
×
151
                    'Template paths have been attempted fetched using an empty value that is NOT NULL in ' .
×
152
                    get_class($this) . '. This indicates a potential problem with your TypoScript configuration - a ' .
×
153
                    'value which is expected to be an array may be defined as a string. This error is not fatal but ' .
×
154
                    'may prevent the affected collection (which cannot be identified here) from showing up'
×
155
                );
×
156
            }
157
            return [];
14✔
158
        }
159

160
        $plugAndPlayEnabled = ExtensionConfigurationUtility::getOption(
35✔
161
            ExtensionOption::OPTION_PLUG_AND_PLAY
35✔
162
        );
35✔
163
        $plugAndPlayDirectory = ExtensionConfigurationUtility::getOption(
35✔
164
            ExtensionOption::OPTION_PLUG_AND_PLAY_DIRECTORY
35✔
165
        );
35✔
166
        if (!is_scalar($plugAndPlayDirectory)) {
35✔
167
            return [];
7✔
168
        }
169
        $plugAndPlayTemplatesDirectory = trim((string) $plugAndPlayDirectory, '/.') . '/';
28✔
170
        if ($plugAndPlayEnabled && $extensionName === 'Flux') {
28✔
171
            return [
7✔
172
                TemplatePaths::CONFIG_TEMPLATEROOTPATHS => [
7✔
173
                    $plugAndPlayTemplatesDirectory
7✔
174
                    . DropInContentTypeDefinition::TEMPLATES_DIRECTORY
7✔
175
                    . DropInContentTypeDefinition::PAGE_DIRECTORY
7✔
176
                ],
7✔
177
                TemplatePaths::CONFIG_PARTIALROOTPATHS => [
7✔
178
                    $plugAndPlayTemplatesDirectory . DropInContentTypeDefinition::PARTIALS_DIRECTORY
7✔
179
                ],
7✔
180
                TemplatePaths::CONFIG_LAYOUTROOTPATHS => [
7✔
181
                    $plugAndPlayTemplatesDirectory . DropInContentTypeDefinition::LAYOUTS_DIRECTORY
7✔
182
                ],
7✔
183
            ];
7✔
184
        }
185
        if (null !== $extensionName) {
21✔
186
            $templatePaths = $this->createTemplatePaths($extensionName);
7✔
187
            return TemplatePathsProxy::toArray($templatePaths);
7✔
188
        }
189
        $configurations = [];
14✔
190
        $registeredExtensionKeys = Core::getRegisteredProviderExtensionKeys('Page');
14✔
191
        foreach ($registeredExtensionKeys as $registeredExtensionKey) {
14✔
192
            $templatePaths = $this->createTemplatePaths($registeredExtensionKey);
14✔
193
            $configurations[$registeredExtensionKey] = TemplatePathsProxy::toArray($templatePaths);
14✔
194
        }
195
        if ($plugAndPlayEnabled) {
14✔
196
            $configurations['FluidTYPO3.Flux'] = array_replace(
7✔
197
                $configurations['FluidTYPO3.Flux'] ?? [],
7✔
198
                [
7✔
199
                    TemplatePaths::CONFIG_TEMPLATEROOTPATHS => [
7✔
200
                        $plugAndPlayTemplatesDirectory
7✔
201
                        . DropInContentTypeDefinition::TEMPLATES_DIRECTORY
7✔
202
                        . DropInContentTypeDefinition::PAGE_DIRECTORY
7✔
203
                    ],
7✔
204
                    TemplatePaths::CONFIG_PARTIALROOTPATHS => [
7✔
205
                        $plugAndPlayTemplatesDirectory . DropInContentTypeDefinition::PARTIALS_DIRECTORY
7✔
206
                    ],
7✔
207
                    TemplatePaths::CONFIG_LAYOUTROOTPATHS => [
7✔
208
                        $plugAndPlayTemplatesDirectory . DropInContentTypeDefinition::LAYOUTS_DIRECTORY
7✔
209
                    ],
7✔
210
                ]
7✔
211
            );
7✔
212
        }
213
        return $configurations;
14✔
214
    }
215

216
    /**
217
     * Gets a list of usable Page Templates from defined page template TypoScript.
218
     * Returns a list of Form instances indexed by the path ot the template file.
219
     *
220
     * @return Form[][]
221
     * @api
222
     */
223
    public function getAvailablePageTemplateFiles(): array
224
    {
225
        $cacheKey = 'page_templates';
35✔
226
        /** @var array|null $fromCache */
227
        $fromCache = $this->runtimeCache->get($cacheKey);
35✔
228
        if ($fromCache) {
35✔
229
            return $fromCache;
×
230
        }
231
        $typoScript = $this->getPageConfiguration();
35✔
232
        $output = [];
35✔
233

234
        foreach ((array) $typoScript as $extensionName => $group) {
35✔
235
            if (!($group['enable'] ?? true)) {
28✔
236
                continue;
7✔
237
            }
238
            $output[$extensionName] = [];
21✔
239
            $templatePaths = $this->createTemplatePaths($extensionName);
21✔
240
            $finder = Finder::create()->in($templatePaths->getTemplateRootPaths())->name('*.html')->sortByName();
21✔
241
            foreach ($finder->files() as $file) {
21✔
242
                /** @var \SplFileInfo $file */
243
                if (substr($file->getBasename(), 0, 1) === '.') {
21✔
244
                    continue;
×
245
                }
246
                if (strpos($file->getPath(), DIRECTORY_SEPARATOR . 'Page') === false) {
21✔
247
                    continue;
21✔
248
                }
249
                $filename = $file->getRelativePathname();
21✔
250
                if (isset($output[$extensionName][$filename])) {
21✔
251
                    continue;
×
252
                }
253

254
                $view = $this->createViewInstance($extensionName, $templatePaths, $file);
21✔
255
                try {
256
                    $view->renderSection('Configuration');
21✔
257
                    $form = $view->getRenderingContext()
21✔
258
                        ->getViewHelperVariableContainer()
21✔
259
                        ->get(FormViewHelper::class, 'form');
21✔
260

261
                    if (!$form instanceof Form) {
21✔
262
                        if ($this->logger instanceof LoggerInterface) {
21✔
263
                            $this->logger->log(
21✔
264
                                'error',
21✔
265
                                'Template file ' . $file . ' contains an unparsable Form definition'
21✔
266
                            );
21✔
267
                        }
268
                        continue;
21✔
269
                    } elseif (!$form->getEnabled()) {
×
270
                        if ($this->logger instanceof LoggerInterface) {
×
271
                            $this->logger->log(
×
272
                                'notice',
×
273
                                'Template file ' . $file . ' is disabled by configuration'
×
274
                            );
×
275
                        }
276
                        continue;
×
277
                    }
278
                    $form->setOption(FormOption::TEMPLATE_FILE, $file->getPathname());
×
279
                    $form->setOption(FormOption::TEMPLATE_FILE_RELATIVE, substr($file->getRelativePathname(), 5, -5));
×
280
                    $form->setExtensionName($extensionName);
×
281
                    $output[$extensionName][$filename] = $form;
×
UNCOV
282
                } catch (InvalidSectionException | ChildNotFoundException $error) {
×
UNCOV
283
                    if ($this->logger instanceof LoggerInterface) {
×
UNCOV
284
                        $this->logger->log('error', $error->getMessage());
×
285
                    }
286
                }
287
            }
288
        }
289
        $this->runtimeCache->set($cacheKey, $output);
35✔
290
        return $output;
35✔
291
    }
292

293
    /**
294
     * @codeCoverageIgnore
295
     */
296
    protected function createTemplatePaths(string $registeredExtensionKey): TemplatePaths
297
    {
298
        /** @var TemplatePaths $templatePaths */
299
        $templatePaths = GeneralUtility::makeInstance(
300
            TemplatePaths::class,
301
            ExtensionNamingUtility::getExtensionKey($registeredExtensionKey)
302
        );
303
        return $templatePaths;
304
    }
305

306
    /**
307
     * @codeCoverageIgnore
308
     */
309
    protected function createViewInstance(
310
        string $extensionName,
311
        TemplatePaths $templatePaths,
312
        \SplFileInfo $file
313
    ): ViewInterface {
314
        /** @var TemplateView $view */
315
        $view = GeneralUtility::makeInstance(TemplateView::class);
316
        $view->getRenderingContext()->setTemplatePaths($templatePaths);
317
        $view->getRenderingContext()->getViewHelperVariableContainer()->addOrUpdate(
318
            FormViewHelper::SCOPE,
319
            FormViewHelper::SCOPE_VARIABLE_EXTENSIONNAME,
320
            $extensionName
321
        );
322
        $templatePaths->setTemplatePathAndFilename($file->getPathname());
323
        return $view;
324
    }
325

326
    /**
327
     * @codeCoverageIgnore
328
     */
329
    protected function getRootLine(int $pageUid): array
330
    {
331
        /** @var RootlineUtility $rootLineUtility */
332
        $rootLineUtility = GeneralUtility::makeInstance(RootlineUtility::class, $pageUid);
333
        return $rootLineUtility->get();
334
    }
335
}
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