[TASK] Improve comments and phpdoc blocks in PageRepository
[Packages/TYPO3.CMS.git] / typo3 / sysext / frontend / Classes / Page / PageRepository.php
1 <?php
2 namespace TYPO3\CMS\Frontend\Page;
3
4 /***************************************************************
5 * Copyright notice
6 *
7 * (c) 1999-2013 Kasper Skårhøj (kasperYYYY@typo3.com)
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 * A copy is found in the text file GPL.txt and important notices to the license
19 * from the author is found in LICENSE.txt distributed with these scripts.
20 *
21 *
22 * This script is distributed in the hope that it will be useful,
23 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 * GNU General Public License for more details.
26 *
27 * This copyright notice MUST APPEAR in all copies of the script!
28 ***************************************************************/
29
30 use TYPO3\CMS\Core\Utility\GeneralUtility;
31 use TYPO3\CMS\Core\Versioning\VersionState;
32
33 /**
34 * Page functions, a lot of sql/pages-related functions
35 *
36 * Mainly used in the frontend but also in some cases in the backend. It's
37 * important to set the right $where_hid_del in the object so that the
38 * functions operate properly
39 *
40 * @author Kasper Skårhøj <kasperYYYY@typo3.com>
41 * @see \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController::fetch_the_id()
42 */
43 class PageRepository {
44
45 /**
46 * @var array
47 * @todo Define visibility
48 */
49 public $urltypes = array('', 'http://', 'ftp://', 'mailto:', 'https://');
50
51 /**
52 * This is not the final clauses. There will normally be conditions for the
53 * hidden, starttime and endtime fields as well. You MUST initialize the object
54 * by the init() function
55 *
56 * @var string
57 * @todo Define visibility
58 */
59 public $where_hid_del = ' AND pages.deleted=0';
60
61 /**
62 * Clause for fe_group access
63 *
64 * @var string
65 * @todo Define visibility
66 */
67 public $where_groupAccess = '';
68
69 /**
70 * @var int
71 * @todo Define visibility
72 */
73 public $sys_language_uid = 0;
74
75 // Versioning preview related
76
77 /**
78 * If TRUE, versioning preview of other record versions is allowed. THIS MUST
79 * ONLY BE SET IF the page is not cached and truely previewed by a backend
80 * user!!!
81 *
82 * @var bool
83 * @todo Define visibility
84 */
85 public $versioningPreview = FALSE;
86
87 /**
88 * Workspace ID for preview
89 *
90 * @var int
91 * @todo Define visibility
92 */
93 public $versioningWorkspaceId = 0;
94
95 /**
96 * @var array
97 * @todo Define visibility
98 */
99 public $workspaceCache = array();
100
101 // Internal, dynamic
102
103 /**
104 * Error string set by getRootLine()
105 *
106 * @var string
107 * @todo Define visibility
108 */
109 public $error_getRootLine = '';
110
111 /**
112 * Error uid set by getRootLine()
113 *
114 * @var int
115 * @todo Define visibility
116 */
117 public $error_getRootLine_failPid = 0;
118
119 // Internal caching
120
121 /**
122 * @var array
123 */
124 protected $cache_getRootLine = array();
125
126 /**
127 * @var array
128 */
129 protected $cache_getPage = array();
130
131 /**
132 * @var array
133 */
134 protected $cache_getPage_noCheck = array();
135
136 /**
137 * @var array
138 */
139 protected $cache_getPageIdFromAlias = array();
140
141 /**
142 * @var array
143 */
144 protected $cache_getMountPointInfo = array();
145
146 /**
147 * @var array
148 */
149 protected $tableNamesAllowedOnRootLevel = array(
150 'sys_file_metadata',
151 'sys_category',
152 );
153
154 /**
155 * Named constants for "magic numbers" of the field doktype
156 */
157 const DOKTYPE_DEFAULT = 1;
158 const DOKTYPE_LINK = 3;
159 const DOKTYPE_SHORTCUT = 4;
160 const DOKTYPE_BE_USER_SECTION = 6;
161 const DOKTYPE_MOUNTPOINT = 7;
162 const DOKTYPE_SPACER = 199;
163 const DOKTYPE_SYSFOLDER = 254;
164 const DOKTYPE_RECYCLER = 255;
165
166 /**
167 * Named constants for "magic numbers" of the field shortcut_mode
168 */
169 const SHORTCUT_MODE_NONE = 0;
170 const SHORTCUT_MODE_FIRST_SUBPAGE = 1;
171 const SHORTCUT_MODE_RANDOM_SUBPAGE = 2;
172 const SHORTCUT_MODE_PARENT_PAGE = 3;
173
174 /**
175 * init() MUST be run directly after creating a new template-object
176 * This sets the internal variable $this->where_hid_del to the correct where
177 * clause for page records taking deleted/hidden/starttime/endtime/t3ver_state
178 * into account
179 *
180 * @param boolean $show_hidden If $show_hidden is TRUE, the hidden-field is ignored!! Normally this should be FALSE. Is used for previewing.
181 * @return void
182 * @see \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController::fetch_the_id(), \TYPO3\CMS\Tstemplate\Controller\TemplateAnalyzerModuleFunctionController::initialize_editor()
183 * @todo Define visibility
184 */
185 public function init($show_hidden) {
186 $this->where_groupAccess = '';
187 $this->where_hid_del = ' AND pages.deleted=0 ';
188 if (!$show_hidden) {
189 $this->where_hid_del .= 'AND pages.hidden=0 ';
190 }
191 $this->where_hid_del .= 'AND pages.starttime<=' . $GLOBALS['SIM_ACCESS_TIME'] . ' AND (pages.endtime=0 OR pages.endtime>' . $GLOBALS['SIM_ACCESS_TIME'] . ') ';
192 // Filter out new/deleted place-holder pages in case we are NOT in a
193 // versioning preview (that means we are online!)
194 if (!$this->versioningPreview) {
195 $this->where_hid_del .= ' AND NOT pages.t3ver_state>' . new VersionState(VersionState::DEFAULT_STATE);
196 } else {
197 // For version previewing, make sure that enable-fields are not
198 // de-selecting hidden pages - we need versionOL() to unset them only
199 // if the overlay record instructs us to.
200 // Copy where_hid_del to other variable (used in relation to versionOL())
201 $this->versioningPreview_where_hid_del = $this->where_hid_del;
202 // Clear where_hid_del
203 $this->where_hid_del = ' AND pages.deleted=0 ';
204 // Restrict to live and current workspaces
205 $this->where_hid_del .= ' AND (pages.t3ver_wsid=0 OR pages.t3ver_wsid=' . (int)$this->versioningWorkspaceId . ')';
206 }
207 }
208
209 /**************************
210 *
211 * Selecting page records
212 *
213 **************************/
214
215 /**
216 * Returns the $row for the page with uid = $uid (observing ->where_hid_del)
217 * Any pages_language_overlay will be applied before the result is returned.
218 * If no page is found an empty array is returned.
219 *
220 * @param integer $uid The page id to look up.
221 * @param boolean $disableGroupAccessCheck If set, the check for group access is disabled. VERY rarely used
222 * @throws \UnexpectedValueException
223 * @return array The page row with overlayed localized fields. Empty it no page.
224 * @see getPage_noCheck()
225 * @todo Define visibility
226 */
227 public function getPage($uid, $disableGroupAccessCheck = FALSE) {
228 // Hook to manipulate the page uid for special overlay handling
229 if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_page.php']['getPage'])) {
230 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_page.php']['getPage'] as $classRef) {
231 $hookObject = GeneralUtility::getUserObj($classRef);
232 if (!$hookObject instanceof \TYPO3\CMS\Frontend\Page\PageRepositoryGetPageHookInterface) {
233 throw new \UnexpectedValueException('$hookObject must implement interface TYPO3\\CMS\\Frontend\\Page\\PageRepositoryGetPageHookInterface', 1251476766);
234 }
235 $hookObject->getPage_preProcess($uid, $disableGroupAccessCheck, $this);
236 }
237 }
238 $accessCheck = $disableGroupAccessCheck ? '' : $this->where_groupAccess;
239 $cacheKey = md5($accessCheck . '-' . $this->where_hid_del . '-' . $this->sys_language_uid);
240 if (is_array($this->cache_getPage[$uid][$cacheKey])) {
241 return $this->cache_getPage[$uid][$cacheKey];
242 }
243 $result = array();
244 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('*', 'pages', 'uid=' . (int)$uid . $this->where_hid_del . $accessCheck);
245 $row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res);
246 $GLOBALS['TYPO3_DB']->sql_free_result($res);
247 if ($row) {
248 $this->versionOL('pages', $row);
249 if (is_array($row)) {
250 $result = $this->getPageOverlay($row);
251 }
252 }
253 $this->cache_getPage[$uid][$cacheKey] = $result;
254 return $result;
255 }
256
257 /**
258 * Return the $row for the page with uid = $uid WITHOUT checking for
259 * ->where_hid_del (start- and endtime or hidden). Only "deleted" is checked!
260 *
261 * @param integer $uid The page id to look up
262 * @return array The page row with overlayed localized fields. Empty array if no page.
263 * @see getPage()
264 * @todo Define visibility
265 */
266 public function getPage_noCheck($uid) {
267 if ($this->cache_getPage_noCheck[$uid]) {
268 return $this->cache_getPage_noCheck[$uid];
269 }
270 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('*', 'pages', 'uid=' . (int)$uid . $this->deleteClause('pages'));
271 $row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res);
272 $GLOBALS['TYPO3_DB']->sql_free_result($res);
273 $result = array();
274 if ($row) {
275 $this->versionOL('pages', $row);
276 if (is_array($row)) {
277 $result = $this->getPageOverlay($row);
278 }
279 }
280 $this->cache_getPage_noCheck[$uid] = $result;
281 return $result;
282 }
283
284 /**
285 * Returns the $row of the first web-page in the tree (for the default menu...)
286 *
287 * @param integer $uid The page id for which to fetch first subpages (PID)
288 * @return mixed If found: The page record (with overlayed localized fields, if any). If NOT found: blank value (not array!)
289 * @see \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController::fetch_the_id()
290 * @todo Define visibility
291 */
292 public function getFirstWebPage($uid) {
293 $output = '';
294 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('*', 'pages', 'pid=' . (int)$uid . $this->where_hid_del . $this->where_groupAccess, '', 'sorting', '1');
295 $row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res);
296 $GLOBALS['TYPO3_DB']->sql_free_result($res);
297 if ($row) {
298 $this->versionOL('pages', $row);
299 if (is_array($row)) {
300 $output = $this->getPageOverlay($row);
301 }
302 }
303 return $output;
304 }
305
306 /**
307 * Returns a pagerow for the page with alias $alias
308 *
309 * @param string $alias The alias to look up the page uid for.
310 * @return integer Returns page uid (integer) if found, otherwise 0 (zero)
311 * @see \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController::checkAndSetAlias(), ContentObjectRenderer::typoLink()
312 * @todo Define visibility
313 */
314 public function getPageIdFromAlias($alias) {
315 $alias = strtolower($alias);
316 if ($this->cache_getPageIdFromAlias[$alias]) {
317 return $this->cache_getPageIdFromAlias[$alias];
318 }
319 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('uid', 'pages', 'alias=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($alias, 'pages') . ' AND pid>=0 AND pages.deleted=0');
320 // "AND pid>=0" because of versioning (means that aliases sent MUST be online!)
321 $row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res);
322 $GLOBALS['TYPO3_DB']->sql_free_result($res);
323 if ($row) {
324 $this->cache_getPageIdFromAlias[$alias] = $row['uid'];
325 return $row['uid'];
326 }
327 $this->cache_getPageIdFromAlias[$alias] = 0;
328 return 0;
329 }
330
331 /**
332 * Returns the relevant page overlay record fields
333 *
334 * @param mixed $pageInput If $pageInput is an integer, it's the pid of the pageOverlay record and thus the page overlay record is returned. If $pageInput is an array, it's a page-record and based on this page record the language record is found and OVERLAYED before the page record is returned.
335 * @param integer $lUid Language UID if you want to set an alternative value to $this->sys_language_uid which is default. Should be >=0
336 * @throws \UnexpectedValueException
337 * @return array Page row which is overlayed with language_overlay record (or the overlay record alone)
338 * @todo Define visibility
339 */
340 public function getPageOverlay($pageInput, $lUid = -1) {
341 // Initialize:
342 if ($lUid < 0) {
343 $lUid = $this->sys_language_uid;
344 }
345 $row = NULL;
346 if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_page.php']['getPageOverlay'])) {
347 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_page.php']['getPageOverlay'] as $classRef) {
348 $hookObject = GeneralUtility::getUserObj($classRef);
349 if (!$hookObject instanceof \TYPO3\CMS\Frontend\Page\PageRepositoryGetPageOverlayHookInterface) {
350 throw new \UnexpectedValueException('$hookObject must implement interface TYPO3\\CMS\\Frontend\\Page\\PageRepositoryGetPageOverlayHookInterface', 1269878881);
351 }
352 $hookObject->getPageOverlay_preProcess($pageInput, $lUid, $this);
353 }
354 }
355 // If language UID is different from zero, do overlay:
356 if ($lUid) {
357 $fieldArr = GeneralUtility::trimExplode(',', $GLOBALS['TYPO3_CONF_VARS']['FE']['pageOverlayFields']);
358 if (is_array($pageInput)) {
359 // Was the whole record
360 $page_id = $pageInput['uid'];
361 // Make sure that only fields which exist in the incoming record are overlaid!
362 $fieldArr = array_intersect($fieldArr, array_keys($pageInput));
363 } else {
364 // Was the id
365 $page_id = $pageInput;
366 }
367 if (count($fieldArr)) {
368 // NOTE to enabledFields('pages_language_overlay'):
369 // Currently the showHiddenRecords of TSFE set will allow
370 // pages_language_overlay records to be selected as they are
371 // child-records of a page.
372 // However you may argue that the showHiddenField flag should
373 // determine this. But that's not how it's done right now.
374 // Selecting overlay record:
375 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery(implode(',', $fieldArr), 'pages_language_overlay', 'pid=' . (int)$page_id . '
376 AND sys_language_uid=' . (int)$lUid . $this->enableFields('pages_language_overlay'), '', '', '1');
377 $row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res);
378 $GLOBALS['TYPO3_DB']->sql_free_result($res);
379 $this->versionOL('pages_language_overlay', $row);
380 if (is_array($row)) {
381 $row['_PAGES_OVERLAY'] = TRUE;
382 $row['_PAGES_OVERLAY_UID'] = $row['uid'];
383 $row['_PAGES_OVERLAY_LANGUAGE'] = $lUid;
384 // Unset vital fields that are NOT allowed to be overlaid:
385 unset($row['uid']);
386 unset($row['pid']);
387 }
388 }
389 }
390 // Create output:
391 if (is_array($pageInput)) {
392 if (is_array($row)) {
393 // Overwrite the original field with the overlay
394 foreach ($row as $fieldName => $fieldValue) {
395 if ($fieldName !== 'uid' && $fieldName !== 'pid') {
396 if ($this->shouldFieldBeOverlaid('pages_language_overlay', $fieldName, $fieldValue)) {
397 $pageInput[$fieldName] = $fieldValue;
398 }
399 }
400 }
401 }
402 return $pageInput;
403 } else {
404 // Always an array in return
405 return is_array($row) ? $row : array();
406 }
407 }
408
409 /**
410 * Creates language-overlay for records in general (where translation is found
411 * in records from the same table)
412 *
413 * @param string $table Table name
414 * @param array $row Record to overlay. Must containt uid, pid and $table]['ctrl']['languageField']
415 * @param integer $sys_language_content Pointer to the sys_language uid for content on the site.
416 * @param string $OLmode Overlay mode. If "hideNonTranslated" then records without translation will not be returned un-translated but unset (and return value is FALSE)
417 * @throws \UnexpectedValueException
418 * @return mixed Returns the input record, possibly overlaid with a translation. But if $OLmode is "hideNonTranslated" then it will return FALSE if no translation is found.
419 * @todo Define visibility
420 */
421 public function getRecordOverlay($table, $row, $sys_language_content, $OLmode = '') {
422 if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_page.php']['getRecordOverlay'])) {
423 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_page.php']['getRecordOverlay'] as $classRef) {
424 $hookObject = GeneralUtility::getUserObj($classRef);
425 if (!$hookObject instanceof \TYPO3\CMS\Frontend\Page\PageRepositoryGetRecordOverlayHookInterface) {
426 throw new \UnexpectedValueException('$hookObject must implement interface TYPO3\\CMS\\Frontend\\Page\\PageRepositoryGetRecordOverlayHookInterface', 1269881658);
427 }
428 $hookObject->getRecordOverlay_preProcess($table, $row, $sys_language_content, $OLmode, $this);
429 }
430 }
431 if ($row['uid'] > 0 && ($row['pid'] > 0 || in_array($table, $this->tableNamesAllowedOnRootLevel))) {
432 if ($GLOBALS['TCA'][$table] && $GLOBALS['TCA'][$table]['ctrl']['languageField'] && $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']) {
433 if (!$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerTable']) {
434 // Will not be able to work with other tables (Just didn't implement it yet;
435 // Requires a scan over all tables [ctrl] part for first FIND the table that
436 // carries localization information for this table (which could even be more
437 // than a single table) and then use that. Could be implemented, but obviously
438 // takes a little more....) Will try to overlay a record only if the
439 // sys_language_content value is larger than zero.
440 if ($sys_language_content > 0) {
441 // Must be default language or [All], otherwise no overlaying:
442 if ($row[$GLOBALS['TCA'][$table]['ctrl']['languageField']] <= 0) {
443 // Select overlay record:
444 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('*', $table, 'pid=' . (int)$row['pid'] . ' AND ' . $GLOBALS['TCA'][$table]['ctrl']['languageField'] . '=' . (int)$sys_language_content . ' AND ' . $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'] . '=' . (int)$row['uid'] . $this->enableFields($table), '', '', '1');
445 $olrow = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res);
446 $GLOBALS['TYPO3_DB']->sql_free_result($res);
447 $this->versionOL($table, $olrow);
448 // Merge record content by traversing all fields:
449 if (is_array($olrow)) {
450 if (isset($olrow['_ORIG_uid'])) {
451 $row['_ORIG_uid'] = $olrow['_ORIG_uid'];
452 }
453 if (isset($olrow['_ORIG_pid'])) {
454 $row['_ORIG_pid'] = $olrow['_ORIG_pid'];
455 }
456 foreach ($row as $fN => $fV) {
457 if ($fN != 'uid' && $fN != 'pid' && isset($olrow[$fN])) {
458 if ($this->shouldFieldBeOverlaid($table, $fN, $olrow[$fN])) {
459 $row[$fN] = $olrow[$fN];
460 }
461 } elseif ($fN == 'uid') {
462 $row['_LOCALIZED_UID'] = $olrow['uid'];
463 }
464 }
465 } elseif ($OLmode === 'hideNonTranslated' && $row[$GLOBALS['TCA'][$table]['ctrl']['languageField']] == 0) {
466 // Unset, if non-translated records should be hidden. ONLY done if the source
467 // record really is default language and not [All] in which case it is allowed.
468 unset($row);
469 }
470 } elseif ($sys_language_content != $row[$GLOBALS['TCA'][$table]['ctrl']['languageField']]) {
471 unset($row);
472 }
473 } else {
474 // When default language is displayed, we never want to return a record carrying
475 // another language!
476 if ($row[$GLOBALS['TCA'][$table]['ctrl']['languageField']] > 0) {
477 unset($row);
478 }
479 }
480 }
481 }
482 }
483 if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_page.php']['getRecordOverlay'])) {
484 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_page.php']['getRecordOverlay'] as $classRef) {
485 $hookObject = GeneralUtility::getUserObj($classRef);
486 if (!$hookObject instanceof \TYPO3\CMS\Frontend\Page\PageRepositoryGetRecordOverlayHookInterface) {
487 throw new \UnexpectedValueException('$hookObject must implement interface TYPO3\\CMS\\Frontend\\Page\\PageRepositoryGetRecordOverlayHookInterface', 1269881659);
488 }
489 $hookObject->getRecordOverlay_postProcess($table, $row, $sys_language_content, $OLmode, $this);
490 }
491 }
492 return $row;
493 }
494
495 /************************************************
496 *
497 * Page related: Menu, Domain record, Root line
498 *
499 ************************************************/
500
501 /**
502 * Returns an array with pagerows for subpages with pid=$uid (which is pid
503 * here!). This is used for menus. If there are mount points in overlay mode
504 * the _MP_PARAM field is set to the corret MPvar.
505 *
506 * If the $uid being input does in itself require MPvars to define a correct
507 * rootline these must be handled externally to this function.
508 *
509 * @param integer $uid The page id for which to fetch subpages (PID)
510 * @param string $fields List of fields to select. Default is "*" = all
511 * @param string $sortField The field to sort by. Default is "sorting
512 * @param string $addWhere Optional additional where clauses. Like "AND title like '%blabla%'" for instance.
513 * @param boolean $checkShortcuts Check if shortcuts exist, checks by default
514 * @return array Array with key/value pairs; keys are page-uid numbers. values are the corresponding page records (with overlayed localized fields, if any)
515 * @see \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController::getPageShortcut(), \TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject::makeMenu()
516 * @see \TYPO3\CMS\WizardCrpages\Controller\CreatePagesWizardModuleFunctionController, \TYPO3\CMS\WizardSortpages\View\SortPagesWizardModuleFunction
517 * @todo Define visibility
518 */
519 public function getMenu($uid, $fields = '*', $sortField = 'sorting', $addWhere = '', $checkShortcuts = TRUE) {
520 $output = array();
521 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery($fields, 'pages', 'pid=' . (int)$uid . $this->where_hid_del . $this->where_groupAccess . ' ' . $addWhere, '', $sortField);
522 while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
523 $this->versionOL('pages', $row, TRUE);
524 if (is_array($row)) {
525 // Keep mount point:
526 $origUid = $row['uid'];
527 // $row MUST have "uid", "pid", "doktype", "mount_pid", "mount_pid_ol" fields
528 // in it
529 $mount_info = $this->getMountPointInfo($origUid, $row);
530 // There is a valid mount point.
531 if (is_array($mount_info) && $mount_info['overlay']) {
532 // Using "getPage" is OK since we need the check for enableFields AND for type 2
533 // of mount pids we DO require a doktype < 200!
534 $mp_row = $this->getPage($mount_info['mount_pid']);
535 if (count($mp_row)) {
536 $row = $mp_row;
537 $row['_MP_PARAM'] = $mount_info['MPvar'];
538 } else {
539 unset($row);
540 }
541 }
542 // If shortcut, look up if the target exists and is currently visible
543 if ($row['doktype'] == self::DOKTYPE_SHORTCUT && ($row['shortcut'] || $row['shortcut_mode']) && $checkShortcuts) {
544 if ($row['shortcut_mode'] == self::SHORTCUT_MODE_NONE) {
545 // No shortcut_mode set, so target is directly set in $row['shortcut']
546 $searchField = 'uid';
547 $searchUid = (int)$row['shortcut'];
548 } elseif ($row['shortcut_mode'] == self::SHORTCUT_MODE_FIRST_SUBPAGE || $row['shortcut_mode'] == self::SHORTCUT_MODE_RANDOM_SUBPAGE) {
549 // Check subpages - first subpage or random subpage
550 $searchField = 'pid';
551 // If a shortcut mode is set and no valid page is given to select subpags
552 // from use the actual page.
553 $searchUid = (int)$row['shortcut'] ?: $row['uid'];
554 } elseif ($row['shortcut_mode'] == self::SHORTCUT_MODE_PARENT_PAGE) {
555 // Shortcut to parent page
556 $searchField = 'uid';
557 $searchUid = $row['pid'];
558 }
559 $count = $GLOBALS['TYPO3_DB']->exec_SELECTcountRows('uid', 'pages', $searchField . '=' . $searchUid . $this->where_hid_del . $this->where_groupAccess . ' ' . $addWhere);
560 if (!$count) {
561 unset($row);
562 }
563 } elseif ($row['doktype'] == self::DOKTYPE_SHORTCUT && $checkShortcuts) {
564 // Neither shortcut target nor mode is set. Remove the page from the menu.
565 unset($row);
566 }
567 // Add to output array after overlaying language:
568 if (is_array($row)) {
569 $output[$origUid] = $this->getPageOverlay($row);
570 }
571 }
572 }
573 $GLOBALS['TYPO3_DB']->sql_free_result($res);
574 return $output;
575 }
576
577 /**
578 * Will find the page carrying the domain record matching the input domain.
579 * Might exit after sending a redirect-header IF a found domain record
580 * instructs to do so.
581 *
582 * @param string $domain Domain name to search for. Eg. "www.typo3.com". Typical the HTTP_HOST value.
583 * @param string $path Path for the current script in domain. Eg. "/somedir/subdir". Typ. supplied by \TYPO3\CMS\Core\Utility\GeneralUtility::getIndpEnv('SCRIPT_NAME')
584 * @param string $request_uri Request URI: Used to get parameters from if they should be appended. Typ. supplied by \TYPO3\CMS\Core\Utility\GeneralUtility::getIndpEnv('REQUEST_URI')
585 * @return mixed If found, returns integer with page UID where found. Otherwise blank. Might exit if location-header is sent, see description.
586 * @see \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController::findDomainRecord()
587 * @todo Define visibility
588 */
589 public function getDomainStartPage($domain, $path = '', $request_uri = '') {
590 $domain = explode(':', $domain);
591 $domain = strtolower(preg_replace('/\\.$/', '', $domain[0]));
592 // Removing extra trailing slashes
593 $path = trim(preg_replace('/\\/[^\\/]*$/', '', $path));
594 // Appending to domain string
595 $domain .= $path;
596 $domain = preg_replace('/\\/*$/', '', $domain);
597 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('pages.uid,sys_domain.redirectTo,sys_domain.redirectHttpStatusCode,sys_domain.prepend_params', 'pages,sys_domain', 'pages.uid=sys_domain.pid
598 AND sys_domain.hidden=0
599 AND (sys_domain.domainName=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($domain, 'sys_domain') . ' OR sys_domain.domainName=' . $GLOBALS['TYPO3_DB']->fullQuoteStr(($domain . '/'), 'sys_domain') . ') ' . $this->where_hid_del . $this->where_groupAccess, '', '', 1);
600 $row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res);
601 $GLOBALS['TYPO3_DB']->sql_free_result($res);
602 if ($row) {
603 if ($row['redirectTo']) {
604 $redirectUrl = $row['redirectTo'];
605 if ($row['prepend_params']) {
606 $redirectUrl = rtrim($redirectUrl, '/');
607 $prependStr = ltrim(substr($request_uri, strlen($path)), '/');
608 $redirectUrl .= '/' . $prependStr;
609 }
610 $statusCode = (int)$row['redirectHttpStatusCode'];
611 if ($statusCode && defined('TYPO3\\CMS\\Core\\Utility\\HttpUtility::HTTP_STATUS_' . $statusCode)) {
612 \TYPO3\CMS\Core\Utility\HttpUtility::redirect($redirectUrl, constant('TYPO3\\CMS\\Core\\Utility\\HttpUtility::HTTP_STATUS_' . $statusCode));
613 } else {
614 \TYPO3\CMS\Core\Utility\HttpUtility::redirect($redirectUrl, \TYPO3\CMS\Core\Utility\HttpUtility::HTTP_STATUS_301);
615 }
616 die;
617 } else {
618 return $row['uid'];
619 }
620 }
621 }
622
623 /**
624 * Returns array with fields of the pages from here ($uid) and back to the root
625 *
626 * NOTICE: This function only takes deleted pages into account! So hidden,
627 * starttime and endtime restricted pages are included no matter what.
628 *
629 * Further: If any "recycler" page is found (doktype=255) then it will also block
630 * for the rootline)
631 *
632 * If you want more fields in the rootline records than default such can be added
633 * by listing them in $GLOBALS['TYPO3_CONF_VARS']['FE']['addRootLineFields']
634 *
635 * @param integer $uid The page uid for which to seek back to the page tree root.
636 * @param string $MP Commalist of MountPoint parameters, eg. "1-2,3-4" etc. Normally this value comes from the GET var, MP
637 * @param boolean $ignoreMPerrors If set, some errors related to Mount Points in root line are ignored.
638 * @throws \Exception
639 * @throws \RuntimeException
640 * @return array Array with page records from the root line as values. The array is ordered with the outer records first and root record in the bottom. The keys are numeric but in reverse order. So if you traverse/sort the array by the numeric keys order you will get the order from root and out. If an error is found (like eternal looping or invalid mountpoint) it will return an empty array.
641 * @see \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController::getPageAndRootline()
642 * @todo Define visibility
643 */
644 public function getRootLine($uid, $MP = '', $ignoreMPerrors = FALSE) {
645 $rootline = GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Utility\\RootlineUtility', $uid, $MP, $this);
646 try {
647 return $rootline->get();
648 } catch (\RuntimeException $ex) {
649 if ($ignoreMPerrors) {
650 $this->error_getRootLine = $ex->getMessage();
651 if (substr($this->error_getRootLine, -7) == 'uid -1.') {
652 $this->error_getRootLine_failPid = -1;
653 }
654 return array();
655 /** @see \TYPO3\CMS\Core\Utility\RootlineUtility::getRecordArray */
656 } elseif ($ex->getCode() === 1343589451) {
657 return array();
658 }
659 throw $ex;
660 }
661 }
662
663 /**
664 * Creates a "path" string for the input root line array titles.
665 * Used for writing statistics.
666 *
667 * @param array $rl A rootline array!
668 * @param integer $len The max length of each title from the rootline.
669 * @return string The path in the form "/page title/This is another pageti.../Another page
670 * @see \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController::getConfigArray()
671 * @todo Define visibility
672 */
673 public function getPathFromRootline($rl, $len = 20) {
674 if (is_array($rl)) {
675 $c = count($rl);
676 $path = '';
677 for ($a = 0; $a < $c; $a++) {
678 if ($rl[$a]['uid']) {
679 $path .= '/' . GeneralUtility::fixed_lgd_cs(strip_tags($rl[$a]['title']), $len);
680 }
681 }
682 return $path;
683 }
684 }
685
686 /**
687 * Returns the URL type for the input page row IF the doktype is 3 and not
688 * disabled.
689 *
690 * @param array $pagerow The page row to return URL type for
691 * @param boolean $disable A flag to simply disable any output from here.
692 * @return string The URL type from $this->urltypes array. False if not found or disabled.
693 * @see \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController::setExternalJumpUrl()
694 * @todo Define visibility
695 */
696 public function getExtURL($pagerow, $disable = 0) {
697 if ($pagerow['doktype'] == self::DOKTYPE_LINK && !$disable) {
698 $redirectTo = $this->urltypes[$pagerow['urltype']] . $pagerow['url'];
699 // If relative path, prefix Site URL:
700 $uI = parse_url($redirectTo);
701 // Relative path assumed now.
702 if (!$uI['scheme'] && $redirectTo[0] !== '/') {
703 $redirectTo = GeneralUtility::getIndpEnv('TYPO3_SITE_URL') . $redirectTo;
704 }
705 return $redirectTo;
706 }
707 }
708
709 /**
710 * Returns MountPoint id for page
711 *
712 * Does a recursive search if the mounted page should be a mount page itself. It
713 * has a run-away break so it can't go into infinite loops.
714 *
715 * @param integer $pageId Page id for which to look for a mount pid. Will be returned only if mount pages are enabled, the correct doktype (7) is set for page and there IS a mount_pid (which has a valid record that is not deleted...)
716 * @param array $pageRec Optional page record for the page id. If not supplied it will be looked up by the system. Must contain at least uid,pid,doktype,mount_pid,mount_pid_ol
717 * @param array $prevMountPids Array accumulating formerly tested page ids for mount points. Used for recursivity brake.
718 * @param integer $firstPageUid The first page id.
719 * @return mixed Returns FALSE if no mount point was found, "-1" if there should have been one, but no connection to it, otherwise an array with information about mount pid and modes.
720 * @see \TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject
721 * @todo Define visibility
722 */
723 public function getMountPointInfo($pageId, $pageRec = FALSE, $prevMountPids = array(), $firstPageUid = 0) {
724 $result = FALSE;
725 if ($GLOBALS['TYPO3_CONF_VARS']['FE']['enable_mount_pids']) {
726 if (isset($this->cache_getMountPointInfo[$pageId])) {
727 return $this->cache_getMountPointInfo[$pageId];
728 }
729 // Get pageRec if not supplied:
730 if (!is_array($pageRec)) {
731 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('uid,pid,doktype,mount_pid,mount_pid_ol,t3ver_state', 'pages', 'uid=' . (int)$pageId . ' AND pages.deleted=0 AND pages.doktype<>255');
732 $pageRec = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res);
733 $GLOBALS['TYPO3_DB']->sql_free_result($res);
734 // Only look for version overlay if page record is not supplied; This assumes
735 // that the input record is overlaid with preview version, if any!
736 $this->versionOL('pages', $pageRec);
737 }
738 // Set first Page uid:
739 if (!$firstPageUid) {
740 $firstPageUid = $pageRec['uid'];
741 }
742 // Look for mount pid value plus other required circumstances:
743 $mount_pid = (int)$pageRec['mount_pid'];
744 if (is_array($pageRec) && $pageRec['doktype'] == self::DOKTYPE_MOUNTPOINT && $mount_pid > 0 && !in_array($mount_pid, $prevMountPids)) {
745 // Get the mount point record (to verify its general existence):
746 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('uid,pid,doktype,mount_pid,mount_pid_ol,t3ver_state', 'pages', 'uid=' . $mount_pid . ' AND pages.deleted=0 AND pages.doktype<>255');
747 $mountRec = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res);
748 $GLOBALS['TYPO3_DB']->sql_free_result($res);
749 $this->versionOL('pages', $mountRec);
750 if (is_array($mountRec)) {
751 // Look for recursive mount point:
752 $prevMountPids[] = $mount_pid;
753 $recursiveMountPid = $this->getMountPointInfo($mount_pid, $mountRec, $prevMountPids, $firstPageUid);
754 // Return mount point information:
755 $result = $recursiveMountPid ?: array(
756 'mount_pid' => $mount_pid,
757 'overlay' => $pageRec['mount_pid_ol'],
758 'MPvar' => $mount_pid . '-' . $firstPageUid,
759 'mount_point_rec' => $pageRec,
760 'mount_pid_rec' => $mountRec
761 );
762 } else {
763 // Means, there SHOULD have been a mount point, but there was none!
764 $result = -1;
765 }
766 }
767 }
768 $this->cache_getMountPointInfo[$pageId] = $result;
769 return $result;
770 }
771
772 /********************************
773 *
774 * Selecting records in general
775 *
776 ********************************/
777
778 /**
779 * Checks if a record exists and is accessible.
780 * The row is returned if everything's OK.
781 *
782 * @param string $table The table name to search
783 * @param integer $uid The uid to look up in $table
784 * @param boolean $checkPage If checkPage is set, it's also required that the page on which the record resides is accessible
785 * @return mixed Returns array (the record) if OK, otherwise blank/0 (zero)
786 * @todo Define visibility
787 */
788 public function checkRecord($table, $uid, $checkPage = 0) {
789 $uid = (int)$uid;
790 if (is_array($GLOBALS['TCA'][$table]) && $uid > 0) {
791 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('*', $table, 'uid = ' . $uid . $this->enableFields($table));
792 $row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res);
793 $GLOBALS['TYPO3_DB']->sql_free_result($res);
794 if ($row) {
795 $this->versionOL($table, $row);
796 if (is_array($row)) {
797 if ($checkPage) {
798 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('uid', 'pages', 'uid=' . (int)$row['pid'] . $this->enableFields('pages'));
799 $numRows = $GLOBALS['TYPO3_DB']->sql_num_rows($res);
800 $GLOBALS['TYPO3_DB']->sql_free_result($res);
801 if ($numRows > 0) {
802 return $row;
803 } else {
804 return 0;
805 }
806 } else {
807 return $row;
808 }
809 }
810 }
811 }
812 }
813
814 /**
815 * Returns record no matter what - except if record is deleted
816 *
817 * @param string $table The table name to search
818 * @param integer $uid The uid to look up in $table
819 * @param string $fields The fields to select, default is "*
820 * @param boolean $noWSOL If set, no version overlay is applied
821 * @return mixed Returns array (the record) if found, otherwise blank/0 (zero)
822 * @see getPage_noCheck()
823 * @todo Define visibility
824 */
825 public function getRawRecord($table, $uid, $fields = '*', $noWSOL = FALSE) {
826 $uid = (int)$uid;
827 // Excluding pages here so we can ask the function BEFORE TCA gets initialized.
828 // Support for this is followed up in deleteClause()...
829 if ((is_array($GLOBALS['TCA'][$table]) || $table == 'pages') && $uid > 0) {
830 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery($fields, $table, 'uid = ' . $uid . $this->deleteClause($table));
831 $row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res);
832 $GLOBALS['TYPO3_DB']->sql_free_result($res);
833 if ($row) {
834 if (!$noWSOL) {
835 $this->versionOL($table, $row);
836 }
837 if (is_array($row)) {
838 return $row;
839 }
840 }
841 }
842 }
843
844 /**
845 * Selects records based on matching a field (ei. other than UID) with a value
846 *
847 * @param string $theTable The table name to search, eg. "pages" or "tt_content
848 * @param string $theField The fieldname to match, eg. "uid" or "alias
849 * @param string $theValue The value that fieldname must match, eg. "123" or "frontpage
850 * @param string $whereClause Optional additional WHERE clauses put in the end of the query. DO NOT PUT IN GROUP BY, ORDER BY or LIMIT!
851 * @param string $groupBy Optional GROUP BY field(s). If none, supply blank string.
852 * @param string $orderBy Optional ORDER BY field(s). If none, supply blank string.
853 * @param string $limit Optional LIMIT value ([begin,]max). If none, supply blank string.
854 * @return mixed Returns array (the record) if found, otherwise nothing (void)
855 * @todo Define visibility
856 */
857 public function getRecordsByField($theTable, $theField, $theValue, $whereClause = '', $groupBy = '', $orderBy = '', $limit = '') {
858 if (is_array($GLOBALS['TCA'][$theTable])) {
859 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('*', $theTable, $theField . '=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($theValue, $theTable) . $this->deleteClause($theTable) . ' ' . $whereClause, $groupBy, $orderBy, $limit);
860 $rows = array();
861 while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
862 if (is_array($row)) {
863 $rows[] = $row;
864 }
865 }
866 $GLOBALS['TYPO3_DB']->sql_free_result($res);
867 if (count($rows)) {
868 return $rows;
869 }
870 }
871 }
872
873 /********************************
874 *
875 * Caching and standard clauses
876 *
877 ********************************/
878
879 /**
880 * Returns data stored for the hash string in the cache "cache_hash"
881 * Can be used to retrieved a cached value, array or object
882 * Can be used from your frontend plugins if you like. It is also used to
883 * store the parsed TypoScript template structures. You can call it directly
884 * like \TYPO3\CMS\Frontend\Page\PageRepository::getHash()
885 *
886 * @param string $hash The hash-string which was used to store the data value
887 * @param integer The expiration time (not used anymore)
888 * @return mixed The "data" from the cache
889 * @see tslib_TStemplate::start(), storeHash()
890 */
891 static public function getHash($hash, $expTime = 0) {
892 $hashContent = NULL;
893 $contentHashCache = GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Cache\\CacheManager')->getCache('cache_hash');
894 $cacheEntry = $contentHashCache->get($hash);
895 if ($cacheEntry) {
896 $hashContent = $cacheEntry;
897 }
898 return $hashContent;
899 }
900
901 /**
902 * Stores $data in the 'cache_hash' cache with the hash key, $hash
903 * and visual/symbolic identification, $ident
904 *
905 * Can be used from your frontend plugins if you like. You can call it
906 * directly like \TYPO3\CMS\Frontend\Page\PageRepository::storeHash()
907 *
908 * @param string $hash 32 bit hash string (eg. a md5 hash of a serialized array identifying the data being stored)
909 * @param mixed $data The data to store
910 * @param string $ident Is just a textual identification in order to inform about the content!
911 * @param integer $lifetime The lifetime for the cache entry in seconds
912 * @return void
913 * @see tslib_TStemplate::start(), getHash()
914 */
915 static public function storeHash($hash, $data, $ident, $lifetime = 0) {
916 GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Cache\\CacheManager')->getCache('cache_hash')->set($hash, $data, array('ident_' . $ident), (int)$lifetime);
917 }
918
919 /**
920 * Returns the "AND NOT deleted" clause for the tablename given IF
921 * $GLOBALS['TCA'] configuration points to such a field.
922 *
923 * @param string $table Tablename
924 * @return string
925 * @see enableFields()
926 * @todo Define visibility
927 */
928 public function deleteClause($table) {
929 // Hardcode for pages because TCA might not be loaded yet (early frontend
930 // initialization)
931 if ($table === 'pages') {
932 return ' AND pages.deleted=0';
933 } else {
934 return $GLOBALS['TCA'][$table]['ctrl']['delete'] ? ' AND ' . $table . '.' . $GLOBALS['TCA'][$table]['ctrl']['delete'] . '=0' : '';
935 }
936 }
937
938 /**
939 * Returns a part of a WHERE clause which will filter out records with start/end
940 * times or hidden/fe_groups fields set to values that should de-select them
941 * according to the current time, preview settings or user login. Definitely a
942 * frontend function.
943 *
944 * Is using the $GLOBALS['TCA'] arrays "ctrl" part where the key "enablefields"
945 * determines for each table which of these features applies to that table.
946 *
947 * @param string $table Table name found in the $GLOBALS['TCA'] array
948 * @param integer $show_hidden If $show_hidden is set (0/1), any hidden-fields in records are ignored. NOTICE: If you call this function, consider what to do with the show_hidden parameter. Maybe it should be set? See ContentObjectRenderer->enableFields where it's implemented correctly.
949 * @param array $ignore_array Array you can pass where keys can be "disabled", "starttime", "endtime", "fe_group" (keys from "enablefields" in TCA) and if set they will make sure that part of the clause is not added. Thus disables the specific part of the clause. For previewing etc.
950 * @param boolean $noVersionPreview If set, enableFields will be applied regardless of any versioning preview settings which might otherwise disable enableFields
951 * @throws \InvalidArgumentException
952 * @return string The clause starting like " AND ...=... AND ...=...
953 * @see \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer::enableFields(), deleteClause()
954 * @todo Define visibility
955 */
956 public function enableFields($table, $show_hidden = -1, $ignore_array = array(), $noVersionPreview = FALSE) {
957 if ($show_hidden == -1 && is_object($GLOBALS['TSFE'])) {
958 // If show_hidden was not set from outside and if TSFE is an object, set it
959 // based on showHiddenPage and showHiddenRecords from TSFE
960 $show_hidden = $table == 'pages' ? $GLOBALS['TSFE']->showHiddenPage : $GLOBALS['TSFE']->showHiddenRecords;
961 }
962 if ($show_hidden == -1) {
963 $show_hidden = 0;
964 }
965 // If show_hidden was not changed during the previous evaluation, do it here.
966 $ctrl = $GLOBALS['TCA'][$table]['ctrl'];
967 $query = '';
968 if (is_array($ctrl)) {
969 // Delete field check:
970 if ($ctrl['delete']) {
971 $query .= ' AND ' . $table . '.' . $ctrl['delete'] . '=0';
972 }
973 if ($ctrl['versioningWS']) {
974 if (!$this->versioningPreview) {
975 // Filter out placeholder records (new/moved/deleted items)
976 // in case we are NOT in a versioning preview (that means we are online!)
977 $query .= ' AND ' . $table . '.t3ver_state<=' . new VersionState(VersionState::DEFAULT_STATE);
978 } else {
979 if ($table !== 'pages') {
980 // show only records of live and of the current workspace
981 // in case we are in a versioning preview
982 $query .= ' AND (' .
983 $table . '.t3ver_wsid=0 OR ' .
984 $table . '.t3ver_wsid=' . (int)$this->versioningWorkspaceId .
985 ')';
986 }
987 }
988
989 // Filter out versioned records
990 if (!$noVersionPreview && empty($ignore_array['pid'])) {
991 $query .= ' AND ' . $table . '.pid<>-1';
992 }
993 }
994
995 // Enable fields:
996 if (is_array($ctrl['enablecolumns'])) {
997 // In case of versioning-preview, enableFields are ignored (checked in
998 // versionOL())
999 if (!$this->versioningPreview || !$ctrl['versioningWS'] || $noVersionPreview) {
1000 if ($ctrl['enablecolumns']['disabled'] && !$show_hidden && !$ignore_array['disabled']) {
1001 $field = $table . '.' . $ctrl['enablecolumns']['disabled'];
1002 $query .= ' AND ' . $field . '=0';
1003 }
1004 if ($ctrl['enablecolumns']['starttime'] && !$ignore_array['starttime']) {
1005 $field = $table . '.' . $ctrl['enablecolumns']['starttime'];
1006 $query .= ' AND ' . $field . '<=' . $GLOBALS['SIM_ACCESS_TIME'];
1007 }
1008 if ($ctrl['enablecolumns']['endtime'] && !$ignore_array['endtime']) {
1009 $field = $table . '.' . $ctrl['enablecolumns']['endtime'];
1010 $query .= ' AND (' . $field . '=0 OR ' . $field . '>' . $GLOBALS['SIM_ACCESS_TIME'] . ')';
1011 }
1012 if ($ctrl['enablecolumns']['fe_group'] && !$ignore_array['fe_group']) {
1013 $field = $table . '.' . $ctrl['enablecolumns']['fe_group'];
1014 $query .= $this->getMultipleGroupsWhereClause($field, $table);
1015 }
1016 // Call hook functions for additional enableColumns
1017 // It is used by the extension ingmar_accessctrl which enables assigning more
1018 // than one usergroup to content and page records
1019 if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_page.php']['addEnableColumns'])) {
1020 $_params = array(
1021 'table' => $table,
1022 'show_hidden' => $show_hidden,
1023 'ignore_array' => $ignore_array,
1024 'ctrl' => $ctrl
1025 );
1026 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_page.php']['addEnableColumns'] as $_funcRef) {
1027 $query .= GeneralUtility::callUserFunction($_funcRef, $_params, $this);
1028 }
1029 }
1030 }
1031 }
1032 } else {
1033 throw new \InvalidArgumentException('There is no entry in the $TCA array for the table "' . $table . '". This means that the function enableFields() is ' . 'called with an invalid table name as argument.', 1283790586);
1034 }
1035 return $query;
1036 }
1037
1038 /**
1039 * Creating where-clause for checking group access to elements in enableFields
1040 * function
1041 *
1042 * @param string $field Field with group list
1043 * @param string $table Table name
1044 * @return string AND sql-clause
1045 * @see enableFields()
1046 * @todo Define visibility
1047 */
1048 public function getMultipleGroupsWhereClause($field, $table) {
1049 $memberGroups = GeneralUtility::intExplode(',', $GLOBALS['TSFE']->gr_list);
1050 $orChecks = array();
1051 // If the field is empty, then OK
1052 $orChecks[] = $field . '=\'\'';
1053 // If the field is NULL, then OK
1054 $orChecks[] = $field . ' IS NULL';
1055 // If the field contsains zero, then OK
1056 $orChecks[] = $field . '=\'0\'';
1057 foreach ($memberGroups as $value) {
1058 $orChecks[] = $GLOBALS['TYPO3_DB']->listQuery($field, $value, $table);
1059 }
1060 return ' AND (' . implode(' OR ', $orChecks) . ')';
1061 }
1062
1063 /**********************
1064 *
1065 * Versioning Preview
1066 *
1067 **********************/
1068
1069 /**
1070 * Finding online PID for offline version record
1071 *
1072 * ONLY active when backend user is previewing records. MUST NEVER affect a site
1073 * served which is not previewed by backend users!!!
1074 *
1075 * Will look if the "pid" value of the input record is -1 (it is an offline
1076 * version) and if the table supports versioning; if so, it will translate the -1
1077 * PID into the PID of the original record.
1078 *
1079 * Used whenever you are tracking something back, like making the root line.
1080 *
1081 * Principle; Record offline! => Find online?
1082 *
1083 * @param string $table Table name
1084 * @param array $rr Record array passed by reference. As minimum, "pid" and "uid" fields must exist! "t3ver_oid" and "t3ver_wsid" is nice and will save you a DB query.
1085 * @return void (Passed by ref).
1086 * @see \TYPO3\CMS\Backend\Utility\BackendUtility::fixVersioningPid(), versionOL(), getRootLine()
1087 * @todo Define visibility
1088 */
1089 public function fixVersioningPid($table, &$rr) {
1090 if ($this->versioningPreview && is_array($rr) && $rr['pid'] == -1 && ($table == 'pages' || $GLOBALS['TCA'][$table]['ctrl']['versioningWS'])) {
1091 // Have to hardcode it for "pages" table since TCA is not loaded at this moment!
1092 // Check values for t3ver_oid and t3ver_wsid:
1093 if (isset($rr['t3ver_oid']) && isset($rr['t3ver_wsid'])) {
1094 // If "t3ver_oid" is already a field, just set this:
1095 $oid = $rr['t3ver_oid'];
1096 $wsid = $rr['t3ver_wsid'];
1097 } else {
1098 // Otherwise we have to expect "uid" to be in the record and look up based
1099 // on this:
1100 $newPidRec = $this->getRawRecord($table, $rr['uid'], 't3ver_oid,t3ver_wsid', TRUE);
1101 if (is_array($newPidRec)) {
1102 $oid = $newPidRec['t3ver_oid'];
1103 $wsid = $newPidRec['t3ver_wsid'];
1104 }
1105 }
1106 // If workspace ids matches and ID of current online version is found, look up
1107 // the PID value of that:
1108 if ($oid && ((int)$this->versioningWorkspaceId === 0 && $this->checkWorkspaceAccess($wsid) || (int)$wsid === (int)$this->versioningWorkspaceId)) {
1109 $oidRec = $this->getRawRecord($table, $oid, 'pid', TRUE);
1110 if (is_array($oidRec)) {
1111 // SWAP uid as well? Well no, because when fixing a versioning PID happens it is
1112 // assumed that this is a "branch" type page and therefore the uid should be
1113 // kept (like in versionOL()). However if the page is NOT a branch version it
1114 // should not happen - but then again, direct access to that uid should not
1115 // happen!
1116 $rr['_ORIG_pid'] = $rr['pid'];
1117 $rr['pid'] = $oidRec['pid'];
1118 }
1119 }
1120 }
1121 // Changing PID in case of moving pointer:
1122 if ($movePlhRec = $this->getMovePlaceholder($table, $rr['uid'], 'pid')) {
1123 $rr['pid'] = $movePlhRec['pid'];
1124 }
1125 }
1126
1127 /**
1128 * Versioning Preview Overlay
1129 *
1130 * ONLY active when backend user is previewing records. MUST NEVER affect a site
1131 * served which is not previewed by backend users!!!
1132 *
1133 * Generally ALWAYS used when records are selected based on uid or pid. If
1134 * records are selected on other fields than uid or pid (eg. "email = ....") then
1135 * usage might produce undesired results and that should be evaluated on
1136 * individual basis.
1137 *
1138 * Principle; Record online! => Find offline?
1139 *
1140 * @param string $table Table name
1141 * @param array $row Record array passed by reference. As minimum, the "uid", "pid" and "t3ver_state" fields must exist! The record MAY be set to FALSE in which case the calling function should act as if the record is forbidden to access!
1142 * @param boolean $unsetMovePointers If set, the $row is cleared in case it is a move-pointer. This is only for preview of moved records (to remove the record from the original location so it appears only in the new location)
1143 * @param boolean $bypassEnableFieldsCheck Unless this option is TRUE, the $row is unset if enablefields for BOTH the version AND the online record deselects it. This is because when versionOL() is called it is assumed that the online record is already selected with no regards to it's enablefields. However, after looking for a new version the online record enablefields must ALSO be evaluated of course. This is done all by this function!
1144 * @return void (Passed by ref).
1145 * @see fixVersioningPid(), \TYPO3\CMS\Backend\Utility\BackendUtility::workspaceOL()
1146 * @todo Define visibility
1147 */
1148 public function versionOL($table, &$row, $unsetMovePointers = FALSE, $bypassEnableFieldsCheck = FALSE) {
1149 if ($this->versioningPreview && is_array($row)) {
1150 // will overlay any movePlhOL found with the real record, which in turn
1151 // will be overlaid with its workspace version if any.
1152 $movePldSwap = $this->movePlhOL($table, $row);
1153 // implode(',',array_keys($row)) = Using fields from original record to make
1154 // sure no additional fields are selected. This is best for eg. getPageOverlay()
1155 if ($wsAlt = $this->getWorkspaceVersionOfRecord($this->versioningWorkspaceId, $table, $row['uid'], implode(',', array_keys($row)), $bypassEnableFieldsCheck)) {
1156 if (is_array($wsAlt)) {
1157 // Always fix PID (like in fixVersioningPid() above). [This is usually not
1158 // the important factor for versioning OL]
1159 // Keep the old (-1) - indicates it was a version...
1160 $wsAlt['_ORIG_pid'] = $wsAlt['pid'];
1161 // Set in the online versions PID.
1162 $wsAlt['pid'] = $row['pid'];
1163 // For versions of single elements or page+content, preserve online UID and PID
1164 // (this will produce true "overlay" of element _content_, not any references)
1165 // For page+content the "_ORIG_uid" should actually be used as PID for selection
1166 // of tables with "versioning_followPages" enabled.
1167 $wsAlt['_ORIG_uid'] = $wsAlt['uid'];
1168 $wsAlt['uid'] = $row['uid'];
1169 // Translate page alias as well so links are pointing to the _online_ page:
1170 if ($table === 'pages') {
1171 $wsAlt['alias'] = $row['alias'];
1172 }
1173 // Changing input record to the workspace version alternative:
1174 $row = $wsAlt;
1175 // Check if it is deleted/new
1176 $rowVersionState = VersionState::cast($row['t3ver_state']);
1177 if (
1178 $rowVersionState->equals(VersionState::NEW_PLACEHOLDER)
1179 || $rowVersionState->equals(VersionState::DELETE_PLACEHOLDER)
1180 ) {
1181 // Unset record if it turned out to be deleted in workspace
1182 $row = FALSE;
1183 }
1184 // Check if move-pointer in workspace (unless if a move-placeholder is the
1185 // reason why it appears!):
1186 // You have to specifically set $unsetMovePointers in order to clear these
1187 // because it is normally a display issue if it should be shown or not.
1188 if (
1189 ($rowVersionState->equals(VersionState::MOVE_POINTER)
1190 && !$movePldSwap
1191 ) && $unsetMovePointers
1192 ) {
1193 // Unset record if it turned out to be deleted in workspace
1194 $row = FALSE;
1195 }
1196 } else {
1197 // No version found, then check if t3ver_state = VersionState::NEW_PLACEHOLDER
1198 // (online version is dummy-representation)
1199 // Notice, that unless $bypassEnableFieldsCheck is TRUE, the $row is unset if
1200 // enablefields for BOTH the version AND the online record deselects it. See
1201 // note for $bypassEnableFieldsCheck
1202 if ($wsAlt <= -1 || VersionState::cast($row['t3ver_state'])->indicatesPlaceholder()) {
1203 // Unset record if it turned out to be "hidden"
1204 $row = FALSE;
1205 }
1206 }
1207 }
1208 }
1209 }
1210
1211 /**
1212 * Checks if record is a move-placeholder
1213 * (t3ver_state==VersionState::MOVE_PLACEHOLDER) and if so it will set $row to be
1214 * the pointed-to live record (and return TRUE) Used from versionOL
1215 *
1216 * @param string $table Table name
1217 * @param array $row Row (passed by reference) - only online records...
1218 * @return boolean TRUE if overlay is made.
1219 * @see \TYPO3\CMS\Backend\Utility\BackendUtility::movePlhOl()
1220 * @todo Define visibility
1221 */
1222 public function movePlhOL($table, &$row) {
1223 if (
1224 ($table == 'pages'
1225 || (int)$GLOBALS['TCA'][$table]['ctrl']['versioningWS'] >= 2
1226 ) && (int)VersionState::cast($row['t3ver_state'])->equals(VersionState::MOVE_PLACEHOLDER)
1227 ) {
1228 // Only for WS ver 2... (moving)
1229 // If t3ver_move_id is not found, then find it (but we like best if it is here)
1230 if (!isset($row['t3ver_move_id'])) {
1231 $moveIDRec = $this->getRawRecord($table, $row['uid'], 't3ver_move_id', TRUE);
1232 $moveID = $moveIDRec['t3ver_move_id'];
1233 } else {
1234 $moveID = $row['t3ver_move_id'];
1235 }
1236 // Find pointed-to record.
1237 if ($moveID) {
1238 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery(implode(',', array_keys($row)), $table, 'uid=' . (int)$moveID . $this->enableFields($table));
1239 $origRow = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res);
1240 $GLOBALS['TYPO3_DB']->sql_free_result($res);
1241 if ($origRow) {
1242 $row = $origRow;
1243 return TRUE;
1244 }
1245 }
1246 }
1247 return FALSE;
1248 }
1249
1250 /**
1251 * Returns move placeholder of online (live) version
1252 *
1253 * @param string $table Table name
1254 * @param integer $uid Record UID of online version
1255 * @param string $fields Field list, default is *
1256 * @return array If found, the record, otherwise nothing.
1257 * @see \TYPO3\CMS\Backend\Utility\BackendUtility::getMovePlaceholder()
1258 * @todo Define visibility
1259 */
1260 public function getMovePlaceholder($table, $uid, $fields = '*') {
1261 if ($this->versioningPreview) {
1262 $workspace = (int)$this->versioningWorkspaceId;
1263 if (($table == 'pages' || (int)$GLOBALS['TCA'][$table]['ctrl']['versioningWS'] >= 2) && $workspace !== 0) {
1264 // Select workspace version of record:
1265 $row = $GLOBALS['TYPO3_DB']->exec_SELECTgetSingleRow($fields, $table, 'pid<>-1 AND
1266 t3ver_state=' . new VersionState(VersionState::MOVE_PLACEHOLDER) . ' AND
1267 t3ver_move_id=' . (int)$uid . ' AND
1268 t3ver_wsid=' . (int)$workspace . $this->deleteClause($table));
1269 if (is_array($row)) {
1270 return $row;
1271 }
1272 }
1273 }
1274 return FALSE;
1275 }
1276
1277 /**
1278 * Select the version of a record for a workspace
1279 *
1280 * @param integer $workspace Workspace ID
1281 * @param string $table Table name to select from
1282 * @param integer $uid Record uid for which to find workspace version.
1283 * @param string $fields Field list to select
1284 * @param boolean $bypassEnableFieldsCheck If TRUE, enablefields are not checked for.
1285 * @return mixed If found, return record, otherwise other value: Returns 1 if version was sought for but not found, returns -1/-2 if record (offline/online) existed but had enableFields that would disable it. Returns FALSE if not in workspace or no versioning for record. Notice, that the enablefields of the online record is also tested.
1286 * @see \TYPO3\CMS\Backend\Utility\BackendUtility::getWorkspaceVersionOfRecord()
1287 * @todo Define visibility
1288 */
1289 public function getWorkspaceVersionOfRecord($workspace, $table, $uid, $fields = '*', $bypassEnableFieldsCheck = FALSE) {
1290 if ($workspace !== 0) {
1291 $workspace = (int)$workspace;
1292 $uid = (int)$uid;
1293 // Have to hardcode it for "pages" table since TCA is not loaded at this moment!
1294 // Setting up enableFields for version record:
1295 if ($table == 'pages') {
1296 $enFields = $this->versioningPreview_where_hid_del;
1297 } else {
1298 $enFields = $this->enableFields($table, -1, array(), TRUE);
1299 }
1300 // Select workspace version of record, only testing for deleted.
1301 $newrow = $GLOBALS['TYPO3_DB']->exec_SELECTgetSingleRow($fields, $table, 'pid=-1 AND
1302 t3ver_oid=' . $uid . ' AND
1303 t3ver_wsid=' . $workspace . $this->deleteClause($table));
1304 // If version found, check if it could have been selected with enableFields on
1305 // as well:
1306 if (is_array($newrow)) {
1307 if ($bypassEnableFieldsCheck || $GLOBALS['TYPO3_DB']->exec_SELECTgetSingleRow('uid', $table, 'pid=-1 AND
1308 t3ver_oid=' . $uid . ' AND
1309 t3ver_wsid=' . $workspace . $enFields)) {
1310 // Return offline version, tested for its enableFields.
1311 return $newrow;
1312 } else {
1313 // Return -1 because offline version was de-selected due to its enableFields.
1314 return -1;
1315 }
1316 } else {
1317 // OK, so no workspace version was found. Then check if online version can be
1318 // selected with full enable fields and if so, return 1:
1319 if ($bypassEnableFieldsCheck || $GLOBALS['TYPO3_DB']->exec_SELECTgetSingleRow('uid', $table, 'uid=' . $uid . $enFields)) {
1320 // Means search was done, but no version found.
1321 return 1;
1322 } else {
1323 // Return -2 because the online record was de-selected due to its enableFields.
1324 return -2;
1325 }
1326 }
1327 }
1328 // No look up in database because versioning not enabled / or workspace not
1329 // offline
1330 return FALSE;
1331 }
1332
1333 /**
1334 * Checks if user has access to workspace.
1335 *
1336 * @param integer $wsid Workspace ID
1337 * @return boolean <code>TRUE</code> if has access
1338 * @todo Define visibility
1339 */
1340 public function checkWorkspaceAccess($wsid) {
1341 if (!$GLOBALS['BE_USER'] || !\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::isLoaded('workspaces')) {
1342 return FALSE;
1343 }
1344 if (isset($this->workspaceCache[$wsid])) {
1345 $ws = $this->workspaceCache[$wsid];
1346 } else {
1347 if ($wsid > 0) {
1348 // No $GLOBALS['TCA'] yet!
1349 $ws = $GLOBALS['TYPO3_DB']->exec_SELECTgetSingleRow('*', 'sys_workspace', 'uid=' . (int)$wsid . ' AND deleted=0');
1350 if (!is_array($ws)) {
1351 return FALSE;
1352 }
1353 } else {
1354 $ws = $wsid;
1355 }
1356 $ws = $GLOBALS['BE_USER']->checkWorkspace($ws);
1357 $this->workspaceCache[$wsid] = $ws;
1358 }
1359 return $ws['_ACCESS'] != '';
1360 }
1361
1362 /**
1363 * Determine if a field needs an overlay
1364 *
1365 * @param string $table TCA tablename
1366 * @param string $field TCA fieldname
1367 * @param mixed $value Current value of the field
1368 * @return boolean Returns TRUE if a given record field needs to be overlaid
1369 */
1370 protected function shouldFieldBeOverlaid($table, $field, $value) {
1371 $l10n_mode = isset($GLOBALS['TCA'][$table]['columns'][$field]['l10n_mode'])
1372 ? $GLOBALS['TCA'][$table]['columns'][$field]['l10n_mode']
1373 : '';
1374
1375 $shouldFieldBeOverlaid = TRUE;
1376
1377 if ($l10n_mode === 'exclude') {
1378 $shouldFieldBeOverlaid = FALSE;
1379 } elseif ($l10n_mode === 'mergeIfNotBlank') {
1380 $checkValue = $value;
1381
1382 // 0 values are considered blank when coming from a group field
1383 if (empty($value) && $GLOBALS['TCA'][$table]['columns'][$field]['config']['type'] === 'group') {
1384 $checkValue = '';
1385 }
1386
1387 if (trim($checkValue) === '') {
1388 $shouldFieldBeOverlaid = FALSE;
1389 }
1390 }
1391
1392 return $shouldFieldBeOverlaid;
1393 }
1394 }