TYPO3 v15 dev-main snapshot ()
This commit is contained in:
@@ -0,0 +1 @@
|
||||
/vendor/
|
||||
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Info\Controller\Event;
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use TYPO3\CMS\Backend\Module\ModuleInterface;
|
||||
use TYPO3\CMS\Backend\Template\ModuleTemplate;
|
||||
|
||||
/**
|
||||
* Listeners to this Event will be able to modify the header and footer content of the info module
|
||||
*/
|
||||
final class ModifyInfoModuleContentEvent
|
||||
{
|
||||
private string $headerContent = '';
|
||||
private string $footerContent = '';
|
||||
|
||||
public function __construct(
|
||||
private readonly bool $access,
|
||||
private readonly ServerRequestInterface $request,
|
||||
private readonly ModuleInterface $currentModule,
|
||||
private readonly ModuleTemplate $moduleTemplate,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Whether the current user has access to the main content of the info module.
|
||||
* IMPORTANT: This is only for informational purposes. Listeners can therefore
|
||||
* decide on their own if their content should be added to the module even if
|
||||
* the user does not have access to the main module content.
|
||||
*/
|
||||
public function hasAccess(): bool
|
||||
{
|
||||
return $this->access;
|
||||
}
|
||||
|
||||
public function getRequest(): ServerRequestInterface
|
||||
{
|
||||
return $this->request;
|
||||
}
|
||||
|
||||
public function getCurrentModule(): ModuleInterface
|
||||
{
|
||||
return $this->currentModule;
|
||||
}
|
||||
|
||||
public function getModuleTemplate(): ModuleTemplate
|
||||
{
|
||||
return $this->moduleTemplate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set content for the header. Can also be used to e.g. reorder existing content.
|
||||
* IMPORTANT: This overwrites existing content from previous listeners!
|
||||
*/
|
||||
public function setHeaderContent(string $content): void
|
||||
{
|
||||
$this->headerContent = $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add additional content to the header
|
||||
*/
|
||||
public function addHeaderContent(string $content): void
|
||||
{
|
||||
$this->headerContent .= $content;
|
||||
}
|
||||
|
||||
public function getHeaderContent(): string
|
||||
{
|
||||
return $this->headerContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set content for the footer. Can also be used to e.g. reorder existing content.
|
||||
* IMPORTANT: This overwrites existing content from previous listeners!
|
||||
*/
|
||||
public function setFooterContent(string $content): void
|
||||
{
|
||||
$this->footerContent = $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add additional content to the footer
|
||||
*/
|
||||
public function addFooterContent(string $content): void
|
||||
{
|
||||
$this->footerContent .= $content;
|
||||
}
|
||||
|
||||
public function getFooterContent(): string
|
||||
{
|
||||
return $this->footerContent;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,734 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Info\Controller;
|
||||
|
||||
use Psr\EventDispatcher\EventDispatcherInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use TYPO3\CMS\Backend\Attribute\AsController;
|
||||
use TYPO3\CMS\Backend\Domain\Repository\Localization\LocalizationRepository;
|
||||
use TYPO3\CMS\Backend\Routing\PreviewUriBuilder;
|
||||
use TYPO3\CMS\Backend\Routing\UriBuilder;
|
||||
use TYPO3\CMS\Backend\Template\Components\ButtonBar;
|
||||
use TYPO3\CMS\Backend\Template\Components\ComponentFactory;
|
||||
use TYPO3\CMS\Backend\Template\ModuleTemplateFactory;
|
||||
use TYPO3\CMS\Backend\Utility\BackendUtility;
|
||||
use TYPO3\CMS\Backend\View\BackendLayoutView;
|
||||
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
|
||||
use TYPO3\CMS\Core\Database\Connection;
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
|
||||
use TYPO3\CMS\Core\Database\Query\Restriction\WorkspaceRestriction;
|
||||
use TYPO3\CMS\Core\Domain\Repository\PageRepository;
|
||||
use TYPO3\CMS\Core\Imaging\IconFactory;
|
||||
use TYPO3\CMS\Core\Imaging\IconSize;
|
||||
use TYPO3\CMS\Core\Localization\LanguageService;
|
||||
use TYPO3\CMS\Core\Schema\Capability\TcaSchemaCapability;
|
||||
use TYPO3\CMS\Core\Schema\TcaSchemaFactory;
|
||||
use TYPO3\CMS\Core\Type\Bitmask\Permission;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Info\Controller\Event\ModifyInfoModuleContentEvent;
|
||||
|
||||
/**
|
||||
* Status -> Pagetree Overview
|
||||
*
|
||||
* @internal This class is a specific Backend controller implementation and is not part of the TYPO3's Core API.
|
||||
*/
|
||||
#[AsController]
|
||||
readonly class PageInformationController
|
||||
{
|
||||
public function __construct(
|
||||
protected IconFactory $iconFactory,
|
||||
protected UriBuilder $uriBuilder,
|
||||
protected ModuleTemplateFactory $moduleTemplateFactory,
|
||||
protected EventDispatcherInterface $eventDispatcher,
|
||||
protected TcaSchemaFactory $tcaSchemaFactory,
|
||||
protected ComponentFactory $componentFactory,
|
||||
protected BackendLayoutView $backendLayoutView,
|
||||
protected ConnectionPool $connectionPool,
|
||||
protected LocalizationRepository $localizationRepository,
|
||||
) {}
|
||||
|
||||
public function handleRequest(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$backendUser = $this->getBackendUser();
|
||||
$languageService = $this->getLanguageService();
|
||||
$module = $request->getAttribute('module');
|
||||
$moduleData = $request->getAttribute('moduleData');
|
||||
$currentSite = $request->getAttribute('site');
|
||||
$pageId = (int)($request->getQueryParams()['id'] ?? $request->getParsedBody()['id'] ?? 0);
|
||||
|
||||
$pageinfo = BackendUtility::readPageAccess($pageId, $backendUser->getPagePermsClause(Permission::PAGE_SHOW)) ?: [];
|
||||
$hasAccess = false;
|
||||
if (($pageId > 0 && $pageinfo !== []) || ($backendUser->isAdmin() && $pageId === 0)) {
|
||||
$hasAccess = true;
|
||||
}
|
||||
if ($pageId === 0 && $backendUser->isAdmin()) {
|
||||
$pageinfo = ['title' => '[root-level]', 'uid' => 0, 'pid' => 0];
|
||||
}
|
||||
|
||||
$siteLanguages = [
|
||||
$currentSite->getDefaultLanguage()->getLanguageId() => $currentSite->getDefaultLanguage(),
|
||||
];
|
||||
foreach ($currentSite->getAvailableLanguages($this->getBackendUser(), false, $pageId) as $language) {
|
||||
$siteLanguages[$language->getLanguageId()] = $language;
|
||||
}
|
||||
|
||||
$fieldConfiguration = $this->getFieldConfiguration($pageId);
|
||||
$allowedModuleOptions = $this->getModuleOptions($siteLanguages, $fieldConfiguration);
|
||||
if ($moduleData->cleanUp($allowedModuleOptions)) {
|
||||
$backendUser->pushModuleData($moduleData->getModuleIdentifier(), $moduleData->toArray());
|
||||
}
|
||||
$selectedDepth = (int)($moduleData->get('depth') ?? 0);
|
||||
$selectedGroup = (string)($moduleData->get('pages') ?? '0'); // field or table list to render
|
||||
$selectedLanguage = (int)($moduleData->get('lang') ?? 0);
|
||||
|
||||
$mainContent = $this->renderMainTable($pageId, $selectedDepth, $selectedLanguage, $siteLanguages, $request, $fieldConfiguration[$selectedGroup]['fields'] ?? []);
|
||||
|
||||
$view = $this->moduleTemplateFactory->create($request);
|
||||
$view->assign('hasAccess', $hasAccess);
|
||||
if ($hasAccess) {
|
||||
$view->setTitle($languageService->sL($module->getTitle()), $pageId !== 0 && isset($pageinfo['title']) ? $pageinfo['title'] : '');
|
||||
$view->getDocHeaderComponent()->setPageBreadcrumb($pageinfo);
|
||||
$view->makeDocHeaderModuleMenu(['id' => $pageId]);
|
||||
$view->getDocHeaderComponent()->setShortcutContext($module->getIdentifier(), sprintf('%s [%d]', $languageService->sL($module->getTitle()), $pageId), ['id' => $pageId]);
|
||||
$previewUriBuilder = PreviewUriBuilder::create($pageinfo);
|
||||
if ($previewUriBuilder->isPreviewable()) {
|
||||
// View page
|
||||
$previewDataAttributes = $previewUriBuilder
|
||||
->withRootLine(BackendUtility::BEgetRootLine($pageinfo['uid']))
|
||||
->buildDispatcherDataAttributes();
|
||||
$viewButton = $this->componentFactory->createLinkButton()
|
||||
->setHref('#')
|
||||
->setDataAttributes($previewDataAttributes ?? [])
|
||||
->setDisabled(!$previewDataAttributes)
|
||||
->setTitle($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.showPage'))
|
||||
->setIcon($this->iconFactory->getIcon('actions-view-page', IconSize::SMALL))
|
||||
->setShowLabelText(true);
|
||||
$view->addButtonToButtonBar($viewButton, ButtonBar::BUTTON_POSITION_LEFT, 2);
|
||||
}
|
||||
}
|
||||
$event = $this->eventDispatcher->dispatch(new ModifyInfoModuleContentEvent($hasAccess, $request, $module, $view));
|
||||
if ($hasAccess) {
|
||||
$view->assignMultiple([
|
||||
'pageUid' => $pageId,
|
||||
'content' => $mainContent,
|
||||
'depthDropdownOptions' => $allowedModuleOptions['depth'],
|
||||
'depthDropdownCurrentValue' => $selectedDepth,
|
||||
'pagesDropdownOptions' => $allowedModuleOptions['pages'],
|
||||
'pagesDropdownCurrentValue' => $selectedGroup,
|
||||
'langDropdownOptions' => $allowedModuleOptions['lang'],
|
||||
'langDropdownCurrentValue' => $selectedLanguage,
|
||||
'headerContent' => $event->getHeaderContent(),
|
||||
'footerContent' => $event->getFooterContent(),
|
||||
]);
|
||||
}
|
||||
return $view->renderResponse('PageInformation');
|
||||
}
|
||||
|
||||
protected function getModuleOptions(array $siteLanguages, array $fieldConfiguration): array
|
||||
{
|
||||
$languageService = $this->getLanguageService();
|
||||
$menu = [
|
||||
'pages' => [],
|
||||
'depth' => [
|
||||
0 => $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_0'),
|
||||
1 => $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_1'),
|
||||
2 => $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_2'),
|
||||
3 => $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_3'),
|
||||
4 => $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_4'),
|
||||
999 => $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_infi'),
|
||||
],
|
||||
'lang' => [],
|
||||
];
|
||||
foreach ($fieldConfiguration as $key => $item) {
|
||||
$menu['pages'][$key] = $item['label'];
|
||||
}
|
||||
foreach ($siteLanguages as $language) {
|
||||
$menu['lang'][$language->getLanguageId()] = $language->getTitle();
|
||||
}
|
||||
return $menu;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate configuration for field and table selection from TSConfig.
|
||||
*/
|
||||
protected function getFieldConfiguration(int $pageId): array
|
||||
{
|
||||
$languageService = $this->getLanguageService();
|
||||
$fieldConfiguration = [];
|
||||
$modTSconfig = BackendUtility::getPagesTSconfig($pageId)['mod.']['web_info.']['fieldDefinitions.'] ?? [];
|
||||
$allowedTables = $this->getAllowedTableNames();
|
||||
foreach ($modTSconfig as $key => $item) {
|
||||
$fieldList = str_replace('###ALL_TABLES###', implode(',', $allowedTables), $item['fields']);
|
||||
$fields = GeneralUtility::trimExplode(',', $fieldList, true);
|
||||
$key = trim($key, '.');
|
||||
$fieldConfiguration[$key] = [
|
||||
'label' => $item['label'] ? $languageService->sL($item['label']) : $key,
|
||||
'fields' => $fields,
|
||||
];
|
||||
}
|
||||
return $fieldConfiguration;
|
||||
}
|
||||
|
||||
/**
|
||||
* A list of table names allowed to be listed when ###ALL_TABLES### is used in TSConfig.
|
||||
* Some tables like 'pages' are blinded by default, all remaining ones are user access checked.
|
||||
*/
|
||||
protected function getAllowedTableNames(): array
|
||||
{
|
||||
$hideTables = ['pages', 'sys_filemounts', 'be_users', 'be_groups']; // Never show these tables
|
||||
$allowedTables = [];
|
||||
foreach ($this->tcaSchemaFactory->all() as $schemaName => $schema) {
|
||||
if (in_array($schemaName, $hideTables, true)
|
||||
|| $schema->hasCapability(TcaSchemaCapability::HideInUi)
|
||||
|| !$this->getBackendUser()->check('tables_select', $schemaName)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
$allowedTables[] = 'table_' . $schemaName;
|
||||
}
|
||||
return $allowedTables;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders records from the pages table from page id
|
||||
*
|
||||
* @return string HTML for the listing
|
||||
*/
|
||||
protected function renderMainTable(int $id, int $depth, int $language, array $siteLanguages, ServerRequestInterface $request, array $fieldArray): string
|
||||
{
|
||||
$languageService = $this->getLanguageService();
|
||||
$backendUser = $this->getBackendUser();
|
||||
$out = '';
|
||||
$pagesSchema = $this->tcaSchemaFactory->get('pages');
|
||||
$languageFieldName = $pagesSchema->getCapability(TcaSchemaCapability::Language)->getLanguageField()->getName();
|
||||
$translationOriginFieldName = $pagesSchema->getCapability(TcaSchemaCapability::Language)->getTranslationOriginPointerField()->getName();
|
||||
$queryBuilder = $this->connectionPool->getQueryBuilderForTable('pages');
|
||||
$queryBuilder->getRestrictions()->removeAll()->add(GeneralUtility::makeInstance(DeletedRestriction::class));
|
||||
$row = $queryBuilder
|
||||
->select('*')
|
||||
->from('pages')
|
||||
->where(
|
||||
$queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($id, Connection::PARAM_INT)),
|
||||
$backendUser->getPagePermsClause(Permission::PAGE_SHOW)
|
||||
)
|
||||
->executeQuery()
|
||||
->fetchAssociative();
|
||||
BackendUtility::workspaceOL('pages', $row);
|
||||
if ($language > 0) {
|
||||
$localizedPageRecord = $this->localizationRepository->getPageTranslations($row['uid'], [$language], $this->getBackendUser()->workspace);
|
||||
if ($localizedPageRecord !== []) {
|
||||
$row = reset($localizedPageRecord)->toArray();
|
||||
$row['uid'] = $row[$translationOriginFieldName];
|
||||
}
|
||||
}
|
||||
if (!is_array($row)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$editUids = [];
|
||||
// Getting children
|
||||
$theRows = $this->getPageRecordsRecursive($row['uid'], $depth, $language);
|
||||
// Get tree root page
|
||||
$treeRootPage = $this->getTreeRootPage($row['uid'], $row[$languageFieldName]);
|
||||
if ($backendUser->doesUserHaveAccess($treeRootPage, Permission::PAGE_EDIT) && $treeRootPage['uid'] > 0) {
|
||||
$editUids[] = $treeRootPage['uid'];
|
||||
}
|
||||
$out .= $this->pages_drawItem($treeRootPage, $request, $siteLanguages, $fieldArray);
|
||||
// Traverse all pages selected:
|
||||
foreach ($theRows as $sRow) {
|
||||
if ($backendUser->doesUserHaveAccess($sRow, Permission::PAGE_EDIT)) {
|
||||
$editUids[] = $sRow['uid'];
|
||||
}
|
||||
$out .= $this->pages_drawItem($sRow, $request, $siteLanguages, $fieldArray);
|
||||
}
|
||||
// Header line is drawn
|
||||
$headerCells = [];
|
||||
$editIdList = implode(',', $editUids);
|
||||
// Traverse fields (as set above) in order to create header values:
|
||||
foreach ($fieldArray as $field) {
|
||||
$editButton = '';
|
||||
if (
|
||||
$editIdList
|
||||
&& $pagesSchema->hasField($field)
|
||||
&& $backendUser->check('tables_modify', 'pages')
|
||||
&& $backendUser->check('non_exclude_fields', 'pages:' . $field)
|
||||
) {
|
||||
$iTitle = sprintf(
|
||||
$languageService->sL('LLL:EXT:backend/Resources/Private/Language/locallang_layout.xlf:editThisColumn'),
|
||||
rtrim(trim($languageService->sL($pagesSchema->getField($field)->getLabel())), ':')
|
||||
);
|
||||
$urlParameters = [
|
||||
'edit' => [
|
||||
'pages' => [
|
||||
$editIdList => 'edit',
|
||||
],
|
||||
],
|
||||
'columnsOnly' => [
|
||||
'pages' => [$field],
|
||||
],
|
||||
'module' => 'web_info_overview',
|
||||
'returnUrl' => $request->getAttribute('normalizedParams')->getRequestUri(),
|
||||
];
|
||||
$url = (string)$this->uriBuilder->buildUriFromRoute('record_edit', $urlParameters);
|
||||
$editButton = '<a class="btn btn-default" href="' . htmlspecialchars($url)
|
||||
. '" title="' . htmlspecialchars($iTitle) . '">'
|
||||
. $this->iconFactory->getIcon('actions-document-open', IconSize::SMALL)->render() . '</a>';
|
||||
}
|
||||
switch ($field) {
|
||||
case 'title':
|
||||
$headerCells[$field] = $editButton . ' <strong>'
|
||||
. $languageService->sL($pagesSchema->getField($field)->getLabel())
|
||||
. '</strong>';
|
||||
break;
|
||||
case 'uid':
|
||||
$headerCells[$field] = '';
|
||||
break;
|
||||
case 'actual_backend_layout':
|
||||
$headerCells[$field] = htmlspecialchars($languageService->sL('LLL:EXT:info/Resources/Private/Language/locallang_webinfo.xlf:actual_backend_layout'));
|
||||
break;
|
||||
default:
|
||||
if (str_starts_with($field, 'table_')) {
|
||||
$f2 = substr($field, 6);
|
||||
if ($this->tcaSchemaFactory->has($f2)) {
|
||||
$schema = $this->tcaSchemaFactory->get($f2);
|
||||
$headerCells[$field] = ' '
|
||||
. '<span title="'
|
||||
. htmlspecialchars($schema->getTitle($languageService->sL(...)) ?: $f2)
|
||||
. '">'
|
||||
. $this->iconFactory->getIconForRecord($f2, [], IconSize::SMALL)->render()
|
||||
. '</span>';
|
||||
}
|
||||
} else {
|
||||
if ($pagesSchema->hasField($field)) {
|
||||
$headerCells[$field] = $editButton . ' <strong>'
|
||||
. htmlspecialchars($languageService->sL($pagesSchema->getField($field)->getLabel()))
|
||||
. '</strong>';
|
||||
} else {
|
||||
// Invalid field configured in `mod.web_info.fieldDefinitions.*`,
|
||||
// using field name as header label.
|
||||
$headerCells[$field] = $editButton . ' <strong>'
|
||||
. htmlspecialchars($field)
|
||||
. '</strong>';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return '
|
||||
<div class="table-fit">
|
||||
<table class="table table-striped table-hover" id="PageInformationControllerTable">
|
||||
<thead>
|
||||
' . $this->addElement($headerCells, $fieldArray) . '
|
||||
</thead>
|
||||
<tbody>
|
||||
' . $out . '
|
||||
</tbody>
|
||||
</table>
|
||||
</div>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get tree root page
|
||||
*
|
||||
* @param int $pid Starting page
|
||||
* @param int $language Selected site language
|
||||
*/
|
||||
protected function getTreeRootPage(int $pid, int $language): array
|
||||
{
|
||||
$backendUser = $this->getBackendUser();
|
||||
$pagesSchema = $this->tcaSchemaFactory->get('pages');
|
||||
$languageFieldName = $pagesSchema->getCapability(TcaSchemaCapability::Language)->getLanguageField()->getName();
|
||||
$translationOriginFieldName = $pagesSchema->getCapability(TcaSchemaCapability::Language)->getTranslationOriginPointerField()->getName();
|
||||
|
||||
$queryBuilder = $this->connectionPool->getQueryBuilderForTable('pages');
|
||||
$queryBuilder->getRestrictions()
|
||||
->removeAll()
|
||||
->add(GeneralUtility::makeInstance(DeletedRestriction::class))
|
||||
->add(GeneralUtility::makeInstance(WorkspaceRestriction::class, $backendUser->workspace));
|
||||
|
||||
if ($language > 0) {
|
||||
return $queryBuilder
|
||||
->select('*')
|
||||
->from('pages')
|
||||
->where(
|
||||
$queryBuilder->expr()->eq($translationOriginFieldName, $queryBuilder->createNamedParameter($pid, Connection::PARAM_INT)),
|
||||
$queryBuilder->expr()->eq($languageFieldName, $queryBuilder->createNamedParameter($language, Connection::PARAM_INT)),
|
||||
$backendUser->getPagePermsClause(Permission::PAGE_SHOW)
|
||||
)
|
||||
->setMaxResults(1)
|
||||
->executeQuery()
|
||||
->fetchAssociative();
|
||||
}
|
||||
|
||||
return $queryBuilder
|
||||
->select('*')
|
||||
->from('pages')
|
||||
->where(
|
||||
$queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($pid, Connection::PARAM_INT)),
|
||||
$backendUser->getPagePermsClause(Permission::PAGE_SHOW)
|
||||
)
|
||||
->setMaxResults(1)
|
||||
->executeQuery()
|
||||
->fetchAssociative();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds pages-rows to an array, selecting recursively in the page tree.
|
||||
*
|
||||
* @param int $pid Starting page id to select from
|
||||
* @param string $iconPrefix Prefix for icon code.
|
||||
* @param int $depth Depth (decreasing)
|
||||
* @param array $rows Array which will accumulate page rows
|
||||
* @return array $rows with added rows.
|
||||
*/
|
||||
protected function getPageRecordsRecursive(int $pid, int $depth, int $language, string $iconPrefix = '', array $rows = []): array
|
||||
{
|
||||
$backendUser = $this->getBackendUser();
|
||||
$pagesSchema = $this->tcaSchemaFactory->get('pages');
|
||||
$languageFieldName = $pagesSchema->getCapability(TcaSchemaCapability::Language)->getLanguageField()->getName();
|
||||
|
||||
$depth--;
|
||||
$queryBuilder = $this->connectionPool->getQueryBuilderForTable('pages');
|
||||
$queryBuilder->getRestrictions()
|
||||
->removeAll()
|
||||
->add(GeneralUtility::makeInstance(DeletedRestriction::class))
|
||||
->add(GeneralUtility::makeInstance(WorkspaceRestriction::class, $backendUser->workspace));
|
||||
|
||||
$queryBuilder
|
||||
->select('*')
|
||||
->from('pages')
|
||||
->where(
|
||||
$queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($pid, Connection::PARAM_INT)),
|
||||
$queryBuilder->expr()->eq($languageFieldName, $queryBuilder->createNamedParameter(0, Connection::PARAM_INT)),
|
||||
$backendUser->getPagePermsClause(Permission::PAGE_SHOW)
|
||||
);
|
||||
|
||||
if ($pagesSchema->hasCapability(TcaSchemaCapability::SortByField)) {
|
||||
$queryBuilder->orderBy($pagesSchema->getCapability(TcaSchemaCapability::SortByField)->getFieldName());
|
||||
}
|
||||
|
||||
if ($depth >= 0) {
|
||||
$countQueryBuilder = clone $queryBuilder;
|
||||
$countQueryBuilder->resetOrderBy()->count('uid');
|
||||
$rowCount = $countQueryBuilder->executeQuery()->fetchOne();
|
||||
$result = $queryBuilder->executeQuery();
|
||||
$count = 0;
|
||||
while ($row = $result->fetchAssociative()) {
|
||||
BackendUtility::workspaceOL('pages', $row);
|
||||
$uid = (int)$row['uid'];
|
||||
if (is_array($row)) {
|
||||
if ($language > 0) {
|
||||
$localizedPageRecord = $this->localizationRepository->getPageTranslations($uid, [$language], $this->getBackendUser()->workspace);
|
||||
if ($localizedPageRecord === []) {
|
||||
continue;
|
||||
}
|
||||
$row = reset($localizedPageRecord)->toArray();
|
||||
}
|
||||
$count++;
|
||||
$row['treeIcons'] = $iconPrefix
|
||||
. '<span class="treeline-icon treeline-icon-join'
|
||||
. ($rowCount === $count ? 'bottom' : '')
|
||||
. '"></span>';
|
||||
$rows[] = $row;
|
||||
// Get the branch
|
||||
$spaceOutIcons = '<span class="treeline-icon treeline-icon-'
|
||||
. ($rowCount === $count ? 'clear' : 'line')
|
||||
. '"></span>';
|
||||
$rows = $this->getPageRecordsRecursive(
|
||||
$uid,
|
||||
$row['php_tree_stop'] ? 0 : $depth,
|
||||
$language,
|
||||
$iconPrefix . $spaceOutIcons,
|
||||
$rows
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a list item for the pages-rendering
|
||||
*/
|
||||
protected function pages_drawItem(array $row, ServerRequestInterface $request, array $siteLanguages, array $fieldArray): string
|
||||
{
|
||||
$languageService = $this->getLanguageService();
|
||||
$backendUser = $this->getBackendUser();
|
||||
$pagesSchema = $this->tcaSchemaFactory->get('pages');
|
||||
$languageFieldName = $pagesSchema->getCapability(TcaSchemaCapability::Language)->getLanguageField()->getName();
|
||||
$backendLayouts = $this->getBackendLayouts($row, 'backend_layout');
|
||||
$backendLayoutsNextLevel = $this->getBackendLayouts($row, 'backend_layout_next_level');
|
||||
$userTsConfig = $this->getBackendUser()->getTSConfig();
|
||||
$theIcon = $this->getIcon($row);
|
||||
// Preparing and getting the data-array
|
||||
$theData = [];
|
||||
foreach ($fieldArray as $field) {
|
||||
switch ($field) {
|
||||
case 'title':
|
||||
$showPageId = !empty($userTsConfig['options.']['pageTree.']['showPageIdWithTitle']);
|
||||
$pTitle = htmlspecialchars(
|
||||
(string)BackendUtility::getProcessedValue(
|
||||
'pages',
|
||||
$field,
|
||||
$row[$field],
|
||||
0,
|
||||
false,
|
||||
false,
|
||||
0,
|
||||
true,
|
||||
0,
|
||||
$row
|
||||
)
|
||||
);
|
||||
$theData[$field] = '<div class="treeline-container">'
|
||||
. ($row['treeIcons'] ?? '')
|
||||
. $theIcon
|
||||
. '<span class="treeline-label">'
|
||||
. ($showPageId ? '[' . $row['uid'] . '] ' : '')
|
||||
. $pTitle
|
||||
. '</span>'
|
||||
. '</div>';
|
||||
break;
|
||||
case $languageFieldName:
|
||||
if (count($siteLanguages) === 1) {
|
||||
$theData[$field] = '';
|
||||
break;
|
||||
}
|
||||
$siteLanguage = $siteLanguages[$row[$languageFieldName]] ?? null;
|
||||
if (!$siteLanguage) {
|
||||
$theData[$field] = '';
|
||||
break;
|
||||
}
|
||||
$theData[$field] = $this->iconFactory->getIcon($siteLanguage->getFlagIdentifier(), IconSize::SMALL)->setTitle($siteLanguage->getTitle())->render()
|
||||
. ' ' . $siteLanguage->getTitle();
|
||||
break;
|
||||
case 'php_tree_stop':
|
||||
// Intended fall through
|
||||
case 'TSconfig':
|
||||
$theData[$field] = $row[$field] ? '<strong>x</strong>' : ' ';
|
||||
break;
|
||||
case 'actual_backend_layout':
|
||||
$backendLayout = $this->backendLayoutView->getBackendLayoutForPage((int)$row['uid']);
|
||||
$theData[$field] = htmlspecialchars($languageService->sL($backendLayout->getTitle()));
|
||||
break;
|
||||
case 'backend_layout':
|
||||
$layoutValue = $backendLayouts[$row[$field]] ?? null;
|
||||
$theData[$field] = $this->resolveBackendLayoutValue($layoutValue, $field, $row);
|
||||
break;
|
||||
case 'backend_layout_next_level':
|
||||
$layoutValue = $backendLayoutsNextLevel[$row[$field]] ?? null;
|
||||
$theData[$field] = $this->resolveBackendLayoutValue($layoutValue, $field, $row);
|
||||
break;
|
||||
case 'uid':
|
||||
$uid = 0;
|
||||
$editButton = '';
|
||||
$viewButton = '';
|
||||
if ($backendUser->doesUserHaveAccess($row, 2) && $row['uid'] > 0) {
|
||||
$uid = (int)$row['uid'];
|
||||
$urlParameters = [
|
||||
'edit' => [
|
||||
'pages' => [
|
||||
$row['uid'] => 'edit',
|
||||
],
|
||||
],
|
||||
'module' => 'web_info_overview',
|
||||
'returnUrl' => $request->getAttribute('normalizedParams')->getRequestUri(),
|
||||
];
|
||||
$url = (string)$this->uriBuilder->buildUriFromRoute('record_edit', $urlParameters);
|
||||
$previewDataAttributes = PreviewUriBuilder::create($row)
|
||||
->withRootLine(BackendUtility::BEgetRootLine($row['uid']))
|
||||
->serializeDispatcherAttributes();
|
||||
$viewButton
|
||||
= '<button ' . ($previewDataAttributes ?? 'disabled="true"') . ' class="btn btn-default" title="'
|
||||
. htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.showPage')) . '">'
|
||||
. $this->iconFactory->getIcon('actions-view-page', IconSize::SMALL)->render()
|
||||
. '</button>';
|
||||
if ($backendUser->check('tables_modify', 'pages')) {
|
||||
$editButton
|
||||
= '<a class="btn btn-default" href="' . htmlspecialchars($url) . '" title="'
|
||||
. htmlspecialchars($languageService->sL('LLL:EXT:backend/Resources/Private/Language/locallang_layout.xlf:editPageProperties')) . '">'
|
||||
. $this->iconFactory->getIcon('actions-page-open', IconSize::SMALL)->render()
|
||||
. '</a>';
|
||||
}
|
||||
}
|
||||
// Since the uid is overwritten with the edit button markup, we need to store
|
||||
// the actual uid to be able to add it as data attribute to the table data cell.
|
||||
// This also makes the distinction between record rows and the header line simpler.
|
||||
$theData['_UID_'] = $uid;
|
||||
$theData[$field] = '<div class="btn-group btn-group-sm" role="group">' . $viewButton . $editButton . '</div>';
|
||||
break;
|
||||
case 'shortcut':
|
||||
case 'shortcut_mode':
|
||||
if ((int)$row['doktype'] === PageRepository::DOKTYPE_SHORTCUT) {
|
||||
$theData[$field] = htmlspecialchars((string)BackendUtility::getProcessedValue('pages', $field, $row[$field], 0, false, false, 0, true, 0, $row));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (str_starts_with($field, 'table_')) {
|
||||
$f2 = substr($field, 6);
|
||||
if ($this->tcaSchemaFactory->has($f2)) {
|
||||
$c = $this->numberOfRecords($f2, (int)$row['uid']);
|
||||
$theData[$field] = ($c ?: '');
|
||||
}
|
||||
} else {
|
||||
$theData[$field] = htmlspecialchars((string)BackendUtility::getProcessedValue('pages', $field, $row[$field], 0, false, false, 0, true, 0, $row));
|
||||
}
|
||||
}
|
||||
}
|
||||
return $this->addElement($theData, $fieldArray);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the icon image tag for the page and wraps it in a link which will trigger the click menu.
|
||||
*/
|
||||
protected function getIcon(array $row): string
|
||||
{
|
||||
$backendUser = $this->getBackendUser();
|
||||
$icon = '<span title="' . BackendUtility::getRecordIconAltText($row, 'pages') . '">' . $this->iconFactory->getIconForRecord('pages', $row, IconSize::SMALL)->render() . '</span>';
|
||||
// The icon with link
|
||||
if ($backendUser->checkRecordEditAccess('pages', $row)->isAllowed) {
|
||||
$icon = BackendUtility::wrapClickMenuOnIcon($icon, 'pages', $row['uid']);
|
||||
}
|
||||
return $icon;
|
||||
}
|
||||
|
||||
/**
|
||||
* Counts and returns the number of records on the page with $pid
|
||||
*/
|
||||
protected function numberOfRecords(string $table, int $pid): int
|
||||
{
|
||||
if (!$this->tcaSchemaFactory->has($table)) {
|
||||
return 0;
|
||||
}
|
||||
$queryBuilder = $this->connectionPool->getQueryBuilderForTable($table);
|
||||
$queryBuilder->getRestrictions()
|
||||
->removeAll()
|
||||
->add(GeneralUtility::makeInstance(DeletedRestriction::class))
|
||||
->add(GeneralUtility::makeInstance(WorkspaceRestriction::class, $this->getBackendUser()->workspace));
|
||||
return (int)$queryBuilder->count('uid')
|
||||
->from($table)
|
||||
->where($queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($pid, Connection::PARAM_INT)))
|
||||
->executeQuery()
|
||||
->fetchOne();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a table-row with the content from the fields in the input data array.
|
||||
*
|
||||
* @param array $data Record with field values, NOT htmlspecialchar'ed
|
||||
* @return string HTML content for the table row
|
||||
*/
|
||||
protected function addElement(array $data, array $fieldArray): string
|
||||
{
|
||||
// Start up:
|
||||
$attributes = '';
|
||||
$rowTag = 'th';
|
||||
if (isset($data['_UID_'])) {
|
||||
$l10nParent = isset($data['_l10nparent_']) ? (int)$data['_l10nparent_'] : 0;
|
||||
$attributes = ' data-uid="' . $data['_UID_'] . '" data-l10nparent="' . $l10nParent . '"';
|
||||
$rowTag = 'td';
|
||||
}
|
||||
$out = '<tr' . $attributes . '>';
|
||||
// Init rendering.
|
||||
$colsp = '';
|
||||
$lastKey = '';
|
||||
$c = 0;
|
||||
// __label is used as the label key to circumvent problems with uid used as label (see #67756)
|
||||
// as it was introduced later on, check if it really exists before using it
|
||||
if (array_key_exists('__label', $data)) {
|
||||
$fieldArray[0] = '__label';
|
||||
}
|
||||
// Traverse field array which contains the data to present:
|
||||
foreach ($fieldArray as $vKey) {
|
||||
if (isset($data[$vKey])) {
|
||||
$cssClass = '';
|
||||
if ($lastKey === 'title') {
|
||||
$cssClass = 'col-title col-responsive';
|
||||
}
|
||||
if ($lastKey) {
|
||||
$out .= '<' . $rowTag . ' class="' . $cssClass . '"' . $colsp . '>' . $data[$lastKey] . '</' . $rowTag . '>';
|
||||
}
|
||||
$lastKey = $vKey;
|
||||
$c = 1;
|
||||
} else {
|
||||
if (!$lastKey) {
|
||||
$lastKey = $vKey;
|
||||
}
|
||||
$c++;
|
||||
}
|
||||
if ($c > 1) {
|
||||
$colsp = ' colspan="' . $c . '"';
|
||||
} else {
|
||||
$colsp = '';
|
||||
}
|
||||
}
|
||||
if ($lastKey) {
|
||||
$cssClass = '';
|
||||
if ($lastKey === 'title') {
|
||||
$cssClass = 'col-title-flexible';
|
||||
}
|
||||
$out .= '<' . $rowTag . ' class="' . $cssClass . ' nowrap"' . $colsp . '>' . $data[$lastKey] . '</' . $rowTag . '>';
|
||||
}
|
||||
$out .= '</tr>';
|
||||
return $out;
|
||||
}
|
||||
|
||||
protected function getBackendLayouts(array $row, string $field): array
|
||||
{
|
||||
$languageService = $this->getLanguageService();
|
||||
$configuration = ['row' => $row, 'table' => 'pages', 'field' => $field, 'items' => []];
|
||||
// Below we call the itemsProcFunc to retrieve properly resolved backend layout items,
|
||||
// including the translated labels and the correct field values (backend layout identifiers).
|
||||
$this->backendLayoutView->addBackendLayoutItems($configuration);
|
||||
$backendLayouts = [];
|
||||
foreach ($configuration['items'] ?? [] as $backendLayout) {
|
||||
if (($backendLayout['label'] ?? false) && ($backendLayout['value'] ?? false)) {
|
||||
$backendLayouts[$backendLayout['value']] = $languageService->sL($backendLayout['label']) ?: $backendLayout['label'];
|
||||
}
|
||||
}
|
||||
return $backendLayouts;
|
||||
}
|
||||
|
||||
protected function resolveBackendLayoutValue(?string $layoutValue, string $field, array $row): string
|
||||
{
|
||||
$languageService = $this->getLanguageService();
|
||||
if ($layoutValue !== null) {
|
||||
// Directly return the resolved layout value from BackendLayoutView
|
||||
return htmlspecialchars($layoutValue);
|
||||
}
|
||||
$layoutValue = htmlspecialchars((string)BackendUtility::getProcessedValue('pages', $field, $row[$field], 0, false, false, 0, true, 0, $row));
|
||||
if ($layoutValue !== '') {
|
||||
// If getProcessedValue() returns a non-empty string, the database field
|
||||
// is filled with an invalid value (the backend layout does no longer exist).
|
||||
return sprintf($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.noMatchingValue'), $layoutValue);
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
protected function getLanguageService(): LanguageService
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
|
||||
protected function getBackendUser(): BackendUserAuthentication
|
||||
{
|
||||
return $GLOBALS['BE_USER'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,513 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Info\Controller;
|
||||
|
||||
use Psr\EventDispatcher\EventDispatcherInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use TYPO3\CMS\Backend\Attribute\AsController;
|
||||
use TYPO3\CMS\Backend\Module\ModuleProvider;
|
||||
use TYPO3\CMS\Backend\Routing\PreviewUriBuilder;
|
||||
use TYPO3\CMS\Backend\Routing\UriBuilder;
|
||||
use TYPO3\CMS\Backend\Template\Components\ButtonBar;
|
||||
use TYPO3\CMS\Backend\Template\Components\ComponentFactory;
|
||||
use TYPO3\CMS\Backend\Template\ModuleTemplateFactory;
|
||||
use TYPO3\CMS\Backend\Tree\View\PageTreeView;
|
||||
use TYPO3\CMS\Backend\Utility\BackendUtility;
|
||||
use TYPO3\CMS\Backend\View\PageViewMode;
|
||||
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
|
||||
use TYPO3\CMS\Core\Database\Connection;
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
|
||||
use TYPO3\CMS\Core\Database\Query\Restriction\WorkspaceRestriction;
|
||||
use TYPO3\CMS\Core\Imaging\IconFactory;
|
||||
use TYPO3\CMS\Core\Imaging\IconSize;
|
||||
use TYPO3\CMS\Core\Imaging\IconState;
|
||||
use TYPO3\CMS\Core\Localization\LanguageService;
|
||||
use TYPO3\CMS\Core\Schema\Capability\LanguageAwareSchemaCapability;
|
||||
use TYPO3\CMS\Core\Schema\Capability\TcaSchemaCapability;
|
||||
use TYPO3\CMS\Core\Schema\TcaSchemaFactory;
|
||||
use TYPO3\CMS\Core\Type\Bitmask\PageTranslationVisibility;
|
||||
use TYPO3\CMS\Core\Type\Bitmask\Permission;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Info\Controller\Event\ModifyInfoModuleContentEvent;
|
||||
|
||||
/**
|
||||
* Status -> Localization overview
|
||||
*
|
||||
* @internal This class is a specific Backend controller implementation and is not part of the TYPO3's Core API.
|
||||
*/
|
||||
#[AsController]
|
||||
readonly class TranslationStatusController
|
||||
{
|
||||
public function __construct(
|
||||
private IconFactory $iconFactory,
|
||||
private UriBuilder $uriBuilder,
|
||||
private ModuleProvider $moduleProvider,
|
||||
private ModuleTemplateFactory $moduleTemplateFactory,
|
||||
private EventDispatcherInterface $eventDispatcher,
|
||||
private TcaSchemaFactory $tcaSchemaFactory,
|
||||
private ComponentFactory $componentFactory,
|
||||
private ConnectionPool $connectionPool,
|
||||
) {}
|
||||
|
||||
public function handleRequest(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$backendUser = $this->getBackendUser();
|
||||
$languageService = $this->getLanguageService();
|
||||
$module = $request->getAttribute('module');
|
||||
$moduleData = $request->getAttribute('moduleData');
|
||||
$currentSite = $request->getAttribute('site');
|
||||
$pageId = (int)($request->getQueryParams()['id'] ?? $request->getParsedBody()['id'] ?? 0);
|
||||
|
||||
$pageinfo = BackendUtility::readPageAccess($pageId, $backendUser->getPagePermsClause(Permission::PAGE_SHOW)) ?: [];
|
||||
$hasAccess = false;
|
||||
if (($pageId && $pageinfo !== []) || ($backendUser->isAdmin() && $pageId === 0)) {
|
||||
$hasAccess = true;
|
||||
}
|
||||
if ($pageId === 0 && $backendUser->isAdmin()) {
|
||||
$pageinfo = ['title' => '[root-level]', 'uid' => 0, 'pid' => 0];
|
||||
}
|
||||
|
||||
$siteLanguages = $currentSite->getAvailableLanguages($backendUser, false, $pageId);
|
||||
$allowedModuleOptions = $this->getModuleOptions($siteLanguages);
|
||||
if ($moduleData->cleanUp($allowedModuleOptions)) {
|
||||
$backendUser->pushModuleData($moduleData->getModuleIdentifier(), $moduleData->toArray());
|
||||
}
|
||||
$selectedDepth = (int)$moduleData->get('depth');
|
||||
$selectedLanguage = (int)$moduleData->get('lang');
|
||||
|
||||
$mainContent = '';
|
||||
if ($pageId > 0) {
|
||||
$tree = $this->getTree($pageId, $selectedDepth);
|
||||
$mainContent = $this->renderL10nTable($tree, $request, $siteLanguages, $selectedLanguage);
|
||||
}
|
||||
|
||||
$view = $this->moduleTemplateFactory->create($request);
|
||||
$view->assign('hasAccess', $hasAccess);
|
||||
if ($hasAccess) {
|
||||
$view->setTitle($languageService->sL($module->getTitle()), $pageId !== 0 && isset($pageinfo['title']) ? $pageinfo['title'] : '');
|
||||
$view->getDocHeaderComponent()->setPageBreadcrumb($pageinfo);
|
||||
$view->makeDocHeaderModuleMenu(['id' => $pageId]);
|
||||
$view->getDocHeaderComponent()->setShortcutContext($module->getIdentifier(), sprintf('%s [%d]', $languageService->sL($module->getTitle()), $pageId), ['id' => $pageId]);
|
||||
$previewUriBuilder = PreviewUriBuilder::create($pageinfo);
|
||||
if ($previewUriBuilder->isPreviewable()) {
|
||||
$previewDataAttributes = $previewUriBuilder
|
||||
->withRootLine(BackendUtility::BEgetRootLine($pageinfo['uid']))
|
||||
->buildDispatcherDataAttributes();
|
||||
$viewButton = $this->componentFactory->createLinkButton()
|
||||
->setHref('#')
|
||||
->setDataAttributes($previewDataAttributes ?? [])
|
||||
->setDisabled(!$previewDataAttributes)
|
||||
->setTitle($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.showPage'))
|
||||
->setIcon($this->iconFactory->getIcon('actions-view-page', IconSize::SMALL))
|
||||
->setShowLabelText(true);
|
||||
$view->addButtonToButtonBar($viewButton, ButtonBar::BUTTON_POSITION_LEFT, 2);
|
||||
}
|
||||
}
|
||||
$event = $this->eventDispatcher->dispatch(new ModifyInfoModuleContentEvent($hasAccess, $request, $module, $view));
|
||||
if ($hasAccess) {
|
||||
$view->assignMultiple([
|
||||
'pageUid' => $pageId,
|
||||
'depthDropdownOptions' => $allowedModuleOptions['depth'],
|
||||
'depthDropdownCurrentValue' => $selectedDepth,
|
||||
'langDropdownOptions' => $allowedModuleOptions['lang'],
|
||||
'langDropdownCurrentValue' => $selectedLanguage,
|
||||
'content' => $mainContent,
|
||||
'headerContent' => $event->getHeaderContent(),
|
||||
'footerContent' => $event->getFooterContent(),
|
||||
]);
|
||||
}
|
||||
return $view->renderResponse('TranslationStatus');
|
||||
}
|
||||
|
||||
private function getModuleOptions(array $siteLanguages): array
|
||||
{
|
||||
$languageService = $this->getLanguageService();
|
||||
$menuArray = [
|
||||
'depth' => [
|
||||
0 => $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_0'),
|
||||
1 => $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_1'),
|
||||
2 => $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_2'),
|
||||
3 => $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_3'),
|
||||
4 => $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_4'),
|
||||
999 => $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_infi'),
|
||||
],
|
||||
'lang' => [],
|
||||
];
|
||||
foreach ($siteLanguages as $language) {
|
||||
if ($language->getLanguageId() === 0) {
|
||||
$menuArray['lang'][0] = $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.allLanguages');
|
||||
} else {
|
||||
$menuArray['lang'][$language->getLanguageId()] = $language->getTitle();
|
||||
}
|
||||
}
|
||||
return $menuArray;
|
||||
}
|
||||
|
||||
private function getTree(int $pageId, int $selectedDepth): PageTreeView
|
||||
{
|
||||
$tree = GeneralUtility::makeInstance(PageTreeView::class);
|
||||
$tree->init('AND ' . $this->getBackendUser()->getPagePermsClause(Permission::PAGE_SHOW));
|
||||
$tree->tree[] = ['row' => BackendUtility::getRecordWSOL('pages', $pageId)];
|
||||
// Create the tree from starting point
|
||||
if ($selectedDepth) {
|
||||
$tree->getTree($pageId, $selectedDepth);
|
||||
}
|
||||
return $tree;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rendering the localization information table.
|
||||
*
|
||||
* @param PageTreeView $tree The Page tree data
|
||||
* @return string HTML for the localization information table.
|
||||
*/
|
||||
private function renderL10nTable(PageTreeView $tree, ServerRequestInterface $request, array $siteLanguages, int $selectedLanguage): string
|
||||
{
|
||||
$lang = $this->getLanguageService();
|
||||
$backendUser = $this->getBackendUser();
|
||||
// Put together the TREE:
|
||||
$output = '';
|
||||
$langRecUids = [];
|
||||
|
||||
$userTsConfig = $backendUser->getTSConfig();
|
||||
$showPageId = !empty($userTsConfig['options.']['pageTree.']['showPageIdWithTitle']);
|
||||
|
||||
$pageModule = 'web_layout';
|
||||
$pageModuleAccess = $this->moduleProvider->accessGranted($pageModule, $backendUser);
|
||||
|
||||
foreach ($tree->tree as $data) {
|
||||
$tCells = [];
|
||||
$langRecUids[0][] = $data['row']['uid'];
|
||||
$pageTitle = ($showPageId ? '[' . (int)$data['row']['uid'] . '] ' : '') . $data['row']['title'];
|
||||
// Page icons / titles etc.
|
||||
if ($pageModuleAccess) {
|
||||
$pageModuleLink = (string)$this->uriBuilder->buildUriFromRoute($pageModule, ['id' => $data['row']['uid'], 'languages' => [0], 'viewMode' => PageViewMode::LayoutView->value]);
|
||||
$pageModuleLink = '<a href="' . htmlspecialchars($pageModuleLink) . '" title="' . $lang->sL('LLL:EXT:info/Resources/Private/Language/locallang_webinfo.xlf:lang_renderl10n_editPage') . '">' . htmlspecialchars($pageTitle) . '</a>';
|
||||
} else {
|
||||
$pageModuleLink = htmlspecialchars($pageTitle);
|
||||
}
|
||||
$icon = '<span title="' . BackendUtility::getRecordIconAltText($data['row']) . '">'
|
||||
. $this->iconFactory->getIconForRecord('pages', $data['row'], IconSize::SMALL)->setTitle(BackendUtility::getRecordIconAltText($data['row'], 'pages', false))->render()
|
||||
. '</span>';
|
||||
|
||||
if ($backendUser->checkRecordEditAccess('pages', $data['row'])->isAllowed) {
|
||||
$icon = BackendUtility::wrapClickMenuOnIcon($icon, 'pages', $data['row']['uid']);
|
||||
}
|
||||
|
||||
$tCells[] = '<td class="col-title col-responsive">'
|
||||
. '<div class="treeline-container">'
|
||||
. (!empty($data['depthData']) ? $data['depthData'] : '')
|
||||
. ($data['HTML'] ?? '')
|
||||
. $icon
|
||||
. '<span class="treeline-label">'
|
||||
. $pageModuleLink
|
||||
. ((string)$data['row']['nav_title'] !== '' ? ' <span>[Nav: <em>' . htmlspecialchars($data['row']['nav_title']) . '</em>]</span>' : '')
|
||||
. '</span>'
|
||||
. '</div>'
|
||||
. '</td>';
|
||||
$previewUriBuilder = PreviewUriBuilder::create($data['row']);
|
||||
// DEFAULT language:
|
||||
$pageTranslationVisibility = new PageTranslationVisibility((int)($data['row']['l18n_cfg'] ?? 0));
|
||||
$status = $pageTranslationVisibility->shouldBeHiddenInDefaultLanguage() ? 'danger' : 'success';
|
||||
// Create links:
|
||||
$editUrl = (string)$this->uriBuilder->buildUriFromRoute('record_edit', [
|
||||
'edit' => [
|
||||
'pages' => [
|
||||
$data['row']['uid'] => 'edit',
|
||||
],
|
||||
],
|
||||
'module' => 'web_info_translations',
|
||||
'returnUrl' => $request->getAttribute('normalizedParams')->getRequestUri(),
|
||||
]);
|
||||
$info = '<button ' . ($previewUriBuilder->serializeDispatcherAttributes() ?? 'disabled="true"')
|
||||
. ' class="btn btn-default" title="' . $lang->sL('LLL:EXT:info/Resources/Private/Language/locallang_webinfo.xlf:lang_renderl10n_viewPage') . '">'
|
||||
. $this->iconFactory->getIcon('actions-view-page', IconSize::SMALL)->render() . '</button>';
|
||||
if ($backendUser->check('tables_modify', 'pages')) {
|
||||
$info .= '<a href="' . htmlspecialchars($editUrl)
|
||||
. '" class="btn btn-default" title="' . $lang->sL(
|
||||
'LLL:EXT:info/Resources/Private/Language/locallang_webinfo.xlf:lang_renderl10n_editPageProperties'
|
||||
) . '">' . $this->iconFactory->getIcon('actions-page-open', IconSize::SMALL)->render() . '</a>';
|
||||
}
|
||||
$info .= ' ';
|
||||
$info .= $pageTranslationVisibility->shouldBeHiddenInDefaultLanguage() ? '<span title="' . htmlspecialchars($lang->sL('LLL:EXT:frontend/Resources/Private/Language/locallang_tca.xlf:pages.l18n_cfg.I.1')) . '">D</span>' : ' ';
|
||||
$info .= $pageTranslationVisibility->shouldHideTranslationIfNoTranslatedRecordExists() ? '<span title="' . htmlspecialchars($lang->sL('LLL:EXT:frontend/Resources/Private/Language/locallang_tca.xlf:pages.l18n_cfg.I.2')) . '">N</span>' : ' ';
|
||||
// Put into cell:
|
||||
$tCells[] = '<td class="' . $status . ' col-border-left col-nowrap"><div class="btn-group btn-group-sm">' . $info . '</div></td>';
|
||||
$tCells[] = '<td class="' . $status . '" title="' . $lang->sL('LLL:EXT:info/Resources/Private/Language/locallang_webinfo.xlf:lang_renderl10n_CEcount') . '" align="center">'
|
||||
. ($this->getContentElementCount((int)$data['row']['uid'], 0) ?: '-')
|
||||
. '</td>';
|
||||
// Traverse system languages:
|
||||
foreach ($siteLanguages as $siteLanguage) {
|
||||
$languageId = $siteLanguage->getLanguageId();
|
||||
if ($languageId === 0) {
|
||||
continue;
|
||||
}
|
||||
if ($selectedLanguage === 0 || $selectedLanguage === $languageId) {
|
||||
$row = $this->getLangStatus((int)$data['row']['uid'], $languageId);
|
||||
if ($pageTranslationVisibility->shouldBeHiddenInDefaultLanguage() || $pageTranslationVisibility->shouldHideTranslationIfNoTranslatedRecordExists()) {
|
||||
$status = 'danger';
|
||||
} else {
|
||||
$status = '';
|
||||
}
|
||||
if (is_array($row)) {
|
||||
$langRecUids[$languageId][] = $row['uid'];
|
||||
if (!$row['_HIDDEN']) {
|
||||
$status = 'success';
|
||||
}
|
||||
if ($row['_COUNT'] > 1) {
|
||||
$status = 'warning';
|
||||
}
|
||||
$info = ($showPageId ? ' [' . (int)$row['uid'] . '] ' : '')
|
||||
. htmlspecialchars($row['title'])
|
||||
. ((string)$row['nav_title'] !== '' ? ' [Nav: <em>' . htmlspecialchars($row['nav_title']) . '</em>]' : '')
|
||||
. ($row['_COUNT'] > 1 ? '<div>' . $lang->sL('LLL:EXT:info/Resources/Private/Language/locallang_webinfo.xlf:lang_renderl10n_badThingThereAre') . '</div>' : '');
|
||||
|
||||
if ($pageModuleAccess) {
|
||||
$pageModuleLink = (string)$this->uriBuilder->buildUriFromRoute($pageModule, ['id' => $data['row']['uid'], 'language' => [$languageId], 'viewMode' => PageViewMode::LanguageComparisonView->value]);
|
||||
$pageModuleLink = '<a href="' . htmlspecialchars($pageModuleLink) . '" title="' . $lang->sL('LLL:EXT:info/Resources/Private/Language/locallang_webinfo.xlf:lang_renderl10n_editTranslatedPage') . '">' . $info . '</a>';
|
||||
} else {
|
||||
$pageModuleLink = $info;
|
||||
}
|
||||
$icon = '<span title="' . BackendUtility::getRecordIconAltText($row) . '">'
|
||||
. $this->iconFactory->getIconForRecord('pages', $row, IconSize::SMALL)->setTitle(BackendUtility::getRecordIconAltText($row, 'pages', false))->render()
|
||||
. '</span>';
|
||||
$tCells[] = '<td class="col-responsive col-border-left ' . $status . '">'
|
||||
. BackendUtility::wrapClickMenuOnIcon($icon, 'pages', (int)$row['uid'])
|
||||
. $pageModuleLink
|
||||
. '</td>';
|
||||
// Edit whole record:
|
||||
// Create links:
|
||||
$editUrl = (string)$this->uriBuilder->buildUriFromRoute('record_edit', [
|
||||
'edit' => [
|
||||
'pages' => [
|
||||
$row['uid'] => 'edit',
|
||||
],
|
||||
],
|
||||
'module' => 'web_info_translations',
|
||||
'returnUrl' => $request->getAttribute('normalizedParams')->getRequestUri(),
|
||||
]);
|
||||
// ViewPageLink
|
||||
$info = '<button ' . ($previewUriBuilder
|
||||
->withLanguage($languageId)
|
||||
->serializeDispatcherAttributes() ?? 'disabled="true"')
|
||||
. ' class="btn btn-default" title="' . $lang->sL('LLL:EXT:info/Resources/Private/Language/locallang_webinfo.xlf:lang_renderl10n_viewTranslatedPage') . '">'
|
||||
. $this->iconFactory->getIcon('actions-view', IconSize::SMALL)->render() . '</button>';
|
||||
$info .= '<a href="' . htmlspecialchars($editUrl)
|
||||
. '" class="btn btn-default" title="' . $lang->sL(
|
||||
'LLL:EXT:info/Resources/Private/Language/locallang_webinfo.xlf:lang_renderl10n_editTranslatedPageProperties'
|
||||
) . '">' . $this->iconFactory->getIcon('actions-open', IconSize::SMALL)->render() . '</a>';
|
||||
$tCells[] = '<td class="' . $status . '"><div class="btn-group btn-group-sm">' . $info . '</div></td>';
|
||||
$tCells[] = '<td class="' . $status . '" title="' . $lang->sL(
|
||||
'LLL:EXT:info/Resources/Private/Language/locallang_webinfo.xlf:lang_renderl10n_CEcount'
|
||||
) . '" align="center">' . ($this->getContentElementCount((int)$data['row']['uid'], $languageId) ?: '-') . '</td>';
|
||||
} else {
|
||||
$idName = sprintf('new-overlay-%d-%d', $languageId, $data['row']['uid']);
|
||||
$info = '<div class="form-check form-check-type-icon-toggle">'
|
||||
. '<input type="checkbox" data-lang="' . $languageId . '" data-uid="' . (int)$data['row']['uid'] . '" name="newOL[' . $languageId . '][' . $data['row']['uid'] . ']" id="' . htmlspecialchars($idName) . '" class="form-check-input" value="1" />'
|
||||
. '<label class="form-check-label" for="' . $idName . '">'
|
||||
. '<span class="form-check-label-icon">'
|
||||
. '<span class="form-check-label-icon-checked">' . $this->iconFactory->getIcon('actions-check', IconSize::SMALL)->render() . '</span>'
|
||||
. '<span class="form-check-label-icon-unchecked">' . $this->iconFactory->getIcon('empty-empty', IconSize::SMALL)->render() . '</span>'
|
||||
. '</span>'
|
||||
. '</label>'
|
||||
. '</div>';
|
||||
$tCells[] = '<td class="' . $status . ' col-border-left"> </td>';
|
||||
$tCells[] = '<td class="' . $status . '"> </td>';
|
||||
$tCells[] = '<td class="' . $status . '">' . $info . '</td>';
|
||||
}
|
||||
}
|
||||
}
|
||||
$output .= '<tr>' . implode('', $tCells) . '</tr>';
|
||||
}
|
||||
// Put together HEADER:
|
||||
$headerCells = [];
|
||||
$headerCells[] = '<th>' . $lang->sL('LLL:EXT:info/Resources/Private/Language/locallang_webinfo.xlf:lang_renderl10n_page') . '</th>';
|
||||
if ($backendUser->check('tables_modify', 'pages')) {
|
||||
$editUrl = (string)$this->uriBuilder->buildUriFromRoute('record_edit', [
|
||||
'edit' => [
|
||||
'pages' => [
|
||||
implode(',', $langRecUids[0]) => 'edit',
|
||||
],
|
||||
],
|
||||
'columnsOnly' => [
|
||||
'pages' => ['title', 'nav_title', 'l18n_cfg', 'hidden'],
|
||||
],
|
||||
'module' => 'web_info_translations',
|
||||
'returnUrl' => $request->getAttribute('normalizedParams')->getRequestUri(),
|
||||
]);
|
||||
$editIco = '<a href="' . htmlspecialchars($editUrl)
|
||||
. '" class="btn btn-default btn-sm" title="' . $lang->sL(
|
||||
'LLL:EXT:info/Resources/Private/Language/locallang_webinfo.xlf:lang_renderl10n_editAllPageProperties'
|
||||
) . '">' . $this->iconFactory->getIcon('actions-document-open', IconSize::SMALL)->render() . '</a>';
|
||||
} else {
|
||||
$editIco = '';
|
||||
}
|
||||
if (isset($siteLanguages[0])) {
|
||||
$defaultLanguageLabel = $siteLanguages[0]->getTitle();
|
||||
} else {
|
||||
$defaultLanguageLabel = $lang->sL('LLL:EXT:info/Resources/Private/Language/locallang_webinfo.xlf:lang_renderl10n_default');
|
||||
}
|
||||
$headerCells[] = '<th class="col-border-left" colspan="2">' . htmlspecialchars($defaultLanguageLabel) . ' ' . $editIco . '</th>';
|
||||
foreach ($siteLanguages as $siteLanguage) {
|
||||
$languageId = $siteLanguage->getLanguageId();
|
||||
if ($languageId === 0) {
|
||||
continue;
|
||||
}
|
||||
if ($selectedLanguage === 0 || $selectedLanguage === $languageId) {
|
||||
// Title:
|
||||
$headerCells[] = '<th class="col-border-left">' . htmlspecialchars($siteLanguage->getTitle()) . '</th>';
|
||||
// Edit language overlay records:
|
||||
if (is_array($langRecUids[$languageId] ?? null)) {
|
||||
$editUrl = (string)$this->uriBuilder->buildUriFromRoute('record_edit', [
|
||||
'edit' => [
|
||||
'pages' => [
|
||||
implode(',', $langRecUids[$languageId]) => 'edit',
|
||||
],
|
||||
],
|
||||
'columnsOnly' => [
|
||||
'pages' => ['title', 'nav_title', 'hidden'],
|
||||
],
|
||||
'module' => 'web_info_translations',
|
||||
'returnUrl' => $request->getAttribute('normalizedParams')->getRequestUri(),
|
||||
]);
|
||||
$editButton = '<a href="' . htmlspecialchars($editUrl)
|
||||
. '" class="btn btn-default" title="' . $lang->sL(
|
||||
'LLL:EXT:info/Resources/Private/Language/locallang_webinfo.xlf:lang_renderl10n_editAllTranslationedPageProperties'
|
||||
) . '">' . $this->iconFactory->getIcon('actions-document-open', IconSize::SMALL)->render() . '</a>';
|
||||
} else {
|
||||
$editButton = '';
|
||||
}
|
||||
// Create new overlay records:
|
||||
$createLink = (string)$this->uriBuilder->buildUriFromRoute('tce_db', [
|
||||
'redirect' => $request->getAttribute('normalizedParams')->getRequestUri(),
|
||||
]);
|
||||
$newButton = '<a href="' . htmlspecialchars($createLink) . '" data-edit-url="' . htmlspecialchars($createLink) . '" class="btn btn-default btn-sm disabled t3js-language-new" data-lang="' . $languageId . '" title="' . $lang->sL(
|
||||
'LLL:EXT:info/Resources/Private/Language/locallang_webinfo.xlf:lang_getlangsta_createNewTranslationHeaders'
|
||||
) . '">' . $this->iconFactory->getIcon('actions-document-new', IconSize::SMALL, null, IconState::STATE_DISABLED)->render() . '</a>';
|
||||
|
||||
$headerCells[] = '<th>' . $editButton . '</th>';
|
||||
$headerCells[] = '<th>' . $newButton . '</th>';
|
||||
}
|
||||
}
|
||||
|
||||
$output
|
||||
= '<div class="table-fit">'
|
||||
. '<table class="table table-striped table-hover" id="langTable">'
|
||||
. '<thead>'
|
||||
. '<tr>'
|
||||
. implode('', $headerCells)
|
||||
. '</tr>'
|
||||
. '</thead>'
|
||||
. '<tbody>'
|
||||
. $output
|
||||
. '</tbody>'
|
||||
. '</table>'
|
||||
. '</div>';
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an alternative language record for a specific page / language
|
||||
*
|
||||
* @param int $pageId Page ID to look up for.
|
||||
* @param int $langId Language UID to select for.
|
||||
* @return array|bool translated pages record
|
||||
*/
|
||||
private function getLangStatus(int $pageId, int $langId): bool|array
|
||||
{
|
||||
$schema = $this->tcaSchemaFactory->get('pages');
|
||||
/** @var LanguageAwareSchemaCapability $languageCapability */
|
||||
$languageCapability = $schema->getCapability(TcaSchemaCapability::Language);
|
||||
$queryBuilder = $this->connectionPool->getQueryBuilderForTable('pages');
|
||||
$queryBuilder
|
||||
->getRestrictions()
|
||||
->removeAll()
|
||||
->add(GeneralUtility::makeInstance(WorkspaceRestriction::class, $this->getBackendUser()->workspace))
|
||||
->add(GeneralUtility::makeInstance(DeletedRestriction::class));
|
||||
$result = $queryBuilder
|
||||
->select('*')
|
||||
->from('pages')
|
||||
->where(
|
||||
$queryBuilder->expr()->eq(
|
||||
$languageCapability->getTranslationOriginPointerField()->getName(),
|
||||
$queryBuilder->createNamedParameter($pageId, Connection::PARAM_INT)
|
||||
)
|
||||
)
|
||||
->andWhere(
|
||||
$queryBuilder->expr()->eq(
|
||||
$languageCapability->getLanguageField()->getName(),
|
||||
$queryBuilder->createNamedParameter($langId, Connection::PARAM_INT)
|
||||
)
|
||||
)
|
||||
->executeQuery();
|
||||
|
||||
$row = $result->fetchAssociative();
|
||||
BackendUtility::workspaceOL('pages', $row);
|
||||
if (is_array($row)) {
|
||||
$row['_COUNT'] = $queryBuilder->count('uid')->executeQuery()->fetchOne();
|
||||
$row['_HIDDEN'] = $row['hidden'] || (int)$row['endtime'] > 0 && (int)$row['endtime'] < $GLOBALS['EXEC_TIME'] || $GLOBALS['EXEC_TIME'] < (int)$row['starttime'];
|
||||
}
|
||||
$result->free();
|
||||
return $row;
|
||||
}
|
||||
|
||||
/**
|
||||
* Counting content elements for a single language on a page.
|
||||
*
|
||||
* @param int $pageId Page id to select for.
|
||||
* @param int $sysLang Sys language uid
|
||||
* @return int Number of content elements from the PID where the language is set to a certain value.
|
||||
*/
|
||||
private function getContentElementCount(int $pageId, int $sysLang): int
|
||||
{
|
||||
$queryBuilder = $this->connectionPool->getQueryBuilderForTable('tt_content');
|
||||
$queryBuilder->getRestrictions()
|
||||
->removeAll()
|
||||
->add(GeneralUtility::makeInstance(DeletedRestriction::class))
|
||||
->add(GeneralUtility::makeInstance(WorkspaceRestriction::class, $this->getBackendUser()->workspace));
|
||||
return (int)$queryBuilder
|
||||
->count('uid')
|
||||
->from('tt_content')
|
||||
->where(
|
||||
$queryBuilder->expr()->eq(
|
||||
'pid',
|
||||
$queryBuilder->createNamedParameter($pageId, Connection::PARAM_INT)
|
||||
)
|
||||
)
|
||||
->andWhere(
|
||||
$queryBuilder->expr()->eq(
|
||||
'sys_language_uid',
|
||||
$queryBuilder->createNamedParameter($sysLang, Connection::PARAM_INT)
|
||||
)
|
||||
)
|
||||
->executeQuery()
|
||||
->fetchOne();
|
||||
}
|
||||
|
||||
private function getLanguageService(): LanguageService
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
|
||||
private function getBackendUser(): BackendUserAuthentication
|
||||
{
|
||||
return $GLOBALS['BE_USER'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
use TYPO3\CMS\Info\Controller\PageInformationController;
|
||||
use TYPO3\CMS\Info\Controller\TranslationStatusController;
|
||||
|
||||
/**
|
||||
* Definitions for modules provided by EXT:info
|
||||
*/
|
||||
return [
|
||||
'web_info_overview' => [
|
||||
'parent' => 'content_status',
|
||||
'position' => ['before' => '*'],
|
||||
'access' => 'user',
|
||||
'path' => '/module/web/info/overview',
|
||||
'iconIdentifier' => 'module-info',
|
||||
'labels' => 'info.modules.overview',
|
||||
'routes' => [
|
||||
'_default' => [
|
||||
'target' => PageInformationController::class . '::handleRequest',
|
||||
],
|
||||
],
|
||||
'moduleData' => [
|
||||
'pages' => '0',
|
||||
'depth' => 0,
|
||||
'lang' => 0,
|
||||
],
|
||||
],
|
||||
'web_info_translations' => [
|
||||
'parent' => 'content_status',
|
||||
'position' => ['after' => 'web_info_overview'],
|
||||
'access' => 'user',
|
||||
'path' => '/module/web/info/translations',
|
||||
'iconIdentifier' => 'module-info',
|
||||
'labels' => 'info.modules.translations',
|
||||
'routes' => [
|
||||
'_default' => [
|
||||
'target' => TranslationStatusController::class . '::handleRequest',
|
||||
],
|
||||
],
|
||||
'moduleData' => [
|
||||
'depth' => 0,
|
||||
'lang' => 0,
|
||||
],
|
||||
],
|
||||
];
|
||||
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'dependencies' => [],
|
||||
'imports' => [
|
||||
'@typo3/info/' => 'EXT:info/Resources/Public/JavaScript/',
|
||||
],
|
||||
];
|
||||
@@ -0,0 +1,8 @@
|
||||
services:
|
||||
_defaults:
|
||||
autowire: true
|
||||
autoconfigure: true
|
||||
public: false
|
||||
|
||||
TYPO3\CMS\Info\:
|
||||
resource: '../Classes/*'
|
||||
@@ -0,0 +1,18 @@
|
||||
mod.web_info.fieldDefinitions {
|
||||
0 {
|
||||
label = LLL:EXT:info/Resources/Private/Language/locallang_webinfo.xlf:pages_0
|
||||
fields = title,uid,sys_language_uid,slug,starttime,endtime,fe_group,target,link,shortcut,shortcut_mode
|
||||
}
|
||||
1 {
|
||||
label = LLL:EXT:info/Resources/Private/Language/locallang_webinfo.xlf:pages_1
|
||||
fields = title,uid,###ALL_TABLES###
|
||||
}
|
||||
2 {
|
||||
label = LLL:EXT:info/Resources/Private/Language/locallang_webinfo.xlf:pages_2
|
||||
fields = title,uid,sys_language_uid,lastUpdated,newUntil,cache_timeout,php_tree_stop,TSconfig,is_siteroot
|
||||
}
|
||||
3 {
|
||||
label = LLL:EXT:info/Resources/Private/Language/locallang_webinfo.xlf:pages_layouts
|
||||
fields = title,uid,sys_language_uid,actual_backend_layout,backend_layout,backend_layout_next_level,layout
|
||||
}
|
||||
}
|
||||
+339
@@ -0,0 +1,339 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
License is intended to guarantee your freedom to share and change free
|
||||
software--to make sure the software is free for all its users. This
|
||||
General Public License applies to most of the Free Software
|
||||
Foundation's software and to any other program whose authors commit to
|
||||
using it. (Some other Free Software Foundation software is covered by
|
||||
the GNU Lesser General Public License instead.) You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
this service if you wish), that you receive source code or can get it
|
||||
if you want it, that you can change the software or use pieces of it
|
||||
in new free programs; and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if you
|
||||
distribute copies of the software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must give the recipients all the rights that
|
||||
you have. You must make sure that they, too, receive or can get the
|
||||
source code. And you must show them these terms so they know their
|
||||
rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and
|
||||
(2) offer you this license which gives you legal permission to copy,
|
||||
distribute and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
software. If the software is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original, so
|
||||
that any problems introduced by others will not reflect on the original
|
||||
authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that redistributors of a free
|
||||
program will individually obtain patent licenses, in effect making the
|
||||
program proprietary. To prevent this, we have made it clear that any
|
||||
patent must be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
a notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this General Public License. The "Program", below,
|
||||
refers to any such program or work, and a "work based on the Program"
|
||||
means either the Program or any derivative work under copyright law:
|
||||
that is to say, a work containing the Program or a portion of it,
|
||||
either verbatim or with modifications and/or translated into another
|
||||
language. (Hereinafter, translation is included without limitation in
|
||||
the term "modification".) Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running the Program is not restricted, and the output from the Program
|
||||
is covered only if its contents constitute a work based on the
|
||||
Program (independent of having been made by running the Program).
|
||||
Whether that is true depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's
|
||||
source code as you receive it, in any medium, provided that you
|
||||
conspicuously and appropriately publish on each copy an appropriate
|
||||
copyright notice and disclaimer of warranty; keep intact all the
|
||||
notices that refer to this License and to the absence of any warranty;
|
||||
and give any other recipients of the Program a copy of this License
|
||||
along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and
|
||||
you may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion
|
||||
of it, thus forming a work based on the Program, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in
|
||||
whole or in part contains or is derived from the Program or any
|
||||
part thereof, to be licensed as a whole at no charge to all third
|
||||
parties under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the most ordinary way, to print or display an
|
||||
announcement including an appropriate copyright notice and a
|
||||
notice that there is no warranty (or else, saying that you provide
|
||||
a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Program, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it,
|
||||
under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable
|
||||
source code, which must be distributed under the terms of Sections
|
||||
1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three
|
||||
years, to give any third party, for a charge no more than your
|
||||
cost of physically performing source distribution, a complete
|
||||
machine-readable copy of the corresponding source code, to be
|
||||
distributed under the terms of Sections 1 and 2 above on a medium
|
||||
customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer
|
||||
to distribute corresponding source code. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form with such
|
||||
an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source
|
||||
code means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to
|
||||
control compilation and installation of the executable. However, as a
|
||||
special exception, the source code distributed need not include
|
||||
anything that is normally distributed (in either source or binary
|
||||
form) with the major components (compiler, kernel, and so on) of the
|
||||
operating system on which the executable runs, unless that component
|
||||
itself accompanies the executable.
|
||||
|
||||
If distribution of executable or object code is made by offering
|
||||
access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
void, and will automatically terminate your rights under this License.
|
||||
However, parties who have received copies, or rights, from you under
|
||||
this License will not have their licenses terminated so long as such
|
||||
parties remain in full compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Program or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Program (or any work based on the
|
||||
Program), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Program at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Program by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under
|
||||
any particular circumstance, the balance of the section is intended to
|
||||
apply and the section as a whole is intended to apply in other
|
||||
circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system, which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
may add an explicit geographical distribution limitation excluding
|
||||
those countries, so that distribution is permitted only in or among
|
||||
countries not thus excluded. In such case, this License incorporates
|
||||
the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions
|
||||
of the General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free Software
|
||||
Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free
|
||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||
REPAIR OR CORRECTION.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program is interactive, make it output a short notice like this
|
||||
when it starts in an interactive mode:
|
||||
|
||||
Gnomovision version 69, Copyright (C) year name of author
|
||||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, the commands you use may
|
||||
be called something other than `show w' and `show c'; they could even be
|
||||
mouse-clicks or menu items--whatever suits your program.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1989
|
||||
Ty Coon, President of Vice
|
||||
|
||||
This General Public License does not permit incorporating your program into
|
||||
proprietary programs. If your program is a subroutine library, you may
|
||||
consider it more useful to permit linking proprietary applications with the
|
||||
library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License.
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
========================
|
||||
TYPO3 extension ``info``
|
||||
========================
|
||||
|
||||
This TYPO3 backend module displays general information, such as a page tree
|
||||
overview and localization information.
|
||||
|
||||
:Repository: https://github.com/typo3/typo3
|
||||
:Issues: https://forge.typo3.org/
|
||||
:Read online: https://docs.typo3.org/
|
||||
:Packagist: https://packagist.org/packages/typo3/cms-info
|
||||
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
|
||||
<file source-language="en" datatype="plaintext" original="EXT:info/Resources/Private/Language/Modules/overview.xlf" date="2026-11-10T13:37:37Z" product-name="overview">
|
||||
<header/>
|
||||
<body>
|
||||
<trans-unit id="description">
|
||||
<source>View page records and settings in a tree structure with detailed metadata.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="title">
|
||||
<source>Pagetree Overview</source>
|
||||
</trans-unit>
|
||||
<!-- intentionally left blank, not utilized (for now) -->
|
||||
<trans-unit id="short_description">
|
||||
<source/>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
</xliff>
|
||||
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
|
||||
<file source-language="en" datatype="plaintext" original="EXT:info/Resources/Private/Language/Modules/translations.xlf" date="2026-11-10T13:37:37Z" product-name="translations">
|
||||
<header/>
|
||||
<body>
|
||||
<trans-unit id="description">
|
||||
<source>Check translation status and manage localized content for pages.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="title">
|
||||
<source>Localization Overview</source>
|
||||
</trans-unit>
|
||||
<!-- intentionally left blank, not utilized (for now) -->
|
||||
<trans-unit id="short_description">
|
||||
<source/>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
</xliff>
|
||||
@@ -0,0 +1,89 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
|
||||
<file source-language="en" datatype="plaintext" original="EXT:info/Resources/Private/Language/locallang_webinfo.xlf" date="2011-10-17T20:22:32Z" product-name="cms">
|
||||
<header/>
|
||||
<body>
|
||||
<trans-unit id="page_title">
|
||||
<source>Pagetree overview</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="pages_0">
|
||||
<source>Basic settings</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="pages_2">
|
||||
<source>Cache and Age</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="pages_1">
|
||||
<source>Record overview</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="pages_layouts">
|
||||
<source>Layouts</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="actual_backend_layout">
|
||||
<source>Actual backend layout</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="lang_title">
|
||||
<source>Localization overview</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="lang_renderl10n_editPage">
|
||||
<source>Edit page content</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="lang_renderl10n_editTranslatedPage">
|
||||
<source>Edit translated page content</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="lang_renderl10n_viewPage">
|
||||
<source>View page</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="lang_renderl10n_viewTranslatedPage">
|
||||
<source>View translated page</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="lang_renderl10n_editPageProperties">
|
||||
<source>Edit page properties</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="lang_renderl10n_editTranslatedPageProperties">
|
||||
<source>Edit translated page properties</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="lang_renderl10n_editAllPageProperties">
|
||||
<source>Edit all page properties</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="lang_renderl10n_editAllTranslationedPageProperties">
|
||||
<source>Edit all translated page properties</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="lang_renderl10n_badThingThereAre">
|
||||
<source>Multiple translated page records exist for this language, but only one is allowed. Please remove the extra records.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="lang_renderl10n_page">
|
||||
<source>Page</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="lang_renderl10n_default">
|
||||
<source>Default</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="lang_getlangsta_createNewTranslationHeaders">
|
||||
<source>Create new translation headers</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="lang_renderl10n_CEcount">
|
||||
<source>Content Element Count</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="moduleFunctions.depth">
|
||||
<source>Depth</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="moduleFunctions.type">
|
||||
<source>Type</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="moduleFunctions.lang">
|
||||
<source>Language</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="error.noAccess.title">
|
||||
<source>No access</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="error.noAccess.message">
|
||||
<source>You don't have access to this module.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="info.noContent.message">
|
||||
<source>There are no page information available.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="info.noPageSelected.message">
|
||||
<source>Please select a page in the page tree.</source>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
</xliff>
|
||||
@@ -0,0 +1,27 @@
|
||||
<html
|
||||
xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers"
|
||||
data-namespace-typo3-fluid="true"
|
||||
>
|
||||
|
||||
<f:render section="Before" arguments="{_all}" optional="true" />
|
||||
|
||||
<div class="module {moduleClass}" data-module-id="{moduleId}" data-module-name="{moduleName}">
|
||||
<f:if condition="{docHeader.enabled}">
|
||||
<f:render partial="DocHeader" arguments="{docHeader:docHeader}" />
|
||||
</f:if>
|
||||
<div class="module-body t3js-module-body">
|
||||
<f:if condition="{uiBlock}">
|
||||
<div id="t3js-ui-block" class="ui-block">
|
||||
<core:icon identifier="spinner-circle" size="large" />
|
||||
</div>
|
||||
</f:if>
|
||||
<f:flashMessages queueIdentifier="{flashMessageQueueIdentifier}" />
|
||||
<f:format.raw>{headerContent}</f:format.raw>
|
||||
<f:render section="Content" arguments="{_all}" optional="true" />
|
||||
<f:format.raw>{footerContent}</f:format.raw>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<f:render section="After" arguments="{_all}" optional="true" />
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,24 @@
|
||||
<html
|
||||
xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers"
|
||||
data-namespace-typo3-fluid="true"
|
||||
>
|
||||
|
||||
<div class="input-group">
|
||||
<select
|
||||
name="{name}"
|
||||
id="{id}"
|
||||
data-global-event="change"
|
||||
data-action-navigate="$data=~s/$value/"
|
||||
data-action-submit="$form"
|
||||
class="form-select"
|
||||
data-menu-identifier="{id}"
|
||||
>
|
||||
<f:for each="{options}" as="label" key="value">
|
||||
<option value="{value}"
|
||||
{f:if(condition: '{value} == {currentValue}', then:'selected="selected"')}
|
||||
>{label}</option>
|
||||
</f:for>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,80 @@
|
||||
<html
|
||||
xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers"
|
||||
data-namespace-typo3-fluid="true"
|
||||
>
|
||||
|
||||
<f:layout name="Module"/>
|
||||
|
||||
<f:section name="Before">
|
||||
<f:asset.module identifier="@typo3/backend/context-menu.js"/>
|
||||
<f:asset.module identifier="@typo3/backend/element/immediate-action-element.js"/>
|
||||
<f:if condition="{hasAccess}">
|
||||
<f:variable name="args" value="{0: 'web', 1: pageUid}"/>
|
||||
<typo3-immediate-action
|
||||
action="TYPO3.Backend.Storage.ModuleStateStorage.update"
|
||||
args="{args -> f:format.json() -> f:format.htmlspecialchars()}"
|
||||
></typo3-immediate-action>
|
||||
</f:if>
|
||||
</f:section>
|
||||
|
||||
<f:section name="Content">
|
||||
|
||||
<f:if condition="!{hasAccess}">
|
||||
<f:then>
|
||||
<f:be.infobox
|
||||
title="{f:translate(key:'LLL:EXT:info/Resources/Private/Language/locallang_webinfo.xlf:error.noAccess.title')}"
|
||||
message="{f:translate(key:'LLL:EXT:info/Resources/Private/Language/locallang_webinfo.xlf:error.noAccess.message')}"
|
||||
state="{f:constant(name: 'TYPO3\CMS\Core\Type\ContextualFeedbackSeverity::ERROR')}"
|
||||
/>
|
||||
</f:then>
|
||||
<f:else>
|
||||
<h1><f:translate key="LLL:EXT:info/Resources/Private/Language/locallang_webinfo.xlf:page_title" /></h1>
|
||||
<f:if condition="{pageUid} == 0">
|
||||
<f:then>
|
||||
<f:be.infobox
|
||||
message="{f:translate(key:'LLL:EXT:info/Resources/Private/Language/locallang_webinfo.xlf:info.noPageSelected.message')}"
|
||||
state="{f:constant(name: 'TYPO3\CMS\Core\Type\ContextualFeedbackSeverity::INFO')}"
|
||||
/>
|
||||
</f:then>
|
||||
<f:else>
|
||||
<f:if condition="{content}">
|
||||
<f:then>
|
||||
<form action="{f:be.uri(route: 'web_info_overview', parameters: '{id: pageUid}')}" method="post">
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="lang">
|
||||
<f:translate key="LLL:EXT:info/Resources/Private/Language/locallang_webinfo.xlf:moduleFunctions.lang" />
|
||||
</label>
|
||||
<f:render partial="DropdownMenu" arguments="{name: 'lang', id: 'lang', options: langDropdownOptions, currentValue: langDropdownCurrentValue}"/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="depth">
|
||||
<f:translate key="LLL:EXT:info/Resources/Private/Language/locallang_webinfo.xlf:moduleFunctions.depth"/>
|
||||
</label>
|
||||
<f:render partial="DropdownMenu" arguments="{name: 'depth', id: 'depth', options: depthDropdownOptions, currentValue: depthDropdownCurrentValue}"/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="pages">
|
||||
<f:translate key="LLL:EXT:info/Resources/Private/Language/locallang_webinfo.xlf:moduleFunctions.type"/>
|
||||
</label>
|
||||
<f:render partial="DropdownMenu" arguments="{name: 'pages', id: 'pages', options: pagesDropdownOptions, currentValue: pagesDropdownCurrentValue}"/>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<f:format.raw>{content}</f:format.raw>
|
||||
</f:then>
|
||||
<f:else>
|
||||
<f:be.infobox
|
||||
message="{f:translate(key:'LLL:EXT:info/Resources/Private/Language/locallang_webinfo.xlf:info.noContent.message')}"
|
||||
state="{f:constant(name: 'TYPO3\CMS\Core\Type\ContextualFeedbackSeverity::NOTICE')}"
|
||||
/>
|
||||
</f:else>
|
||||
</f:if>
|
||||
</f:else>
|
||||
</f:if>
|
||||
</f:else>
|
||||
</f:if>
|
||||
|
||||
</f:section>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,75 @@
|
||||
<html
|
||||
xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers"
|
||||
data-namespace-typo3-fluid="true"
|
||||
>
|
||||
|
||||
<f:layout name="Module"/>
|
||||
|
||||
<f:section name="Before">
|
||||
<f:asset.module identifier="@typo3/backend/context-menu.js"/>
|
||||
<f:asset.module identifier="@typo3/backend/element/immediate-action-element.js"/>
|
||||
<f:asset.module identifier="@typo3/info/translation-status.js"/>
|
||||
<f:if condition="{hasAccess}">
|
||||
<f:variable name="args" value="{0: 'web', 1: pageUid}" />
|
||||
<typo3-immediate-action
|
||||
action="TYPO3.Backend.Storage.ModuleStateStorage.update"
|
||||
args="{args -> f:format.json() -> f:format.htmlspecialchars()}"
|
||||
></typo3-immediate-action>
|
||||
</f:if>
|
||||
</f:section>
|
||||
|
||||
<f:section name="Content">
|
||||
|
||||
<f:if condition="!{hasAccess}">
|
||||
<f:then>
|
||||
<f:be.infobox
|
||||
title="{f:translate(key:'LLL:EXT:info/Resources/Private/Language/locallang_webinfo.xlf:error.noAccess.title')}"
|
||||
message="{f:translate(key:'LLL:EXT:info/Resources/Private/Language/locallang_webinfo.xlf:error.noAccess.message')}"
|
||||
state="{f:constant(name: 'TYPO3\CMS\Core\Type\ContextualFeedbackSeverity::ERROR')}"
|
||||
/>
|
||||
</f:then>
|
||||
<f:else>
|
||||
<h1><f:translate key="LLL:EXT:info/Resources/Private/Language/locallang_webinfo.xlf:lang_title" /></h1>
|
||||
<f:if condition="{pageUid} == 0">
|
||||
<f:then>
|
||||
<f:be.infobox
|
||||
message="{f:translate(key:'LLL:EXT:info/Resources/Private/Language/locallang_webinfo.xlf:info.noPageSelected.message')}"
|
||||
state="{f:constant(name: 'TYPO3\CMS\Core\Type\ContextualFeedbackSeverity::INFO')}"
|
||||
/>
|
||||
</f:then>
|
||||
<f:else>
|
||||
<f:if condition="{content}">
|
||||
<f:then>
|
||||
<form action="{f:be.uri(route: 'web_info_translations', parameters: '{id: pageUid}')}" method="post" name="webinfoForm">
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="depth">
|
||||
<f:translate key="LLL:EXT:info/Resources/Private/Language/locallang_webinfo.xlf:moduleFunctions.depth" />
|
||||
</label>
|
||||
<f:render partial="DropdownMenu" arguments="{name: 'depth', id: 'depth', options: depthDropdownOptions, currentValue: depthDropdownCurrentValue}"/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="lang">
|
||||
<f:translate key="LLL:EXT:info/Resources/Private/Language/locallang_webinfo.xlf:moduleFunctions.lang" />
|
||||
</label>
|
||||
<f:render partial="DropdownMenu" arguments="{name: 'lang', id: 'lang', options: langDropdownOptions, currentValue: langDropdownCurrentValue}"/>
|
||||
</div>
|
||||
</div>
|
||||
<f:format.raw>{content}</f:format.raw>
|
||||
</form>
|
||||
</f:then>
|
||||
<f:else>
|
||||
<f:be.infobox
|
||||
message="{f:translate(key:'LLL:EXT:info/Resources/Private/Language/locallang_webinfo.xlf:info.noContent.message')}"
|
||||
state="{f:constant(name: 'TYPO3\CMS\Core\Type\ContextualFeedbackSeverity::NOTICE')}"
|
||||
/>
|
||||
</f:else>
|
||||
</f:if>
|
||||
</f:else>
|
||||
</f:if>
|
||||
</f:else>
|
||||
</f:if>
|
||||
|
||||
</f:section>
|
||||
|
||||
</html>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 245 B |
@@ -0,0 +1,13 @@
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
import r from"@typo3/core/event/regular-event.js";import e from"@typo3/backend/icons.js";class i{constructor(){this.registerEvents()}registerEvents(){new r("click",this.toggleNewButton).delegateTo(document,'input[type="checkbox"][data-lang]')}async toggleNewButton(){const t=document.querySelector(`.t3js-language-new[data-lang="${this.dataset.lang}"]`),a=t.querySelector(".t3js-icon"),n=document.querySelectorAll(`input[type="checkbox"][data-lang="${this.dataset.lang}"]:checked`),s=new URL(location.origin+t.dataset.editUrl);n.forEach(c=>{s.searchParams.set(`cmd[pages][${c.dataset.uid}][localize]`,this.dataset.lang)});const o=n.length===0;t.href=s.toString(),t.classList.toggle("disabled",o);const l=await e.getIcon(a.dataset.identifier,e.sizes.small,null,o?e.states.disabled:e.states.default);a.replaceWith(document.createRange().createContextualFragment(l))}}var d=new i;export{d as default};
|
||||
@@ -0,0 +1,59 @@
|
||||
{
|
||||
"name": "typo3/cms-info",
|
||||
"type": "typo3-cms-framework",
|
||||
"description": "TYPO3 CMS Info - TYPO3 backend module for displaying information, such as a pagetree overview and localization information.",
|
||||
"homepage": "https://typo3.community/",
|
||||
"funding": [
|
||||
{
|
||||
"type": "membership",
|
||||
"url": "https://typo3.org/membership"
|
||||
}
|
||||
],
|
||||
"license": [
|
||||
"GPL-2.0-or-later"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "TYPO3 Core Team",
|
||||
"email": "typo3cms@typo3.org",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://forge.typo3.org/issues/",
|
||||
"forum": "https://talk.typo3.org/",
|
||||
"source": "https://github.com/TYPO3/typo3/",
|
||||
"docs": "https://docs.typo3.org/",
|
||||
"rss": "https://news.typo3.com/rss/",
|
||||
"chat": "https://typo3.community/meet/slack/",
|
||||
"security": "https://typo3.org/security/"
|
||||
},
|
||||
"config": {
|
||||
"sort-packages": true
|
||||
},
|
||||
"require": {
|
||||
"typo3/cms-core": "15.0.*@dev"
|
||||
},
|
||||
"conflict": {
|
||||
"typo3/cms": "*"
|
||||
},
|
||||
"replace": {
|
||||
"typo3/cms-info-pagetsconfig": "self.version"
|
||||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "15.0.x-dev"
|
||||
},
|
||||
"typo3/cms": {
|
||||
"Package": {
|
||||
"partOfFactoryDefault": true
|
||||
},
|
||||
"extension-key": "info"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"TYPO3\\CMS\\Info\\": "Classes/"
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user