[TASK] Remove @package and @subpackage annotations
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / Utility / RootlineUtility.php
1 <?php
2 namespace TYPO3\CMS\Core\Utility;
3
4 /***************************************************************
5 * Copyright notice
6 *
7 * (c) 2012 Steffen Ritter <steffen.ritter@typo3.org>
8 * All rights reserved
9 *
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.
15 *
16 * The GNU General Public License can be found at
17 * http://www.gnu.org/copyleft/gpl.html.
18 *
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.
23 *
24 * This copyright notice MUST APPEAR in all copies of the script!
25 ***************************************************************/
26 /**
27 * A utility resolving and Caching the Rootline generation
28 *
29 * @author Steffen Ritter <steffen.ritter@typo3.org>
30 */
31 class RootlineUtility {
32
33 /**
34 * @var integer
35 */
36 protected $pageUid;
37
38 /**
39 * @var string
40 */
41 protected $mountPointParameter;
42
43 /**
44 * @var array
45 */
46 protected $parsedMountPointParameters = array();
47
48 /**
49 * @var integer
50 */
51 protected $languageUid = 0;
52
53 /**
54 * @var integer
55 */
56 protected $workspaceUid = 0;
57
58 /**
59 * @var boolean
60 */
61 protected $versionPreview = FALSE;
62
63 /**
64 * @var \TYPO3\CMS\Core\Cache\Frontend\FrontendInterface
65 */
66 static protected $cache = NULL;
67
68 /**
69 * @var array
70 */
71 static protected $localCache = array();
72
73 /**
74 * Fields to fetch when populating rootline data
75 *
76 * @var array
77 */
78 static protected $rootlineFields = array(
79 'pid',
80 'uid',
81 't3ver_oid',
82 't3ver_wsid',
83 't3ver_state',
84 'title',
85 'alias',
86 'nav_title',
87 'media',
88 'layout',
89 'hidden',
90 'starttime',
91 'endtime',
92 'fe_group',
93 'extendToSubpages',
94 'doktype',
95 'TSconfig',
96 'storage_pid',
97 'is_siteroot',
98 'mount_pid',
99 'mount_pid_ol',
100 'fe_login_mode',
101 'backend_layout_next_level'
102 );
103
104 /**
105 * Rootline Context
106 *
107 * @var \TYPO3\CMS\Frontend\Page\PageRepository
108 */
109 protected $pageContext;
110
111 /**
112 * @var array
113 */
114 static protected $pageRecordCache = array();
115
116 /**
117 * @param int $uid
118 * @param string $mountPointParameter
119 * @param \TYPO3\CMS\Frontend\Page\PageRepository $context
120 * @throws \RuntimeException
121 */
122 public function __construct($uid, $mountPointParameter = '', \TYPO3\CMS\Frontend\Page\PageRepository $context = NULL) {
123 $this->pageUid = intval($uid);
124 $this->mountPointParameter = trim($mountPointParameter);
125 if ($context === NULL) {
126 if ($GLOBALS['TSFE']->sys_page !== NULL) {
127 $this->pageContext = $GLOBALS['TSFE']->sys_page;
128 } else {
129 $this->pageContext = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Frontend\\Page\\PageRepository');
130 }
131 } else {
132 $this->pageContext = $context;
133 }
134 $this->initializeObject();
135 }
136
137 /**
138 * Initialize a state to work with
139 *
140 * @throws \RuntimeException
141 * @return void
142 */
143 protected function initializeObject() {
144 $this->languageUid = intval($this->pageContext->sys_language_uid);
145 $this->workspaceUid = intval($this->pageContext->versioningWorkspaceId);
146 $this->versionPreview = $this->pageContext->versioningPreview;
147 if ($this->mountPointParameter !== '') {
148 if (!$GLOBALS['TYPO3_CONF_VARS']['FE']['enable_mount_pids']) {
149 throw new \RuntimeException('Mount-Point Pages are disabled for this installation. Cannot resolve a Rootline for a page with Mount-Points', 1343462896);
150 } else {
151 $this->parseMountPointParameter();
152 }
153 }
154 if (self::$cache === NULL) {
155 self::$cache = $GLOBALS['typo3CacheManager']->getCache('cache_rootline');
156 }
157 self::$rootlineFields = array_merge(self::$rootlineFields, \TYPO3\CMS\Core\Utility\GeneralUtility::trimExplode(',', $GLOBALS['TYPO3_CONF_VARS']['FE']['addRootLineFields'], TRUE));
158 array_unique(self::$rootlineFields);
159 }
160
161 /**
162 * Constructs the cache Identifier
163 *
164 * @param integer $otherUid
165 * @return string
166 */
167 public function getCacheIdentifier($otherUid = NULL) {
168 return implode('_', array(
169 $otherUid !== NULL ? intval($otherUid) : $this->pageUid,
170 $this->mountPointParameter,
171 $this->languageUid,
172 $this->workspaceUid,
173 $this->versionPreview ? 1 : 0
174 ));
175 }
176
177 /**
178 * Returns the actual rootline
179 *
180 * @return array
181 */
182 public function get() {
183 $cacheIdentifier = $this->getCacheIdentifier();
184 if (!isset(self::$localCache[$cacheIdentifier])) {
185 if (!self::$cache->has($cacheIdentifier)) {
186 $this->generateRootlineCache();
187 } else {
188 self::$localCache[$cacheIdentifier] = self::$cache->get($cacheIdentifier);
189 }
190 }
191 return self::$localCache[$cacheIdentifier];
192 }
193
194 /**
195 * Queries the database for the page record and returns it.
196 *
197 * @param integer $uid Page id
198 * @throws \RuntimeException
199 * @return array
200 */
201 protected function getRecordArray($uid) {
202 if (!isset(self::$pageRecordCache[$this->getCacheIdentifier($uid)])) {
203 if (!is_array($GLOBALS['TCA']['pages']['columns'])) {
204 if (isset($GLOBALS['TSFE'])) {
205 $GLOBALS['TSFE']->includeTCA($GLOBALS['TSFE']->TCAloaded);
206 }
207 \TYPO3\CMS\Core\Utility\GeneralUtility::loadTCA('pages');
208 }
209 $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);
210 if (empty($row)) {
211 throw new \RuntimeException('Could not fetch page data for uid ' . $uid . '.', 1343589451);
212 }
213 $this->pageContext->versionOL('pages', $row, FALSE, TRUE);
214 $this->pageContext->fixVersioningPid('pages', $row);
215 if (is_array($row)) {
216 $this->pageContext->getPageOverlay($row, $this->languageUid);
217 $row = $this->enrichWithRelationFields($uid, $row);
218 self::$pageRecordCache[$this->getCacheIdentifier($uid)] = $row;
219 }
220 }
221 if (!is_array(self::$pageRecordCache[$this->getCacheIdentifier($uid)])) {
222 throw new \RuntimeException('Broken rootline. Could not resolve page with uid ' . $uid . '.', 1343464101);
223 }
224 return self::$pageRecordCache[$this->getCacheIdentifier($uid)];
225 }
226
227 /**
228 * Resolve relations as defined in TCA and add them to the provided $pageRecord array.
229 *
230 * @param integer $uid Page id
231 * @param array $pageRecord Array with page data to add relation data to.
232 * @throws \RuntimeException
233 * @return array $pageRecord with additional relations
234 */
235 protected function enrichWithRelationFields($uid, array $pageRecord) {
236 foreach ($GLOBALS['TCA']['pages']['columns'] as $column => $configuration) {
237 if ($this->columnHasRelationToResolve($configuration)) {
238 $configuration = $configuration['config'];
239 if ($configuration['MM']) {
240 /** @var $loadDBGroup \TYPO3\CMS\Core\Database\RelationHandler */
241 $loadDBGroup = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Database\\RelationHandler');
242 $loadDBGroup->start($pageRecord[$column], $configuration['foreign_table'], $configuration['MM'], $uid, 'pages', $configuration);
243 $relatedUids = $loadDBGroup->tableArray[$configuration['foreign_table']];
244 } elseif ($configuration['foreign_field']) {
245 $table = $configuration['foreign_table'];
246 $field = $configuration['foreign_field'];
247 $whereClauseParts = array('`' . $field . '` = ' . intval($uid));
248 if (isset($configuration['foreign_match_fields']) && is_array($configuration['foreign_match_fields'])) {
249 foreach ($configuration['foreign_match_fields'] as $field => $value) {
250 $whereClauseParts[] = '`' . $field . '` = ' . $GLOBALS['TYPO3_DB']->fullQuoteStr($value, $table);
251 }
252 }
253 if (isset($configuration['foreign_table_field'])) {
254 if (intval($pageRecord['sys_language_uid']) > 0) {
255 $whereClauseParts[] = '`' . trim($configuration['foreign_table_field']) . '` = \'pages_language_overlay\'';
256 } else {
257 $whereClauseParts[] = '`' . trim($configuration['foreign_table_field']) . '` = \'pages\'';
258 }
259 }
260 $whereClause = implode(' AND ', $whereClauseParts);
261 $whereClause .= $this->pageContext->deleteClause($table);
262 $rows = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows('uid', $table, $whereClause);
263 if (!is_array($rows)) {
264 throw new \RuntimeException('Could to resolve related records for page ' . $uid . ' and foreign_table ' . htmlspecialchars($configuration['foreign_table']), 1343589452);
265 }
266 $relatedUids = array();
267 foreach ($rows as $row) {
268 $relatedUids[] = $row['uid'];
269 }
270 }
271 $pageRecord[$column] = implode(',', $relatedUids);
272 }
273 }
274 return $pageRecord;
275 }
276
277 /**
278 * Checks whether the TCA Configuration array of a column
279 * describes a relation which is not stored as CSV in the record
280 *
281 * @param array $configuration TCA configuration to check
282 * @return boolean TRUE, if it describes a non-CSV relation
283 */
284 protected function columnHasRelationToResolve(array $configuration) {
285 $configuration = $configuration['config'];
286 if (isset($configuration['MM']) && isset($configuration['type']) && in_array($configuration['type'], array('select', 'inline', 'group'))) {
287 return TRUE;
288 }
289 if (isset($configuration['foreign_field']) && isset($configuration['type']) && in_array($configuration['type'], array('select', 'inline'))) {
290 return TRUE;
291 }
292 return FALSE;
293 }
294
295 /**
296 * Actual function to generate the rootline and cache it
297 *
298 * @throws \RuntimeException
299 * @return void
300 */
301 protected function generateRootlineCache() {
302 $page = $this->getRecordArray($this->pageUid);
303 // If the current page is a mounted (according to the MP parameter) handle the mount-point
304 if ($this->isMountedPage()) {
305 $mountPoint = $this->getRecordArray($this->parsedMountPointParameters[$this->pageUid]);
306 $page = $this->processMountedPage($page, $mountPoint);
307 $parentUid = $mountPoint['pid'];
308 // Anyhow after reaching the mount-point, we have to go up that rootline
309 unset($this->parsedMountPointParameters[$this->pageUid]);
310 } else {
311 $parentUid = $page['pid'];
312 }
313 $cacheTags = array('pageId_' . $page['uid']);
314 if ($parentUid > 0) {
315 // Get rootline of (and including) parent page
316 $mountPointParameter = count($this->parsedMountPointParameters) > 0 ? $this->mountPointParameter : '';
317 /** @var $rootline \TYPO3\CMS\Core\Utility\RootlineUtility */
318 $rootline = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Utility\\RootlineUtility', $parentUid, $mountPointParameter, $this->pageContext);
319 $rootline = $rootline->get();
320 // retrieve cache tags of parent rootline
321 foreach ($rootline as $entry) {
322 $cacheTags[] = 'pageId_' . $entry['uid'];
323 if ($entry['uid'] == $this->pageUid) {
324 throw new \RuntimeException('Circular connection in rootline for page with uid ' . $this->pageUid . ' found. Check your mountpoint configuration.', 1343464103);
325 }
326 }
327 } else {
328 $rootline = array();
329 }
330 array_push($rootline, $page);
331 krsort($rootline);
332 self::$cache->set($this->getCacheIdentifier(), $rootline, $cacheTags);
333 self::$localCache[$this->getCacheIdentifier()] = $rootline;
334 }
335
336 /**
337 * Checks whether the current Page is a Mounted Page
338 * (according to the MP-URL-Parameter)
339 *
340 * @return boolean
341 */
342 public function isMountedPage() {
343 return in_array($this->pageUid, array_keys($this->parsedMountPointParameters));
344 }
345
346 /**
347 * Enhances with mount point information or replaces the node if needed
348 *
349 * @param array $mountedPageData page record array of mounted page
350 * @param array $mountPointPageData page record array of mount point page
351 * @throws \RuntimeException
352 * @return array
353 */
354 protected function processMountedPage(array $mountedPageData, array $mountPointPageData) {
355 if ($mountPointPageData['mount_pid'] != $mountedPageData['uid']) {
356 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);
357 }
358 // Current page replaces the original mount-page
359 if ($mountPointPageData['mount_pid_ol']) {
360 $mountedPageData['_MOUNT_OL'] = TRUE;
361 $mountedPageData['_MOUNT_PAGE'] = array(
362 'uid' => $mountPointPageData['uid'],
363 'pid' => $mountPointPageData['pid'],
364 'title' => $mountPointPageData['title']
365 );
366 } else {
367 // The mount-page is not replaced, the mount-page itself has to be used
368 $mountedPageData = $mountPointPageData;
369 }
370 $mountedPageData['_MOUNTED_FROM'] = $this->pageUid;
371 $mountedPageData['_MP_PARAM'] = $this->pageUid . '-' . $mountPointPageData['uid'];
372 return $mountedPageData;
373 }
374
375 /**
376 * Parse the MountPoint Parameters
377 * Splits the MP-Param via "," for several nested mountpoints
378 * and afterwords registers the mountpoint configurations
379 *
380 * @return void
381 */
382 protected function parseMountPointParameter() {
383 $mountPoints = \TYPO3\CMS\Core\Utility\GeneralUtility::trimExplode(',', $this->mountPointParameter);
384 foreach ($mountPoints as $mP) {
385 list($mountedPageUid, $mountPageUid) = \TYPO3\CMS\Core\Utility\GeneralUtility::intExplode('-', $mP);
386 $this->parsedMountPointParameters[$mountedPageUid] = $mountPageUid;
387 }
388 }
389
390 }
391
392
393 ?>