[BUGFIX] Call fullQuoteStr with table argument in RootlineUtility
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / Utility / RootlineUtility.php
1 <?php
2 namespace TYPO3\CMS\Core\Utility;
3
4 /***************************************************************
5 * Copyright notice
6 *
7 * (c) 2012 Steffen Ritter <steffen.ritter@typo3.org>
8 * All rights reserved
9 *
10 * This script is part of the TYPO3 project. The TYPO3 project is
11 * free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
15 *
16 * The GNU General Public License can be found at
17 * http://www.gnu.org/copyleft/gpl.html.
18 *
19 * This script is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
23 *
24 * This copyright notice MUST APPEAR in all copies of the script!
25 ***************************************************************/
26 /**
27 * A utility resolving and Caching the Rootline generation
28 *
29 * @author Steffen Ritter <steffen.ritter@typo3.org>
30 * @package TYPO3
31 * @subpackage t3lib
32 */
33 class RootlineUtility {
34
35 /**
36 * @var integer
37 */
38 protected $pageUid;
39
40 /**
41 * @var string
42 */
43 protected $mountPointParameter;
44
45 /**
46 * @var array
47 */
48 protected $parsedMountPointParameters = array();
49
50 /**
51 * @var integer
52 */
53 protected $languageUid = 0;
54
55 /**
56 * @var integer
57 */
58 protected $workspaceUid = 0;
59
60 /**
61 * @var boolean
62 */
63 protected $versionPreview = FALSE;
64
65 /**
66 * @var \TYPO3\CMS\Core\Cache\Frontend\FrontendInterface
67 */
68 static protected $cache = NULL;
69
70 /**
71 * @var array
72 */
73 static protected $localCache = array();
74
75 /**
76 * Fields to fetch when populating rootline data
77 *
78 * @var array
79 */
80 static protected $rootlineFields = array(
81 'pid',
82 'uid',
83 't3ver_oid',
84 't3ver_wsid',
85 't3ver_state',
86 'title',
87 'alias',
88 'nav_title',
89 'media',
90 'layout',
91 'hidden',
92 'starttime',
93 'endtime',
94 'fe_group',
95 'extendToSubpages',
96 'doktype',
97 'TSconfig',
98 'storage_pid',
99 'is_siteroot',
100 'mount_pid',
101 'mount_pid_ol',
102 'fe_login_mode',
103 'backend_layout_next_level'
104 );
105
106 /**
107 * Rootline Context
108 *
109 * @var \TYPO3\CMS\Frontend\Page\PageRepository
110 */
111 protected $pageContext;
112
113 /**
114 * @var array
115 */
116 static protected $pageRecordCache = array();
117
118 /**
119 * @param int $uid
120 * @param string $mountPointParameter
121 * @param \TYPO3\CMS\Frontend\Page\PageRepository $context
122 * @throws RuntimeException
123 */
124 public function __construct($uid, $mountPointParameter = '', \TYPO3\CMS\Frontend\Page\PageRepository $context = NULL) {
125 $this->pageUid = intval($uid);
126 $this->mountPointParameter = trim($mountPointParameter);
127 if ($context === NULL) {
128 if ($GLOBALS['TSFE']->sys_page !== NULL) {
129 $this->pageContext = $GLOBALS['TSFE']->sys_page;
130 } else {
131 $this->pageContext = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Frontend\\Page\\PageRepository');
132 }
133 } else {
134 $this->pageContext = $context;
135 }
136 $this->initializeObject();
137 }
138
139 /**
140 * Initialize a state to work with
141 *
142 * @throws RuntimeException
143 * @return void
144 */
145 protected function initializeObject() {
146 $this->languageUid = intval($this->pageContext->sys_language_uid);
147 $this->workspaceUid = intval($this->pageContext->versioningWorkspaceId);
148 $this->versionPreview = $this->pageContext->versioningPreview;
149 if ($this->mountPointParameter !== '') {
150 if (!$GLOBALS['TYPO3_CONF_VARS']['FE']['enable_mount_pids']) {
151 throw new \RuntimeException('Mount-Point Pages are disabled for this installation. Cannot resolve a Rootline for a page with Mount-Points', 1343462896);
152 } else {
153 $this->parseMountPointParameter();
154 }
155 }
156 if (self::$cache === NULL) {
157 self::$cache = $GLOBALS['typo3CacheManager']->getCache('cache_rootline');
158 }
159 self::$rootlineFields = array_merge(self::$rootlineFields, \TYPO3\CMS\Core\Utility\GeneralUtility::trimExplode(',', $GLOBALS['TYPO3_CONF_VARS']['FE']['addRootLineFields'], TRUE));
160 array_unique(self::$rootlineFields);
161 }
162
163 /**
164 * Constructs the cache Identifier
165 *
166 * @param integer $otherUid
167 * @return string
168 */
169 public function getCacheIdentifier($otherUid = NULL) {
170 return implode('_', array(
171 $otherUid !== NULL ? intval($otherUid) : $this->pageUid,
172 $this->mountPointParameter,
173 $this->languageUid,
174 $this->workspaceUid,
175 $this->versionPreview ? 1 : 0
176 ));
177 }
178
179 /**
180 * Returns the actual rootline
181 *
182 * @return array
183 */
184 public function get() {
185 $cacheIdentifier = $this->getCacheIdentifier();
186 if (!isset(self::$localCache[$cacheIdentifier])) {
187 if (!self::$cache->has($cacheIdentifier)) {
188 $this->generateRootlineCache();
189 } else {
190 self::$localCache[$cacheIdentifier] = self::$cache->get($cacheIdentifier);
191 }
192 }
193 return self::$localCache[$cacheIdentifier];
194 }
195
196 /**
197 * Queries the database for the page record and returns it.
198 *
199 * @param integer $uid Page id
200 * @throws RuntimeException
201 * @return array
202 */
203 protected function getRecordArray($uid) {
204 if (!isset(self::$pageRecordCache[$this->getCacheIdentifier($uid)])) {
205 if (!is_array($GLOBALS['TCA']['pages']['columns'])) {
206 if (isset($GLOBALS['TSFE'])) {
207 $GLOBALS['TSFE']->includeTCA($GLOBALS['TSFE']->TCAloaded);
208 }
209 \TYPO3\CMS\Core\Utility\GeneralUtility::loadTCA('pages');
210 }
211 $row = $GLOBALS['TYPO3_DB']->exec_SELECTgetSingleRow(implode(',', self::$rootlineFields), 'pages', (('uid = ' . intval($uid)) . ' AND pages.deleted = 0 AND pages.doktype <> ') . \TYPO3\CMS\Frontend\Page\PageRepository::DOKTYPE_RECYCLER);
212 if (empty($row)) {
213 throw new \RuntimeException(('Could not fetch page data for uid ' . $uid) . '.', 1343589451);
214 }
215 $this->pageContext->versionOL('pages', $row, FALSE, TRUE);
216 $this->pageContext->fixVersioningPid('pages', $row);
217 if (is_array($row)) {
218 $row = $this->enrichWithRelationFields($uid, $row);
219 $this->pageContext->getPageOverlay($row, $this->languageUid);
220 self::$pageRecordCache[$this->getCacheIdentifier($uid)] = $row;
221 }
222 }
223 if (!is_array(self::$pageRecordCache[$this->getCacheIdentifier($uid)])) {
224 throw new \RuntimeException(('Broken rootline. Could not resolve page with uid ' . $uid) . '.', 1343464101);
225 }
226 return self::$pageRecordCache[$this->getCacheIdentifier($uid)];
227 }
228
229 /**
230 * Resolve relations as defined in TCA and add them to the provided $pageRecord array.
231 *
232 * @param integer $uid Page id
233 * @param array $pageRecord Array with page data to add relation data to.
234 * @throws RuntimeException
235 * @return array $pageRecord with additional relations
236 */
237 protected function enrichWithRelationFields($uid, array $pageRecord) {
238 foreach ($GLOBALS['TCA']['pages']['columns'] as $column => $configuration) {
239 if ($this->columnHasRelationToResolve($configuration)) {
240 $configuration = $configuration['config'];
241 if ($configuration['MM']) {
242 /** @var $loadDBGroup \TYPO3\CMS\Core\Database\RelationHandler */
243 $loadDBGroup = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Database\\RelationHandler');
244 $loadDBGroup->start($pageRecord[$column], $configuration['foreign_table'], $configuration['MM'], $uid, 'pages', $configuration);
245 $relatedUids = $loadDBGroup->tableArray[$configuration['foreign_table']];
246 } elseif ($configuration['foreign_field']) {
247 $table = $configuration['foreign_table'];
248 $field = $configuration['foreign_field'];
249 $whereClauseParts = array((('`' . $field) . '` = ') . intval($uid));
250 if (isset($configuration['foreign_match_fields']) && is_array($configuration['foreign_match_fields'])) {
251 foreach ($configuration['foreign_match_fields'] as $field => $value) {
252 $whereClauseParts[] = (('`' . $field) . '` = ') . $GLOBALS['TYPO3_DB']->fullQuoteStr($value, $table);
253 }
254 }
255 if (isset($configuration['foreign_table_field'])) {
256 $whereClauseParts[] = ('`' . trim($configuration['foreign_table_field'])) . '` = \'pages\'';
257 }
258 $whereClause = implode(' AND ', $whereClauseParts);
259 $rows = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows('uid', $table, $whereClause);
260 if (!is_array($rows)) {
261 throw new \RuntimeException((('Could to resolve related records for page ' . $uid) . ' and foreign_table ') . htmlspecialchars($configuration['foreign_table']), 1343589452);
262 }
263 $relatedUids = array();
264 foreach ($rows as $row) {
265 $relatedUids[] = $row['uid'];
266 }
267 }
268 $pageRecord[$column] = implode(',', $relatedUids);
269 }
270 }
271 return $pageRecord;
272 }
273
274 /**
275 * Checks whether the TCA Configuration array of a column
276 * describes a relation which is not stored as CSV in the record
277 *
278 * @param array $configuration TCA configuration to check
279 * @return boolean TRUE, if it describes a non-CSV relation
280 */
281 protected function columnHasRelationToResolve(array $configuration) {
282 $configuration = $configuration['config'];
283 if ((isset($configuration['MM']) && isset($configuration['type'])) && in_array($configuration['type'], array('select', 'inline', 'group'))) {
284 return TRUE;
285 }
286 if ((isset($configuration['foreign_field']) && isset($configuration['type'])) && in_array($configuration['type'], array('select', 'inline'))) {
287 return TRUE;
288 }
289 return FALSE;
290 }
291
292 /**
293 * Actual function to generate the rootline and cache it
294 *
295 * @throws RuntimeException
296 * @return void
297 */
298 protected function generateRootlineCache() {
299 $page = $this->getRecordArray($this->pageUid);
300 // If the current page is a mounted (according to the MP parameter) handle the mount-point
301 if ($this->isMountedPage()) {
302 $mountPoint = $this->getRecordArray($this->parsedMountPointParameters[$this->pageUid]);
303 $page = $this->processMountedPage($page, $mountPoint);
304 $parentUid = $mountPoint['pid'];
305 // Anyhow after reaching the mount-point, we have to go up that rootline
306 unset($this->parsedMountPointParameters[$this->pageUid]);
307 } else {
308 $parentUid = $page['pid'];
309 }
310 $cacheTags = array('pageId_' . $page['uid']);
311 if ($parentUid > 0) {
312 // Get rootline of (and including) parent page
313 $mountPointParameter = count($this->parsedMountPointParameters) > 0 ? $this->mountPointParameter : '';
314 /** @var $rootline \TYPO3\CMS\Core\Utility\RootlineUtility */
315 $rootline = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Utility\\RootlineUtility', $parentUid, $mountPointParameter, $this->pageContext);
316 $rootline = $rootline->get();
317 // retrieve cache tags of parent rootline
318 foreach ($rootline as $entry) {
319 $cacheTags[] = 'pageId_' . $entry['uid'];
320 if ($entry['uid'] == $this->pageUid) {
321 throw new \RuntimeException(('Circular connection in rootline for page with uid ' . $this->pageUid) . ' found. Check your mountpoint configuration.', 1343464103);
322 }
323 }
324 } else {
325 $rootline = array();
326 }
327 array_push($rootline, $page);
328 krsort($rootline);
329 self::$cache->set($this->getCacheIdentifier(), $rootline, $cacheTags);
330 self::$localCache[$this->getCacheIdentifier()] = $rootline;
331 }
332
333 /**
334 * Checks whether the current Page is a Mounted Page
335 * (according to the MP-URL-Parameter)
336 *
337 * @return boolean
338 */
339 public function isMountedPage() {
340 return in_array($this->pageUid, array_keys($this->parsedMountPointParameters));
341 }
342
343 /**
344 * Enhances with mount point information or replaces the node if needed
345 *
346 * @param array $mountedPageData page record array of mounted page
347 * @param array $mountPointPageData page record array of mount point page
348 * @throws RuntimeException
349 * @return array
350 */
351 protected function processMountedPage(array $mountedPageData, array $mountPointPageData) {
352 if ($mountPointPageData['mount_pid'] != $mountedPageData['uid']) {
353 throw new \RuntimeException(((('Broken rootline. Mountpoint parameter does not match the actual rootline. mount_pid (' . $mountPointPageData['mount_pid']) . ') does not match page uid (') . $mountedPageData['uid']) . ').', 1343464100);
354 }
355 // Current page replaces the original mount-page
356 if ($mountPointPageData['mount_pid_ol']) {
357 $mountedPageData['_MOUNT_OL'] = TRUE;
358 $mountedPageData['_MOUNT_PAGE'] = array(
359 'uid' => $mountPointPageData['uid'],
360 'pid' => $mountPointPageData['pid'],
361 'title' => $mountPointPageData['title']
362 );
363 } else {
364 // The mount-page is not replaced, the mount-page itself has to be used
365 $mountedPageData = $mountPointPageData;
366 }
367 $mountedPageData['_MOUNTED_FROM'] = $this->pageUid;
368 $mountedPageData['_MP_PARAM'] = ($this->pageUid . '-') . $mountPointPageData['uid'];
369 return $mountedPageData;
370 }
371
372 /**
373 * Parse the MountPoint Parameters
374 * Splits the MP-Param via "," for several nested mountpoints
375 * and afterwords registers the mountpoint configurations
376 *
377 * @return void
378 */
379 protected function parseMountPointParameter() {
380 $mountPoints = \TYPO3\CMS\Core\Utility\GeneralUtility::trimExplode(',', $this->mountPointParameter);
381 foreach ($mountPoints as $mP) {
382 list($mountedPageUid, $mountPageUid) = \TYPO3\CMS\Core\Utility\GeneralUtility::intExplode('-', $mP);
383 $this->parsedMountPointParameters[$mountedPageUid] = $mountPageUid;
384 }
385 }
386
387 }
388
389
390 ?>