options = array_merge([ 'limit' => 10, 'historyLimit' => 1000, ], $options); } public function getSettingsDefinitions(): array { return [ new SettingDefinition( key: 'restrictToCurrentUser', type: 'bool', default: false, label: 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.latestChangedPages.settings.restrictToCurrentUser', ), ]; } public function renderWidget(WidgetContext $context): WidgetResult { $restrictToCurrentUser = (bool)$context->settings->get('restrictToCurrentUser'); $sysHistoryEntries = $this->getSysHistoryEntries($this->options['historyLimit'], $restrictToCurrentUser); $latestPages = $this->getLatestPagesFromSysHistory($sysHistoryEntries, $this->options['limit']); $latestPages = $this->enrichPageInformation($latestPages); $view = $this->backendViewFactory->create($context->request); $view->assignMultiple([ 'latestPages' => $latestPages, 'configuration' => $this->configuration, 'dateFormat' => $GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'] . ' ' . $GLOBALS['TYPO3_CONF_VARS']['SYS']['hhmm'], ]); return new WidgetResult( content: $view->render('Widget/LatestChangedPagesWidget'), refreshable: true, label: $restrictToCurrentUser ? $this->getLanguageService()->sL('LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.latestChangedPages.title.restrictToCurrentUser') : null, ); } private function getSysHistoryEntries(int $limit, bool $restrictToCurrentUser): array { $queryBuilder = $this->getQueryBuilderSysHistory(); $workspaceId = GeneralUtility::makeInstance(Context::class)->getPropertyFromAspect('workspace', 'id'); $queryBuilder ->select('tablename', 'recuid', 'tstamp', 'userid') ->from('sys_history') ->where( $queryBuilder->expr()->in( 'tablename', [ $queryBuilder->createNamedParameter('pages'), $queryBuilder->createNamedParameter('tt_content'), ] ), $queryBuilder->expr()->eq('workspace', $workspaceId), ) ->addOrderBy('tstamp', 'desc') ->setMaxResults($limit); if ($restrictToCurrentUser) { $queryBuilder->andWhere( $queryBuilder->expr()->eq( 'userid', $queryBuilder->createNamedParameter($this->getBackendUser()->user['uid']) ), ); } return $queryBuilder ->executeQuery() ->fetchAllAssociative(); } private function getLatestPagesFromSysHistory(array $history, int $limit): array { $latestPages = []; foreach ($history as $historyEntry) { $pageId = $historyEntry['recuid']; if ($historyEntry['tablename'] === 'tt_content') { $pageId = $this->getPageOfContentElement($historyEntry['recuid']); } if (!$pageId || isset($latestPages[$pageId])) { continue; } $pageRecord = BackendUtility::readPageAccess($pageId, $this->getBackendUser()->getPagePermsClause(Permission::PAGE_SHOW)); if ($pageRecord === false || $pageRecord === []) { // Backend user has no access to show page information. Dismiss this page. continue; } $latestPages[$pageId]['history'] = $historyEntry; $pageRecord['_uid'] = $pageRecord['language_tag'] > 0 ? $pageRecord['l10n_parent'] : $pageRecord['uid']; $latestPages[$pageId]['pageRecord'] = $pageRecord; try { $latestPages[$pageId]['siteLanguage'] = $this->siteFinder->getSiteByPageId($pageRecord['_uid'])->getLanguageById($pageRecord['language_tag']); } catch (SiteNotFoundException $exception) { $latestPages[$pageId]['siteLanguage'] = null; } // Override tstamp of pageRecord with tstamp from history record if newer $latestPages[$pageId]['pageRecord']['tstamp'] = max($historyEntry['tstamp'], $latestPages[$pageId]['pageRecord']['tstamp']); if (count($latestPages) >= $limit) { break; } } return $latestPages; } private function enrichPageInformation(array $latestPages): array { $userNames = BackendUtility::getUserNames('username,realName,uid'); foreach ($latestPages as $pageId => &$page) { $page['rootline'] = $this->getRootLine($pageId); $uriPageId = $page['pageRecord']['language_tag'] > 0 ? $page['pageRecord']['l10n_parent'] : $page['pageRecord']['uid']; $page['viewLink'] = (string)PreviewUriBuilder::create($page['pageRecord']) ->withRootLine(BackendUtility::BEgetRootLine($uriPageId)) ->buildUri(); $page['userName'] = $userNames[$page['history']['userid']]['username'] ?? ''; $page['realName'] = $userNames[$page['history']['userid']]['realName'] ?? ''; } return $latestPages; } private function getPageOfContentElement(int $uid): ?int { $queryBuilder = $this->getQueryBuilderForContentElements(); $contentRecord = $queryBuilder ->select('pid', 'sys_language_uid') ->from('tt_content') ->where($queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($uid, Connection::PARAM_INT))) ->setMaxResults(1) ->executeQuery() ->fetchAssociative(); if (!$contentRecord) { // Content element has been deleted return null; } $queryBuilder = $this->getQueryBuilderForPages(); if ((int)$contentRecord['language_tag'] > 0) { return $queryBuilder ->select('uid') ->from('pages') ->where( $queryBuilder->expr()->eq('l10n_parent', $queryBuilder->createNamedParameter($contentRecord['pid'], Connection::PARAM_INT)), $queryBuilder->expr()->eq('language_tag', $queryBuilder->createNamedParameter($contentRecord['language_tag'], Connection::PARAM_INT)) ) ->setMaxResults(1) ->executeQuery() ->fetchAssociative()['uid'] ?? null; } return $queryBuilder ->select('uid') ->from('pages') ->where( $queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($contentRecord['pid'], Connection::PARAM_INT)) ) ->setMaxResults(1) ->executeQuery() ->fetchAssociative()['uid'] ?? null; } private function getRootLine(int $pageId): string { $rootLine = GeneralUtility::makeInstance(RootlineUtility::class, $pageId)->get(); return implode(' / ', array_slice( array_map( static fn(array $page): string => $page['title'], array_reverse($rootLine) ), 0, -1 )); } private function getQueryBuilderSysHistory(): QueryBuilder { $queryBuilder = $this->connectionPool->getConnectionForTable('sys_history')->createQueryBuilder(); return $queryBuilder; } private function getQueryBuilderForContentElements(): QueryBuilder { $queryBuilder = $this->connectionPool->getConnectionForTable('tt_content')->createQueryBuilder(); $queryBuilder->getRestrictions()->removeAll(); $queryBuilder->getRestrictions()->add(GeneralUtility::makeInstance(DeletedRestriction::class)); return $queryBuilder; } private function getQueryBuilderForPages(): QueryBuilder { $queryBuilder = $this->connectionPool->getConnectionForTable('pages')->createQueryBuilder(); $queryBuilder->getRestrictions()->removeAll(); $queryBuilder->getRestrictions()->add(GeneralUtility::makeInstance(DeletedRestriction::class)); return $queryBuilder; } public function getOptions(): array { return $this->options; } private function getBackendUser(): BackendUserAuthentication { return $GLOBALS['BE_USER']; } protected function getLanguageService(): LanguageService { return $GLOBALS['LANG']; } }