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