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