Completed
Push — master ( 5434e6...3e57ee )
by
unknown
22:28 queued 08:05
created

PageTsConfigParser::parse()   C

Complexity

Conditions 12
Paths 10

Size

Total Lines 62
Code Lines 38

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 12
eloc 38
c 0
b 0
f 0
nc 10
nop 3
dl 0
loc 62
rs 6.9666

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the TYPO3 CMS project.
7
 *
8
 * It is free software; you can redistribute it and/or modify it under
9
 * the terms of the GNU General Public License, either version 2
10
 * of the License, or any later version.
11
 *
12
 * For the full copyright and license information, please read the
13
 * LICENSE.txt file that was distributed with this source code.
14
 *
15
 * The TYPO3 project - inspiring people to share!
16
 */
17
18
namespace TYPO3\CMS\Core\Configuration\Parser;
19
20
use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface;
21
use TYPO3\CMS\Core\Configuration\TypoScript\ConditionMatching\ConditionMatcherInterface;
22
use TYPO3\CMS\Core\Site\Entity\Site;
23
use TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser;
24
use TYPO3\CMS\Core\Utility\ArrayUtility;
25
26
/**
27
 * A TS-Config parsing class which performs condition evaluation.
28
 *
29
 * This class does parsing of a compiled TSconfig string, and applies matching() based on the
30
 * Context (FE or BE) in it, allowing to be fully agnostic to the outside world.
31
 */
32
class PageTsConfigParser
33
{
34
    /**
35
     * @var TypoScriptParser
36
     */
37
    protected $typoScriptParser;
38
39
    /**
40
     * @var FrontendInterface
41
     */
42
    protected $cache;
43
44
    public function __construct(TypoScriptParser $typoScriptParser, FrontendInterface $cache)
45
    {
46
        $this->typoScriptParser = $typoScriptParser;
47
        $this->cache = $cache;
48
    }
49
50
    /**
51
     * Parses and matches a given string
52
     * Adds entries to the cache:
53
     * - when an exact on the conditions are there
54
     * - when a parse is there, then matches are happening anyway, and it is checked if this can be cached as well.
55
     *
56
     * If a site is provided the settings stored in the site's configuration is available as constants for the TSconfig.
57
     *
58
     * @param string $content pageTSconfig, usually accumulated by the PageTsConfigLoader
59
     * @param ConditionMatcherInterface $matcher an instance to match strings
60
     * @param Site|null $site The current site the page TSconfig is parsed for
61
     * @return array the
62
     */
63
    public function parse(string $content, ConditionMatcherInterface $matcher, ?Site $site = null): array
64
    {
65
        $hashOfContent = md5('PAGES:' . $content);
66
        $cachedContent = $this->cache->get($hashOfContent);
67
        // Something about this content has been cached before, lets verify the matchings, if they also apply
68
        if (is_array($cachedContent) && is_array($cachedContent[0])) {
69
            // Cache entry found, see if the "matching" section matches with the matcher
70
            $storedData = $cachedContent[0];
71
            $storedMD5 = $cachedContent[1];
72
            $storedData['match'] = $this->matching($storedData['sections'] ?? [], $matcher);
73
            $hashOfDataWithMatches = md5(json_encode($storedData));
74
            // The matches are the same, so nothing to do here
75
            if ($hashOfDataWithMatches === $storedMD5) {
76
                $result = $storedData['TSconfig'];
77
            } else {
78
                // Create a hash out of the content-hash PLUS the matching information and try again
79
                $shash = md5($hashOfDataWithMatches . $hashOfContent);
80
                $storedData = $this->cache->get($shash);
81
                if (is_array($storedData)) {
82
                    $result = $storedData['TSconfig'];
83
                } else {
84
                    // Create a new content with the matcher, and cache it as a new entry
85
                    $parsedAndMatchedData = $this->parseAndMatch($content, $matcher);
86
                    // Now store the full data from the parser (with matches)
87
                    $this->cache->set($shash, $parsedAndMatchedData, ['pageTSconfig'], 0);
88
                    $result = $parsedAndMatchedData['TSconfig'];
89
                }
90
            }
91
            return $result;
92
        }
93
94
        if ($site) {
95
            $siteSettings = $site->getConfiguration()['settings'] ?? [];
96
            if (!empty($siteSettings)) {
97
                $siteSettings = ArrayUtility::flatten($siteSettings);
98
            }
99
            if (!empty($siteSettings)) {
100
                // Recursive substitution of site settings (up to 10 nested levels)
101
                // note: this code is more or less a duplicate of \TYPO3\CMS\Core\TypoScript\TemplateService::substituteConstants
102
                for ($i = 0; $i < 10; $i++) {
103
                    $beforeSubstitution = $content;
104
                    $content = preg_replace_callback(
105
                        '/\\{\\$(.[^}]*)\\}/',
106
                        function (array $matches) use ($siteSettings): string {
107
                            return isset($siteSettings[$matches[1]]) && !is_array($siteSettings[$matches[1]])
108
                                ? (string)$siteSettings[$matches[1]] : $matches[0];
0 ignored issues
show
Coding Style introduced by
Expected 1 space before "?"; newline found
Loading history...
109
                        },
110
                        $content
111
                    );
112
                    if ($beforeSubstitution === $content) {
113
                        break;
114
                    }
115
                }
116
            }
117
        }
118
119
        // Nothing found in cache for this content string, let's do everything.
120
        $parsedAndMatchedData = $this->parseAndMatch($content, $matcher);
121
        // ALL parts, including the matching part is cached.
122
        $md5 = md5(json_encode($parsedAndMatchedData));
123
        $this->cache->set($hashOfContent, [$parsedAndMatchedData, $md5], ['pageTSconfig'], 0);
124
        return $parsedAndMatchedData['TSconfig'];
125
    }
126
127
    /**
128
     * Does the actual parsing using the TypoScriptParser "parse" method by applying a condition matcher.
129
     *
130
     * @param string $content The TSConfig being parsed
131
     * @param ConditionMatcherInterface $matcher
132
     * @return array Array containing the parsed TSConfig, the encountered sections, the matched sections. This is stored in cache.
133
     */
134
    protected function parseAndMatch(string $content, ConditionMatcherInterface $matcher): array
135
    {
136
        $this->typoScriptParser->parse($content, $matcher);
137
        return [
138
            'TSconfig' => $this->typoScriptParser->setup,
139
            'sections' => $this->typoScriptParser->sections,
140
            'match' => $this->typoScriptParser->sectionsMatch
141
        ];
142
    }
143
144
    /**
145
     * Is just going through an array of conditions to determine which are matching (for getting correct cache entry)
146
     *
147
     * @param array $sectionsToMatch An array containing the sections to match
148
     * @param ConditionMatcherInterface $matcher
149
     * @return array The input array with matching sections to be filled into the "match" key
150
     */
151
    protected function matching(array $sectionsToMatch, ConditionMatcherInterface $matcher): array
152
    {
153
        $matches = [];
154
        foreach ($sectionsToMatch ?? [] as $key => $pre) {
155
            if ($matcher->match($pre)) {
156
                $matches[$key] = $pre;
157
            }
158
        }
159
        return $matches;
160
    }
161
}
162