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