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