* All rights reserved * * This script is part of the TYPO3 project. The TYPO3 project is * free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * The GNU General Public License can be found at * http://www.gnu.org/copyleft/gpl.html. * * This script is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * This copyright notice MUST APPEAR in all copies of the script! ***************************************************************/ /** * A utility resolving and Caching the Rootline generation * * @author Steffen Ritter */ class RootlineUtility { /** * @var integer */ protected $pageUid; /** * @var string */ protected $mountPointParameter; /** * @var array */ protected $parsedMountPointParameters = array(); /** * @var integer */ protected $languageUid = 0; /** * @var integer */ protected $workspaceUid = 0; /** * @var boolean */ protected $versionPreview = FALSE; /** * @var \TYPO3\CMS\Core\Cache\Frontend\FrontendInterface */ static protected $cache = NULL; /** * @var array */ static protected $localCache = array(); /** * Fields to fetch when populating rootline data * * @var array */ static protected $rootlineFields = array( 'pid', 'uid', 't3ver_oid', 't3ver_wsid', 't3ver_state', 'title', 'alias', 'nav_title', 'media', 'layout', 'hidden', 'starttime', 'endtime', 'fe_group', 'extendToSubpages', 'doktype', 'TSconfig', 'storage_pid', 'is_siteroot', 'mount_pid', 'mount_pid_ol', 'fe_login_mode', 'backend_layout_next_level' ); /** * Rootline Context * * @var \TYPO3\CMS\Frontend\Page\PageRepository */ protected $pageContext; /** * @var array */ static protected $pageRecordCache = array(); /** * @param int $uid * @param string $mountPointParameter * @param \TYPO3\CMS\Frontend\Page\PageRepository $context * @throws \RuntimeException */ public function __construct($uid, $mountPointParameter = '', \TYPO3\CMS\Frontend\Page\PageRepository $context = NULL) { $this->pageUid = intval($uid); $this->mountPointParameter = trim($mountPointParameter); if ($context === NULL) { if ($GLOBALS['TSFE']->sys_page !== NULL) { $this->pageContext = $GLOBALS['TSFE']->sys_page; } else { $this->pageContext = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Frontend\\Page\\PageRepository'); } } else { $this->pageContext = $context; } $this->initializeObject(); } /** * Initialize a state to work with * * @throws \RuntimeException * @return void */ protected function initializeObject() { $this->languageUid = intval($this->pageContext->sys_language_uid); $this->workspaceUid = intval($this->pageContext->versioningWorkspaceId); $this->versionPreview = $this->pageContext->versioningPreview; if ($this->mountPointParameter !== '') { if (!$GLOBALS['TYPO3_CONF_VARS']['FE']['enable_mount_pids']) { throw new \RuntimeException('Mount-Point Pages are disabled for this installation. Cannot resolve a Rootline for a page with Mount-Points', 1343462896); } else { $this->parseMountPointParameter(); } } if (self::$cache === NULL) { self::$cache = $GLOBALS['typo3CacheManager']->getCache('cache_rootline'); } self::$rootlineFields = array_merge(self::$rootlineFields, \TYPO3\CMS\Core\Utility\GeneralUtility::trimExplode(',', $GLOBALS['TYPO3_CONF_VARS']['FE']['addRootLineFields'], TRUE)); array_unique(self::$rootlineFields); } /** * Constructs the cache Identifier * * @param integer $otherUid * @return string */ public function getCacheIdentifier($otherUid = NULL) { return implode('_', array( $otherUid !== NULL ? intval($otherUid) : $this->pageUid, $this->mountPointParameter, $this->languageUid, $this->workspaceUid, $this->versionPreview ? 1 : 0 )); } /** * Returns the actual rootline * * @return array */ public function get() { $cacheIdentifier = $this->getCacheIdentifier(); if (!isset(self::$localCache[$cacheIdentifier])) { if (!self::$cache->has($cacheIdentifier)) { $this->generateRootlineCache(); } else { self::$localCache[$cacheIdentifier] = self::$cache->get($cacheIdentifier); } } return self::$localCache[$cacheIdentifier]; } /** * Queries the database for the page record and returns it. * * @param integer $uid Page id * @throws \RuntimeException * @return array */ protected function getRecordArray($uid) { if (!isset(self::$pageRecordCache[$this->getCacheIdentifier($uid)])) { if (!is_array($GLOBALS['TCA']['pages']['columns'])) { if (isset($GLOBALS['TSFE'])) { $GLOBALS['TSFE']->includeTCA($GLOBALS['TSFE']->TCAloaded); } \TYPO3\CMS\Core\Utility\GeneralUtility::loadTCA('pages'); } $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); if (empty($row)) { throw new \RuntimeException('Could not fetch page data for uid ' . $uid . '.', 1343589451); } $this->pageContext->versionOL('pages', $row, FALSE, TRUE); $this->pageContext->fixVersioningPid('pages', $row); if (is_array($row)) { $this->pageContext->getPageOverlay($row, $this->languageUid); $row = $this->enrichWithRelationFields($uid, $row); self::$pageRecordCache[$this->getCacheIdentifier($uid)] = $row; } } if (!is_array(self::$pageRecordCache[$this->getCacheIdentifier($uid)])) { throw new \RuntimeException('Broken rootline. Could not resolve page with uid ' . $uid . '.', 1343464101); } return self::$pageRecordCache[$this->getCacheIdentifier($uid)]; } /** * Resolve relations as defined in TCA and add them to the provided $pageRecord array. * * @param integer $uid Page id * @param array $pageRecord Array with page data to add relation data to. * @throws \RuntimeException * @return array $pageRecord with additional relations */ protected function enrichWithRelationFields($uid, array $pageRecord) { foreach ($GLOBALS['TCA']['pages']['columns'] as $column => $configuration) { if ($this->columnHasRelationToResolve($configuration)) { $configuration = $configuration['config']; if ($configuration['MM']) { /** @var $loadDBGroup \TYPO3\CMS\Core\Database\RelationHandler */ $loadDBGroup = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Database\\RelationHandler'); $loadDBGroup->start($pageRecord[$column], $configuration['foreign_table'], $configuration['MM'], $uid, 'pages', $configuration); $relatedUids = $loadDBGroup->tableArray[$configuration['foreign_table']]; } elseif ($configuration['foreign_field']) { $table = $configuration['foreign_table']; $field = $configuration['foreign_field']; $whereClauseParts = array('`' . $field . '` = ' . intval($uid)); if (isset($configuration['foreign_match_fields']) && is_array($configuration['foreign_match_fields'])) { foreach ($configuration['foreign_match_fields'] as $field => $value) { $whereClauseParts[] = '`' . $field . '` = ' . $GLOBALS['TYPO3_DB']->fullQuoteStr($value, $table); } } if (isset($configuration['foreign_table_field'])) { if (intval($pageRecord['sys_language_uid']) > 0) { $whereClauseParts[] = '`' . trim($configuration['foreign_table_field']) . '` = \'pages_language_overlay\''; } else { $whereClauseParts[] = '`' . trim($configuration['foreign_table_field']) . '` = \'pages\''; } } $whereClause = implode(' AND ', $whereClauseParts); $whereClause .= $this->pageContext->deleteClause($table); $rows = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows('uid', $table, $whereClause); if (!is_array($rows)) { throw new \RuntimeException('Could to resolve related records for page ' . $uid . ' and foreign_table ' . htmlspecialchars($configuration['foreign_table']), 1343589452); } $relatedUids = array(); foreach ($rows as $row) { $relatedUids[] = $row['uid']; } } $pageRecord[$column] = implode(',', $relatedUids); } } return $pageRecord; } /** * Checks whether the TCA Configuration array of a column * describes a relation which is not stored as CSV in the record * * @param array $configuration TCA configuration to check * @return boolean TRUE, if it describes a non-CSV relation */ protected function columnHasRelationToResolve(array $configuration) { $configuration = $configuration['config']; if (isset($configuration['MM']) && isset($configuration['type']) && in_array($configuration['type'], array('select', 'inline', 'group'))) { return TRUE; } if (isset($configuration['foreign_field']) && isset($configuration['type']) && in_array($configuration['type'], array('select', 'inline'))) { return TRUE; } return FALSE; } /** * Actual function to generate the rootline and cache it * * @throws \RuntimeException * @return void */ protected function generateRootlineCache() { $page = $this->getRecordArray($this->pageUid); // If the current page is a mounted (according to the MP parameter) handle the mount-point if ($this->isMountedPage()) { $mountPoint = $this->getRecordArray($this->parsedMountPointParameters[$this->pageUid]); $page = $this->processMountedPage($page, $mountPoint); $parentUid = $mountPoint['pid']; // Anyhow after reaching the mount-point, we have to go up that rootline unset($this->parsedMountPointParameters[$this->pageUid]); } else { $parentUid = $page['pid']; } $cacheTags = array('pageId_' . $page['uid']); if ($parentUid > 0) { // Get rootline of (and including) parent page $mountPointParameter = count($this->parsedMountPointParameters) > 0 ? $this->mountPointParameter : ''; /** @var $rootline \TYPO3\CMS\Core\Utility\RootlineUtility */ $rootline = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Utility\\RootlineUtility', $parentUid, $mountPointParameter, $this->pageContext); $rootline = $rootline->get(); // retrieve cache tags of parent rootline foreach ($rootline as $entry) { $cacheTags[] = 'pageId_' . $entry['uid']; if ($entry['uid'] == $this->pageUid) { throw new \RuntimeException('Circular connection in rootline for page with uid ' . $this->pageUid . ' found. Check your mountpoint configuration.', 1343464103); } } } else { $rootline = array(); } array_push($rootline, $page); krsort($rootline); self::$cache->set($this->getCacheIdentifier(), $rootline, $cacheTags); self::$localCache[$this->getCacheIdentifier()] = $rootline; } /** * Checks whether the current Page is a Mounted Page * (according to the MP-URL-Parameter) * * @return boolean */ public function isMountedPage() { return in_array($this->pageUid, array_keys($this->parsedMountPointParameters)); } /** * Enhances with mount point information or replaces the node if needed * * @param array $mountedPageData page record array of mounted page * @param array $mountPointPageData page record array of mount point page * @throws \RuntimeException * @return array */ protected function processMountedPage(array $mountedPageData, array $mountPointPageData) { if ($mountPointPageData['mount_pid'] != $mountedPageData['uid']) { 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); } // Current page replaces the original mount-page if ($mountPointPageData['mount_pid_ol']) { $mountedPageData['_MOUNT_OL'] = TRUE; $mountedPageData['_MOUNT_PAGE'] = array( 'uid' => $mountPointPageData['uid'], 'pid' => $mountPointPageData['pid'], 'title' => $mountPointPageData['title'] ); } else { // The mount-page is not replaced, the mount-page itself has to be used $mountedPageData = $mountPointPageData; } $mountedPageData['_MOUNTED_FROM'] = $this->pageUid; $mountedPageData['_MP_PARAM'] = $this->pageUid . '-' . $mountPointPageData['uid']; return $mountedPageData; } /** * Parse the MountPoint Parameters * Splits the MP-Param via "," for several nested mountpoints * and afterwords registers the mountpoint configurations * * @return void */ protected function parseMountPointParameter() { $mountPoints = \TYPO3\CMS\Core\Utility\GeneralUtility::trimExplode(',', $this->mountPointParameter); foreach ($mountPoints as $mP) { list($mountedPageUid, $mountPageUid) = \TYPO3\CMS\Core\Utility\GeneralUtility::intExplode('-', $mP); $this->parsedMountPointParameters[$mountedPageUid] = $mountPageUid; } } } ?>