2 namespace TYPO3\CMS\Core\Utility
;
4 /***************************************************************
7 * (c) 2012 Steffen Ritter <steffen.ritter@typo3.org>
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.
16 * The GNU General Public License can be found at
17 * http://www.gnu.org/copyleft/gpl.html.
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.
24 * This copyright notice MUST APPEAR in all copies of the script!
25 ***************************************************************/
27 * A utility resolving and Caching the Rootline generation
29 * @author Steffen Ritter <steffen.ritter@typo3.org>
33 class RootlineUtility
{
43 protected $mountPointParameter;
48 protected $parsedMountPointParameters = array();
53 protected $languageUid = 0;
58 protected $workspaceUid = 0;
63 protected $versionPreview = FALSE;
66 * @var \TYPO3\CMS\Core\Cache\Frontend\FrontendInterface
68 static protected $cache = NULL;
73 static protected $localCache = array();
76 * Fields to fetch when populating rootline data
80 static protected $rootlineFields = array(
103 'backend_layout_next_level'
109 * @var \TYPO3\CMS\Frontend\Page\PageRepository
111 protected $pageContext;
116 static protected $pageRecordCache = array();
120 * @param string $mountPointParameter
121 * @param \TYPO3\CMS\Frontend\Page\PageRepository $context
122 * @throws \RuntimeException
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
;
131 $this->pageContext
= \TYPO3\CMS\Core\Utility\GeneralUtility
::makeInstance('TYPO3\\CMS\\Frontend\\Page\\PageRepository');
134 $this->pageContext
= $context;
136 $this->initializeObject();
140 * Initialize a state to work with
142 * @throws \RuntimeException
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);
153 $this->parseMountPointParameter();
156 if (self
::$cache === NULL) {
157 self
::$cache = $GLOBALS['typo3CacheManager']->getCache('cache_rootline');
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);
164 * Constructs the cache Identifier
166 * @param integer $otherUid
169 public function getCacheIdentifier($otherUid = NULL) {
170 return implode('_', array(
171 $otherUid !== NULL ?
intval($otherUid) : $this->pageUid
,
172 $this->mountPointParameter
,
175 $this->versionPreview ?
1 : 0
180 * Returns the actual rootline
184 public function get() {
185 $cacheIdentifier = $this->getCacheIdentifier();
186 if (!isset(self
::$localCache[$cacheIdentifier])) {
187 if (!self
::$cache->has($cacheIdentifier)) {
188 $this->generateRootlineCache();
190 self
::$localCache[$cacheIdentifier] = self
::$cache->get($cacheIdentifier);
193 return self
::$localCache[$cacheIdentifier];
197 * Queries the database for the page record and returns it.
199 * @param integer $uid Page id
200 * @throws \RuntimeException
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
);
209 \TYPO3\CMS\Core\Utility\GeneralUtility
::loadTCA('pages');
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
);
213 throw new \
RuntimeException('Could not fetch page data for uid ' . $uid . '.', 1343589451);
215 $this->pageContext
->versionOL('pages', $row, FALSE, TRUE);
216 $this->pageContext
->fixVersioningPid('pages', $row);
217 if (is_array($row)) {
218 $this->pageContext
->getPageOverlay($row, $this->languageUid
);
219 $row = $this->enrichWithRelationFields($uid, $row);
220 self
::$pageRecordCache[$this->getCacheIdentifier($uid)] = $row;
223 if (!is_array(self
::$pageRecordCache[$this->getCacheIdentifier($uid)])) {
224 throw new \
RuntimeException('Broken rootline. Could not resolve page with uid ' . $uid . '.', 1343464101);
226 return self
::$pageRecordCache[$this->getCacheIdentifier($uid)];
230 * Resolve relations as defined in TCA and add them to the provided $pageRecord array.
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
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);
255 if (isset($configuration['foreign_table_field'])) {
256 if (intval($pageRecord['sys_language_uid']) > 0) {
257 $whereClauseParts[] = '`' . trim($configuration['foreign_table_field']) . '` = \'pages_language_overlay\'';
259 $whereClauseParts[] = '`' . trim($configuration['foreign_table_field']) . '` = \'pages\'';
262 $whereClause = implode(' AND ', $whereClauseParts);
263 $whereClause .= $this->pageContext
->deleteClause($table);
264 $rows = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows('uid', $table, $whereClause);
265 if (!is_array($rows)) {
266 throw new \
RuntimeException('Could to resolve related records for page ' . $uid . ' and foreign_table ' . htmlspecialchars($configuration['foreign_table']), 1343589452);
268 $relatedUids = array();
269 foreach ($rows as $row) {
270 $relatedUids[] = $row['uid'];
273 $pageRecord[$column] = implode(',', $relatedUids);
280 * Checks whether the TCA Configuration array of a column
281 * describes a relation which is not stored as CSV in the record
283 * @param array $configuration TCA configuration to check
284 * @return boolean TRUE, if it describes a non-CSV relation
286 protected function columnHasRelationToResolve(array $configuration) {
287 $configuration = $configuration['config'];
288 if (isset($configuration['MM']) && isset($configuration['type']) && in_array($configuration['type'], array('select', 'inline', 'group'))) {
291 if (isset($configuration['foreign_field']) && isset($configuration['type']) && in_array($configuration['type'], array('select', 'inline'))) {
298 * Actual function to generate the rootline and cache it
300 * @throws \RuntimeException
303 protected function generateRootlineCache() {
304 $page = $this->getRecordArray($this->pageUid
);
305 // If the current page is a mounted (according to the MP parameter) handle the mount-point
306 if ($this->isMountedPage()) {
307 $mountPoint = $this->getRecordArray($this->parsedMountPointParameters
[$this->pageUid
]);
308 $page = $this->processMountedPage($page, $mountPoint);
309 $parentUid = $mountPoint['pid'];
310 // Anyhow after reaching the mount-point, we have to go up that rootline
311 unset($this->parsedMountPointParameters
[$this->pageUid
]);
313 $parentUid = $page['pid'];
315 $cacheTags = array('pageId_' . $page['uid']);
316 if ($parentUid > 0) {
317 // Get rootline of (and including) parent page
318 $mountPointParameter = count($this->parsedMountPointParameters
) > 0 ?
$this->mountPointParameter
: '';
319 /** @var $rootline \TYPO3\CMS\Core\Utility\RootlineUtility */
320 $rootline = \TYPO3\CMS\Core\Utility\GeneralUtility
::makeInstance('TYPO3\\CMS\\Core\\Utility\\RootlineUtility', $parentUid, $mountPointParameter, $this->pageContext
);
321 $rootline = $rootline->get();
322 // retrieve cache tags of parent rootline
323 foreach ($rootline as $entry) {
324 $cacheTags[] = 'pageId_' . $entry['uid'];
325 if ($entry['uid'] == $this->pageUid
) {
326 throw new \
RuntimeException('Circular connection in rootline for page with uid ' . $this->pageUid
. ' found. Check your mountpoint configuration.', 1343464103);
332 array_push($rootline, $page);
334 self
::$cache->set($this->getCacheIdentifier(), $rootline, $cacheTags);
335 self
::$localCache[$this->getCacheIdentifier()] = $rootline;
339 * Checks whether the current Page is a Mounted Page
340 * (according to the MP-URL-Parameter)
344 public function isMountedPage() {
345 return in_array($this->pageUid
, array_keys($this->parsedMountPointParameters
));
349 * Enhances with mount point information or replaces the node if needed
351 * @param array $mountedPageData page record array of mounted page
352 * @param array $mountPointPageData page record array of mount point page
353 * @throws \RuntimeException
356 protected function processMountedPage(array $mountedPageData, array $mountPointPageData) {
357 if ($mountPointPageData['mount_pid'] != $mountedPageData['uid']) {
358 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);
360 // Current page replaces the original mount-page
361 if ($mountPointPageData['mount_pid_ol']) {
362 $mountedPageData['_MOUNT_OL'] = TRUE;
363 $mountedPageData['_MOUNT_PAGE'] = array(
364 'uid' => $mountPointPageData['uid'],
365 'pid' => $mountPointPageData['pid'],
366 'title' => $mountPointPageData['title']
369 // The mount-page is not replaced, the mount-page itself has to be used
370 $mountedPageData = $mountPointPageData;
372 $mountedPageData['_MOUNTED_FROM'] = $this->pageUid
;
373 $mountedPageData['_MP_PARAM'] = $this->pageUid
. '-' . $mountPointPageData['uid'];
374 return $mountedPageData;
378 * Parse the MountPoint Parameters
379 * Splits the MP-Param via "," for several nested mountpoints
380 * and afterwords registers the mountpoint configurations
384 protected function parseMountPointParameter() {
385 $mountPoints = \TYPO3\CMS\Core\Utility\GeneralUtility
::trimExplode(',', $this->mountPointParameter
);
386 foreach ($mountPoints as $mP) {
387 list($mountedPageUid, $mountPageUid) = \TYPO3\CMS\Core\Utility\GeneralUtility
::intExplode('-', $mP);
388 $this->parsedMountPointParameters
[$mountedPageUid] = $mountPageUid;