TYPO3  7.6
RootlineUtility.php
Go to the documentation of this file.
1 <?php
2 namespace TYPO3\CMS\Core\Utility;
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 
18 
23 {
27  protected $pageUid;
28 
33 
37  protected $parsedMountPointParameters = array();
38 
42  protected $languageUid = 0;
43 
47  protected $workspaceUid = 0;
48 
52  protected $versionPreview = false;
53 
57  protected static $cache = null;
58 
62  protected static $localCache = array();
63 
69  protected static $rootlineFields = array(
70  'pid',
71  'uid',
72  't3ver_oid',
73  't3ver_wsid',
74  't3ver_state',
75  'title',
76  'alias',
77  'nav_title',
78  'media',
79  'layout',
80  'hidden',
81  'starttime',
82  'endtime',
83  'fe_group',
84  'extendToSubpages',
85  'doktype',
86  'TSconfig',
87  'is_siteroot',
88  'mount_pid',
89  'mount_pid_ol',
90  'fe_login_mode',
91  'backend_layout_next_level'
92  );
93 
99  protected $pageContext;
100 
104  protected $cacheIdentifier;
105 
109  protected static $pageRecordCache = array();
110 
115 
122  public function __construct($uid, $mountPointParameter = '', PageRepository $context = null)
123  {
124  $this->pageUid = (int)$uid;
125  $this->mountPointParameter = trim($mountPointParameter);
126  if ($context === null) {
127  if (isset($GLOBALS['TSFE']) && is_object($GLOBALS['TSFE']) && is_object($GLOBALS['TSFE']->sys_page)) {
128  $this->pageContext = $GLOBALS['TSFE']->sys_page;
129  } else {
130  $this->pageContext = GeneralUtility::makeInstance(\TYPO3\CMS\Frontend\Page\PageRepository::class);
131  }
132  } else {
133  $this->pageContext = $context;
134  }
135  $this->initializeObject();
136  }
137 
144  protected function initializeObject()
145  {
146  $this->languageUid = (int)$this->pageContext->sys_language_uid;
147  $this->workspaceUid = (int)$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 = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Cache\CacheManager::class)->getCache('cache_rootline');
158  }
159  self::$rootlineFields = array_merge(self::$rootlineFields, GeneralUtility::trimExplode(',', $GLOBALS['TYPO3_CONF_VARS']['FE']['addRootLineFields'], true));
160  self::$rootlineFields = array_unique(self::$rootlineFields);
161  $this->databaseConnection = $GLOBALS['TYPO3_DB'];
162 
163  $this->cacheIdentifier = $this->getCacheIdentifier();
164  }
165 
173  public static function purgeCaches()
174  {
175  self::$localCache = array();
176  self::$pageRecordCache = array();
177  }
178 
185  public function getCacheIdentifier($otherUid = null)
186  {
187  $mountPointParameter = (string)$this->mountPointParameter;
188  if ($mountPointParameter !== '' && strpos($mountPointParameter, ',') !== false) {
189  $mountPointParameter = str_replace(',', '__', $mountPointParameter);
190  }
191 
192  return implode('_', array(
193  $otherUid !== null ? (int)$otherUid : $this->pageUid,
195  $this->languageUid,
196  $this->workspaceUid,
197  $this->versionPreview ? 1 : 0
198  ));
199  }
200 
206  public function get()
207  {
208  if (!isset(static::$localCache[$this->cacheIdentifier])) {
209  $entry = static::$cache->get($this->cacheIdentifier);
210  if (!$entry) {
211  $this->generateRootlineCache();
212  } else {
213  static::$localCache[$this->cacheIdentifier] = $entry;
214  $depth = count($entry);
215  // Populate the root-lines for parent pages as well
216  // since they are part of the current root-line
217  while ($depth > 1) {
218  --$depth;
219  $parentCacheIdentifier = $this->getCacheIdentifier($entry[$depth - 1]['uid']);
220  // Abort if the root-line of the parent page is
221  // already in the local cache data
222  if (isset(static::$localCache[$parentCacheIdentifier])) {
223  break;
224  }
225  // Behaves similar to array_shift(), but preserves
226  // the array keys - which contain the page ids here
227  $entry = array_slice($entry, 1, null, true);
228  static::$localCache[$parentCacheIdentifier] = $entry;
229  }
230  }
231  }
232  return static::$localCache[$this->cacheIdentifier];
233  }
234 
242  protected function getRecordArray($uid)
243  {
244  $currentCacheIdentifier = $this->getCacheIdentifier($uid);
245  if (!isset(self::$pageRecordCache[$currentCacheIdentifier])) {
246  $row = $this->databaseConnection->exec_SELECTgetSingleRow(implode(',', self::$rootlineFields), 'pages', 'uid = ' . (int)$uid . ' AND pages.deleted = 0 AND pages.doktype <> ' . PageRepository::DOKTYPE_RECYCLER);
247  if (empty($row)) {
248  throw new \RuntimeException('Could not fetch page data for uid ' . $uid . '.', 1343589451);
249  }
250  $this->pageContext->versionOL('pages', $row, false, true);
251  $this->pageContext->fixVersioningPid('pages', $row);
252  if (is_array($row)) {
253  if ($this->languageUid > 0) {
254  $row = $this->pageContext->getPageOverlay($row, $this->languageUid);
255  }
256  $row = $this->enrichWithRelationFields(isset($row['_PAGES_OVERLAY_UID']) ? $row['_PAGES_OVERLAY_UID'] : $uid, $row);
257  self::$pageRecordCache[$currentCacheIdentifier] = $row;
258  }
259  }
260  if (!is_array(self::$pageRecordCache[$currentCacheIdentifier])) {
261  throw new \RuntimeException('Broken rootline. Could not resolve page with uid ' . $uid . '.', 1343464101);
262  }
263  return self::$pageRecordCache[$currentCacheIdentifier];
264  }
265 
274  protected function enrichWithRelationFields($uid, array $pageRecord)
275  {
276  $pageOverlayFields = GeneralUtility::trimExplode(',', $GLOBALS['TYPO3_CONF_VARS']['FE']['pageOverlayFields']);
277  foreach ($GLOBALS['TCA']['pages']['columns'] as $column => $configuration) {
278  if ($this->columnHasRelationToResolve($configuration)) {
279  $configuration = $configuration['config'];
280  if ($configuration['MM']) {
282  $loadDBGroup = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Database\RelationHandler::class);
283  $loadDBGroup->start(
284  $pageRecord[$column],
285  isset($configuration['allowed']) ? $configuration['allowed'] : $configuration['foreign_table'],
286  $configuration['MM'],
287  $uid,
288  'pages',
289  $configuration
290  );
291  $relatedUids = isset($loadDBGroup->tableArray[$configuration['foreign_table']])
292  ? $loadDBGroup->tableArray[$configuration['foreign_table']]
293  : array();
294  } else {
295  $columnIsOverlaid = in_array($column, $pageOverlayFields, true);
296  $table = $configuration['foreign_table'];
297  $field = $configuration['foreign_field'];
298  $whereClauseParts = array($field . ' = ' . (int)($columnIsOverlaid ? $uid : $pageRecord['uid']));
299  if (isset($configuration['foreign_match_fields']) && is_array($configuration['foreign_match_fields'])) {
300  foreach ($configuration['foreign_match_fields'] as $field => $value) {
301  $whereClauseParts[] = $field . ' = ' . $this->databaseConnection->fullQuoteStr($value, $table);
302  }
303  }
304  if (isset($configuration['foreign_table_field'])) {
305  if ((int)$this->languageUid > 0 && $columnIsOverlaid) {
306  $whereClauseParts[] = trim($configuration['foreign_table_field']) . ' = \'pages_language_overlay\'';
307  } else {
308  $whereClauseParts[] = trim($configuration['foreign_table_field']) . ' = \'pages\'';
309  }
310  }
311  if (isset($GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['disabled'])) {
312  $whereClauseParts[] = $table . '.' . $GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['disabled'] . ' = 0';
313  }
314  $whereClause = implode(' AND ', $whereClauseParts);
315  $whereClause .= $this->pageContext->deleteClause($table);
316  $orderBy = isset($configuration['foreign_sortby']) ? $configuration['foreign_sortby'] : '';
317  $rows = $this->databaseConnection->exec_SELECTgetRows('uid', $table, $whereClause, '', $orderBy);
318  if (!is_array($rows)) {
319  throw new \RuntimeException('Could to resolve related records for page ' . $uid . ' and foreign_table ' . htmlspecialchars($configuration['foreign_table']), 1343589452);
320  }
321  $relatedUids = array();
322  foreach ($rows as $row) {
323  $relatedUids[] = $row['uid'];
324  }
325  }
326  $pageRecord[$column] = implode(',', $relatedUids);
327  }
328  }
329  return $pageRecord;
330  }
331 
339  protected function columnHasRelationToResolve(array $configuration)
340  {
341  $configuration = $configuration['config'];
342  if (!empty($configuration['MM']) && !empty($configuration['type']) && in_array($configuration['type'], array('select', 'inline', 'group'))) {
343  return true;
344  }
345  if (!empty($configuration['foreign_field']) && !empty($configuration['type']) && in_array($configuration['type'], array('select', 'inline'))) {
346  return true;
347  }
348  return false;
349  }
350 
357  protected function generateRootlineCache()
358  {
359  $page = $this->getRecordArray($this->pageUid);
360  // If the current page is a mounted (according to the MP parameter) handle the mount-point
361  if ($this->isMountedPage()) {
362  $mountPoint = $this->getRecordArray($this->parsedMountPointParameters[$this->pageUid]);
363  $page = $this->processMountedPage($page, $mountPoint);
364  $parentUid = $mountPoint['pid'];
365  // Anyhow after reaching the mount-point, we have to go up that rootline
366  unset($this->parsedMountPointParameters[$this->pageUid]);
367  } else {
368  $parentUid = $page['pid'];
369  }
370  $cacheTags = array('pageId_' . $page['uid']);
371  if ($parentUid > 0) {
372  // Get rootline of (and including) parent page
373  $mountPointParameter = !empty($this->parsedMountPointParameters) ? $this->mountPointParameter : '';
375  $rootline = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Utility\RootlineUtility::class, $parentUid, $mountPointParameter, $this->pageContext);
376  $rootline = $rootline->get();
377  // retrieve cache tags of parent rootline
378  foreach ($rootline as $entry) {
379  $cacheTags[] = 'pageId_' . $entry['uid'];
380  if ($entry['uid'] == $this->pageUid) {
381  throw new \RuntimeException('Circular connection in rootline for page with uid ' . $this->pageUid . ' found. Check your mountpoint configuration.', 1343464103);
382  }
383  }
384  } else {
385  $rootline = array();
386  }
387  array_push($rootline, $page);
388  krsort($rootline);
389  static::$cache->set($this->cacheIdentifier, $rootline, $cacheTags);
390  static::$localCache[$this->cacheIdentifier] = $rootline;
391  }
392 
399  public function isMountedPage()
400  {
401  return in_array($this->pageUid, array_keys($this->parsedMountPointParameters));
402  }
403 
412  protected function processMountedPage(array $mountedPageData, array $mountPointPageData)
413  {
414  if ($mountPointPageData['mount_pid'] != $mountedPageData['uid']) {
415  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);
416  }
417  // Current page replaces the original mount-page
418  if ($mountPointPageData['mount_pid_ol']) {
419  $mountedPageData['_MOUNT_OL'] = true;
420  $mountedPageData['_MOUNT_PAGE'] = array(
421  'uid' => $mountPointPageData['uid'],
422  'pid' => $mountPointPageData['pid'],
423  'title' => $mountPointPageData['title']
424  );
425  } else {
426  // The mount-page is not replaced, the mount-page itself has to be used
427  $mountedPageData = $mountPointPageData;
428  }
429  $mountedPageData['_MOUNTED_FROM'] = $this->pageUid;
430  $mountedPageData['_MP_PARAM'] = $this->pageUid . '-' . $mountPointPageData['uid'];
431  return $mountedPageData;
432  }
433 
441  protected function parseMountPointParameter()
442  {
443  $mountPoints = GeneralUtility::trimExplode(',', $this->mountPointParameter);
444  foreach ($mountPoints as $mP) {
445  list($mountedPageUid, $mountPageUid) = GeneralUtility::intExplode('-', $mP);
446  $this->parsedMountPointParameters[$mountedPageUid] = $mountPageUid;
447  }
448  }
449 }