61dde4787c4efc1fc2e5afed1c31d7c6561f9f00
[Packages/TYPO3.CMS.git] / typo3 / sysext / frontend / Classes / Page / CacheHashCalculator.php
1 <?php
2 namespace TYPO3\CMS\Frontend\Page;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use TYPO3\CMS\Core\SingletonInterface;
18
19 /**
20 * Logic for cHash calculation
21 */
22 class CacheHashCalculator implements SingletonInterface
23 {
24 /**
25 * @var array Parameters that are relevant for cacheHash calculation. Optional.
26 */
27 protected $cachedParametersWhiteList = [];
28
29 /**
30 * @var array Parameters that are not relevant for cacheHash calculation.
31 */
32 protected $excludedParameters = [];
33
34 /**
35 * @var array Parameters that forces a presence of a valid cacheHash.
36 */
37 protected $requireCacheHashPresenceParameters = [];
38
39 /**
40 * @var array Parameters that need a value to be relevant for cacheHash calculation
41 */
42 protected $excludedParametersIfEmpty = [];
43
44 /**
45 * @var bool Whether to exclude all empty parameters for cacheHash calculation
46 */
47 protected $excludeAllEmptyParameters = false;
48
49 /**
50 * @var bool
51 */
52 protected $includePageId = false;
53
54 /**
55 * Initialise class properties by using the relevant TYPO3 configuration
56 */
57 public function __construct()
58 {
59 $this->setConfiguration($GLOBALS['TYPO3_CONF_VARS']['FE']['cacheHash']);
60 }
61
62 /**
63 * Calculates the cHash based on the provided parameters
64 *
65 * @param array $params Array of cHash key-value pairs
66 * @return string Hash of all the values
67 */
68 public function calculateCacheHash(array $params)
69 {
70 return !empty($params) ? md5(serialize($params)) : '';
71 }
72
73 /**
74 * Returns the cHash based on provided query parameters and added values from internal call
75 *
76 * @param string $queryString Query-parameters: "&xxx=yyy&zzz=uuu
77 * @return string Hash of all the values
78 * @throws \RuntimeException
79 */
80 public function generateForParameters($queryString)
81 {
82 $cacheHashParams = $this->getRelevantParameters($queryString);
83 return $this->calculateCacheHash($cacheHashParams);
84 }
85
86 /**
87 * Checks whether a parameter of the given $queryString requires cHash calculation
88 *
89 * @param string $queryString
90 * @return bool
91 */
92 public function doParametersRequireCacheHash($queryString)
93 {
94 if (empty($this->requireCacheHashPresenceParameters)) {
95 return false;
96 }
97 $hasRequiredParameter = false;
98 $parameterNames = array_keys($this->splitQueryStringToArray($queryString));
99 foreach ($parameterNames as $parameterName) {
100 if (in_array($parameterName, $this->requireCacheHashPresenceParameters, true)) {
101 $hasRequiredParameter = true;
102 break;
103 }
104 }
105 return $hasRequiredParameter;
106 }
107
108 /**
109 * Splits the input query-parameters into an array with certain parameters filtered out.
110 * Used to create the cHash value
111 *
112 * @param string $queryString Query-parameters: "&xxx=yyy&zzz=uuu
113 * @return array Array with key/value pairs of query-parameters WITHOUT a certain list of
114 * @throws \RuntimeException
115 * @see \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController::makeCacheHash(), \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer::typoLink()
116 */
117 public function getRelevantParameters($queryString)
118 {
119 $parameters = $this->splitQueryStringToArray($queryString);
120 $relevantParameters = [];
121 foreach ($parameters as $parameterName => $parameterValue) {
122 if ($this->isAdminPanelParameter($parameterName) || $this->isExcludedParameter($parameterName) || $this->isCoreParameter($parameterName)) {
123 continue;
124 }
125 if ($this->hasCachedParametersWhiteList() && !$this->isInCachedParametersWhiteList($parameterName)) {
126 continue;
127 }
128 if (($parameterValue === null || $parameterValue === '') && !$this->isAllowedWithEmptyValue($parameterName)) {
129 continue;
130 }
131 $relevantParameters[$parameterName] = $parameterValue;
132 }
133 if (!empty($relevantParameters)) {
134 if ($this->includePageId) {
135 if (empty($parameters['id'])) {
136 throw new \RuntimeException('ID parameter needs to be passed for the cHash calculation! As a temporary not recommended workaround, you can set $GLOBALS[\'TYPO3_CONF_VARS\'][\'FE\'][\'cHashIncludePageId\'] to false to avoid this error.', 1467983513);
137 }
138 $relevantParameters['id'] = $parameters['id'];
139 }
140 // Finish and sort parameters array by keys:
141 $relevantParameters['encryptionKey'] = $GLOBALS['TYPO3_CONF_VARS']['SYS']['encryptionKey'];
142 ksort($relevantParameters);
143 }
144 return $relevantParameters;
145 }
146
147 /**
148 * Parses the query string and converts it to an array.
149 * Unlike parse_str it only creates an array with one level.
150 *
151 * e.g. foo[bar]=baz will be array('foo[bar]' => 'baz')
152 *
153 * @param string $queryString
154 * @return array
155 */
156 protected function splitQueryStringToArray($queryString)
157 {
158 $parameters = array_filter(explode('&', ltrim($queryString, '?')));
159 $parameterArray = [];
160 foreach ($parameters as $parameter) {
161 list($parameterName, $parameterValue) = explode('=', $parameter);
162 if (trim($parameterName) === '') {
163 // This parameter cannot appear in $_GET in PHP even if its value is not empty, so it should be ignored!
164 continue;
165 }
166 $parameterArray[rawurldecode($parameterName)] = rawurldecode($parameterValue);
167 }
168 return $parameterArray;
169 }
170
171 /**
172 * Checks whether the given parameter starts with TSFE_ADMIN_PANEL
173 * stripos check added to avoid bad performance
174 *
175 * @param string $key
176 * @return bool
177 */
178 protected function isAdminPanelParameter($key)
179 {
180 return stripos($key, 'TSFE_ADMIN_PANEL') !== false && preg_match('/TSFE_ADMIN_PANEL\\[.*?\\]/', $key);
181 }
182
183 /**
184 * Checks whether the given parameter is a core parameter
185 *
186 * @param string $key
187 * @return bool
188 */
189 protected function isCoreParameter($key)
190 {
191 return $key === 'id' || $key === 'type' || $key === 'no_cache' || $key === 'cHash' || $key === 'MP' || $key === 'ftu';
192 }
193
194 /**
195 * Checks whether the given parameter should be excluded from cHash calculation
196 *
197 * @param string $key
198 * @return bool
199 */
200 protected function isExcludedParameter($key)
201 {
202 return in_array($key, $this->excludedParameters, true);
203 }
204
205 /**
206 * Checks whether the given parameter is an exclusive parameter for cHash calculation
207 *
208 * @param string $key
209 * @return bool
210 */
211 protected function isInCachedParametersWhiteList($key)
212 {
213 return in_array($key, $this->cachedParametersWhiteList, true);
214 }
215
216 /**
217 * Checks whether cachedParametersWhiteList parameters are configured
218 *
219 * @return bool
220 */
221 protected function hasCachedParametersWhiteList()
222 {
223 return !empty($this->cachedParametersWhiteList);
224 }
225
226 /**
227 * Check whether the given parameter may be used even with an empty value
228 *
229 * @param $key
230 * @return bool
231 */
232 protected function isAllowedWithEmptyValue($key)
233 {
234 return !($this->excludeAllEmptyParameters || in_array($key, $this->excludedParametersIfEmpty, true));
235 }
236
237 /**
238 * Loops through the configuration array and calls the accordant
239 * getters with the value.
240 *
241 * @param array $configuration
242 */
243 public function setConfiguration(array $configuration)
244 {
245 foreach ($configuration as $name => $value) {
246 $setterName = 'set' . ucfirst($name);
247 if (method_exists($this, $setterName)) {
248 $this->{$setterName}($value);
249 }
250 }
251 }
252
253 /**
254 * @param array $cachedParametersWhiteList
255 */
256 protected function setCachedParametersWhiteList(array $cachedParametersWhiteList)
257 {
258 $this->cachedParametersWhiteList = $cachedParametersWhiteList;
259 }
260
261 /**
262 * @param bool $includePageId
263 */
264 protected function setIncludePageId($includePageId)
265 {
266 $this->includePageId = $includePageId;
267 }
268
269 /**
270 * @param bool $excludeAllEmptyParameters
271 */
272 protected function setExcludeAllEmptyParameters($excludeAllEmptyParameters)
273 {
274 $this->excludeAllEmptyParameters = $excludeAllEmptyParameters;
275 }
276
277 /**
278 * @param array $excludedParameters
279 */
280 protected function setExcludedParameters(array $excludedParameters)
281 {
282 $this->excludedParameters = $excludedParameters;
283 }
284
285 /**
286 * @param array $excludedParametersIfEmpty
287 */
288 protected function setExcludedParametersIfEmpty(array $excludedParametersIfEmpty)
289 {
290 $this->excludedParametersIfEmpty = $excludedParametersIfEmpty;
291 }
292
293 /**
294 * @param array $requireCacheHashPresenceParameters
295 */
296 protected function setRequireCacheHashPresenceParameters(array $requireCacheHashPresenceParameters)
297 {
298 $this->requireCacheHashPresenceParameters = $requireCacheHashPresenceParameters;
299 }
300 }