TYPO3CMS  8
 All Classes Namespaces Files Functions Variables Pages
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 
17 use Doctrine\DBAL\DBALException;
22 
27 {
31  protected $pageUid;
32 
37 
42 
46  protected $languageUid = 0;
47 
51  protected $workspaceUid = 0;
52 
56  protected $versionPreview = false;
57 
61  protected static $cache = null;
62 
66  protected static $localCache = [];
67 
73  protected static $rootlineFields = [
74  'pid',
75  'uid',
76  't3ver_oid',
77  't3ver_wsid',
78  't3ver_state',
79  'title',
80  'alias',
81  'nav_title',
82  'media',
83  'layout',
84  'hidden',
85  'starttime',
86  'endtime',
87  'fe_group',
88  'extendToSubpages',
89  'doktype',
90  'TSconfig',
91  'tsconfig_includes',
92  'is_siteroot',
93  'mount_pid',
94  'mount_pid_ol',
95  'fe_login_mode',
96  'backend_layout_next_level'
97  ];
98 
104  protected $pageContext;
105 
109  protected $cacheIdentifier;
110 
114  protected static $pageRecordCache = [];
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 
162  $this->cacheIdentifier = $this->getCacheIdentifier();
163  }
164 
172  public static function purgeCaches()
173  {
174  self::$localCache = [];
175  self::$pageRecordCache = [];
176  }
177 
184  public function getCacheIdentifier($otherUid = null)
185  {
186  $mountPointParameter = (string)$this->mountPointParameter;
187  if ($mountPointParameter !== '' && strpos($mountPointParameter, ',') !== false) {
188  $mountPointParameter = str_replace(',', '__', $mountPointParameter);
189  }
190 
191  return implode('_', [
192  $otherUid !== null ? (int)$otherUid : $this->pageUid,
194  $this->languageUid,
195  $this->workspaceUid,
196  $this->versionPreview ? 1 : 0
197  ]);
198  }
199 
205  public function get()
206  {
207  if (!isset(static::$localCache[$this->cacheIdentifier])) {
208  $entry = static::$cache->get($this->cacheIdentifier);
209  if (!$entry) {
210  $this->generateRootlineCache();
211  } else {
212  static::$localCache[$this->cacheIdentifier] = $entry;
213  $depth = count($entry);
214  // Populate the root-lines for parent pages as well
215  // since they are part of the current root-line
216  while ($depth > 1) {
217  --$depth;
218  $parentCacheIdentifier = $this->getCacheIdentifier($entry[$depth - 1]['uid']);
219  // Abort if the root-line of the parent page is
220  // already in the local cache data
221  if (isset(static::$localCache[$parentCacheIdentifier])) {
222  break;
223  }
224  // Behaves similar to array_shift(), but preserves
225  // the array keys - which contain the page ids here
226  $entry = array_slice($entry, 1, null, true);
227  static::$localCache[$parentCacheIdentifier] = $entry;
228  }
229  }
230  }
231  return static::$localCache[$this->cacheIdentifier];
232  }
233 
241  protected function getRecordArray($uid)
242  {
243  $currentCacheIdentifier = $this->getCacheIdentifier($uid);
244  if (!isset(self::$pageRecordCache[$currentCacheIdentifier])) {
245  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
246  $queryBuilder->getRestrictions()->removeAll()->add(GeneralUtility::makeInstance(DeletedRestriction::class));
247  $row = $queryBuilder->select(...self::$rootlineFields)
248  ->from('pages')
249  ->where(
250  $queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT)),
251  $queryBuilder->expr()->neq(
252  'doktype',
253  $queryBuilder->createNamedParameter(PageRepository::DOKTYPE_RECYCLER, \PDO::PARAM_INT)
254  )
255  )
256  ->execute()
257  ->fetch();
258  if (empty($row)) {
259  throw new \RuntimeException('Could not fetch page data for uid ' . $uid . '.', 1343589451);
260  }
261  $this->pageContext->versionOL('pages', $row, false, true);
262  $this->pageContext->fixVersioningPid('pages', $row);
263  if (is_array($row)) {
264  if ($this->languageUid > 0) {
265  $row = $this->pageContext->getPageOverlay($row, $this->languageUid);
266  }
267  $row = $this->enrichWithRelationFields(isset($row['_PAGES_OVERLAY_UID']) ? $row['_PAGES_OVERLAY_UID'] : $uid, $row);
268  self::$pageRecordCache[$currentCacheIdentifier] = $row;
269  }
270  }
271  if (!is_array(self::$pageRecordCache[$currentCacheIdentifier])) {
272  throw new \RuntimeException('Broken rootline. Could not resolve page with uid ' . $uid . '.', 1343464101);
273  }
274  return self::$pageRecordCache[$currentCacheIdentifier];
275  }
276 
285  protected function enrichWithRelationFields($uid, array $pageRecord)
286  {
287  $pageOverlayFields = GeneralUtility::trimExplode(',', $GLOBALS['TYPO3_CONF_VARS']['FE']['pageOverlayFields']);
288  $connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
289 
290  foreach ($GLOBALS['TCA']['pages']['columns'] as $column => $configuration) {
291  if ($this->columnHasRelationToResolve($configuration)) {
292  $configuration = $configuration['config'];
293  if ($configuration['MM']) {
295  $loadDBGroup = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Database\RelationHandler::class);
296  $loadDBGroup->start(
297  $pageRecord[$column],
298  isset($configuration['allowed']) ? $configuration['allowed'] : $configuration['foreign_table'],
299  $configuration['MM'],
300  $uid,
301  'pages',
302  $configuration
303  );
304  $relatedUids = isset($loadDBGroup->tableArray[$configuration['foreign_table']])
305  ? $loadDBGroup->tableArray[$configuration['foreign_table']]
306  : [];
307  } else {
308  $columnIsOverlaid = in_array($column, $pageOverlayFields, true);
309  $table = $configuration['foreign_table'];
310 
311  $queryBuilder = $connectionPool->getQueryBuilderForTable($table);
312  $queryBuilder->getRestrictions()->removeAll()
313  ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
314  ->add(GeneralUtility::makeInstance(HiddenRestriction::class));
315  $queryBuilder->select('uid')
316  ->from($table)
317  ->where(
318  $queryBuilder->expr()->eq(
319  $configuration['foreign_field'],
320  $queryBuilder->createNamedParameter(
321  $columnIsOverlaid ? $uid : $pageRecord['uid'],
322  \PDO::PARAM_INT
323  )
324  )
325  );
326 
327  if (isset($configuration['foreign_match_fields']) && is_array($configuration['foreign_match_fields'])) {
328  foreach ($configuration['foreign_match_fields'] as $field => $value) {
329  $queryBuilder->andWhere(
330  $queryBuilder->expr()->eq(
331  $field,
332  $queryBuilder->createNamedParameter($value, \PDO::PARAM_STR)
333  )
334  );
335  }
336  }
337  if (isset($configuration['foreign_table_field'])) {
338  $queryBuilder->andWhere(
339  $queryBuilder->expr()->eq(
340  trim($configuration['foreign_table_field']),
341  $queryBuilder->createNamedParameter(
342  (int)$this->languageUid > 0 && $columnIsOverlaid ? 'pages_language_overlay' : 'pages',
343  \PDO::PARAM_STR
344  )
345  )
346  );
347  }
348  if (isset($configuration['foreign_sortby'])) {
349  $queryBuilder->orderBy($configuration['foreign_sortby']);
350  }
351  try {
352  $statement = $queryBuilder->execute();
353  } catch (DBALException $e) {
354  throw new \RuntimeException('Could to resolve related records for page ' . $uid . ' and foreign_table ' . htmlspecialchars($table), 1343589452);
355  }
356  $relatedUids = [];
357  while ($row = $statement->fetch()) {
358  $relatedUids[] = $row['uid'];
359  }
360  }
361  $pageRecord[$column] = implode(',', $relatedUids);
362  }
363  }
364  return $pageRecord;
365  }
366 
374  protected function columnHasRelationToResolve(array $configuration)
375  {
376  $configuration = $configuration['config'];
377  if (!empty($configuration['MM']) && !empty($configuration['type']) && in_array($configuration['type'], ['select', 'inline', 'group'])) {
378  return true;
379  }
380  if (!empty($configuration['foreign_field']) && !empty($configuration['type']) && in_array($configuration['type'], ['select', 'inline'])) {
381  return true;
382  }
383  return false;
384  }
385 
392  protected function generateRootlineCache()
393  {
394  $page = $this->getRecordArray($this->pageUid);
395  // If the current page is a mounted (according to the MP parameter) handle the mount-point
396  if ($this->isMountedPage()) {
397  $mountPoint = $this->getRecordArray($this->parsedMountPointParameters[$this->pageUid]);
398  $page = $this->processMountedPage($page, $mountPoint);
399  $parentUid = $mountPoint['pid'];
400  // Anyhow after reaching the mount-point, we have to go up that rootline
401  unset($this->parsedMountPointParameters[$this->pageUid]);
402  } else {
403  $parentUid = $page['pid'];
404  }
405  $cacheTags = ['pageId_' . $page['uid']];
406  if ($parentUid > 0) {
407  // Get rootline of (and including) parent page
408  $mountPointParameter = !empty($this->parsedMountPointParameters) ? $this->mountPointParameter : '';
410  $rootline = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Utility\RootlineUtility::class, $parentUid, $mountPointParameter, $this->pageContext);
411  $rootline = $rootline->get();
412  // retrieve cache tags of parent rootline
413  foreach ($rootline as $entry) {
414  $cacheTags[] = 'pageId_' . $entry['uid'];
415  if ($entry['uid'] == $this->pageUid) {
416  throw new \RuntimeException('Circular connection in rootline for page with uid ' . $this->pageUid . ' found. Check your mountpoint configuration.', 1343464103);
417  }
418  }
419  } else {
420  $rootline = [];
421  }
422  array_push($rootline, $page);
423  krsort($rootline);
424  static::$cache->set($this->cacheIdentifier, $rootline, $cacheTags);
425  static::$localCache[$this->cacheIdentifier] = $rootline;
426  }
427 
434  public function isMountedPage()
435  {
436  return in_array($this->pageUid, array_keys($this->parsedMountPointParameters));
437  }
438 
447  protected function processMountedPage(array $mountedPageData, array $mountPointPageData)
448  {
449  if ($mountPointPageData['mount_pid'] != $mountedPageData['uid']) {
450  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);
451  }
452  // Current page replaces the original mount-page
453  if ($mountPointPageData['mount_pid_ol']) {
454  $mountedPageData['_MOUNT_OL'] = true;
455  $mountedPageData['_MOUNT_PAGE'] = [
456  'uid' => $mountPointPageData['uid'],
457  'pid' => $mountPointPageData['pid'],
458  'title' => $mountPointPageData['title']
459  ];
460  } else {
461  // The mount-page is not replaced, the mount-page itself has to be used
462  $mountedPageData = $mountPointPageData;
463  }
464  $mountedPageData['_MOUNTED_FROM'] = $this->pageUid;
465  $mountedPageData['_MP_PARAM'] = $this->pageUid . '-' . $mountPointPageData['uid'];
466  return $mountedPageData;
467  }
468 
476  protected function parseMountPointParameter()
477  {
478  $mountPoints = GeneralUtility::trimExplode(',', $this->mountPointParameter);
479  foreach ($mountPoints as $mP) {
480  list($mountedPageUid, $mountPageUid) = GeneralUtility::intExplode('-', $mP);
481  $this->parsedMountPointParameters[$mountedPageUid] = $mountPageUid;
482  }
483  }
484 }
processMountedPage(array $mountedPageData, array $mountPointPageData)
__construct($uid, $mountPointParameter= '', PageRepository $context=null)
static trimExplode($delim, $string, $removeEmptyValues=false, $limit=0)
columnHasRelationToResolve(array $configuration)
if(TYPO3_MODE=== 'BE') $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsfebeuserauth.php']['frontendEditingController']['default']
static makeInstance($className,...$constructorArguments)
static intExplode($delimiter, $string, $removeEmptyValues=false, $limit=0)