476 lines
19 KiB
PHP
476 lines
19 KiB
PHP
<?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\Backend\View\BackendLayout\Grid;
|
|
|
|
use Psr\EventDispatcher\EventDispatcherInterface;
|
|
use TYPO3\CMS\Backend\Domain\Repository\Localization\LocalizationRepository;
|
|
use TYPO3\CMS\Backend\Preview\StandardPreviewRendererResolver;
|
|
use TYPO3\CMS\Backend\Routing\UriBuilder;
|
|
use TYPO3\CMS\Backend\Utility\BackendUtility;
|
|
use TYPO3\CMS\Backend\View\BackendLayoutView;
|
|
use TYPO3\CMS\Backend\View\Event\AfterPageContentPreviewRenderedEvent;
|
|
use TYPO3\CMS\Backend\View\Event\PageContentPreviewRenderingEvent;
|
|
use TYPO3\CMS\Backend\View\PageLayoutContext;
|
|
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
|
|
use TYPO3\CMS\Core\Database\ReferenceIndex;
|
|
use TYPO3\CMS\Core\Domain\RecordInterface;
|
|
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\SchemaLabelResolver;
|
|
use TYPO3\CMS\Core\Schema\TcaSchema;
|
|
use TYPO3\CMS\Core\Schema\TcaSchemaFactory;
|
|
use TYPO3\CMS\Core\Site\Entity\SiteLanguage;
|
|
use TYPO3\CMS\Core\Type\Bitmask\Permission;
|
|
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
|
|
|
/**
|
|
* Grid Column Item
|
|
*
|
|
* Model/proxy around a single record which appears in a grid column
|
|
* in the page layout. Returns titles, urls etc. and performs basic
|
|
* assertions on the contained content element record such as
|
|
* is-versioned, is-editable, is-delible and so on.
|
|
*
|
|
* Accessed from Fluid templates.
|
|
*
|
|
* @internal
|
|
*/
|
|
class GridColumnItem
|
|
{
|
|
/**
|
|
* @var GridColumnItem[]
|
|
*/
|
|
protected array $translations = [];
|
|
protected TcaSchema $schema;
|
|
protected BackendLayoutView $backendLayoutView;
|
|
protected readonly IconFactory $iconFactory;
|
|
|
|
public function __construct(
|
|
protected readonly PageLayoutContext $context,
|
|
protected readonly GridColumn $column,
|
|
protected RecordInterface $record,
|
|
protected readonly string $table = 'tt_content'
|
|
) {
|
|
$this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
|
|
$this->schema = GeneralUtility::makeInstance(TcaSchemaFactory::class)->get($this->table);
|
|
$this->backendLayoutView = GeneralUtility::makeInstance(BackendLayoutView::class);
|
|
}
|
|
|
|
public function getContext(): PageLayoutContext
|
|
{
|
|
return $this->context;
|
|
}
|
|
|
|
public function isVersioned(): bool
|
|
{
|
|
return $this->record->getComputedProperties()->getVersionedUid() > 0 || (int)($this->getRow()['t3ver_state'] ?? 0) !== 0;
|
|
}
|
|
|
|
public function getPreview(): string
|
|
{
|
|
$eventDispatcher = GeneralUtility::makeInstance(EventDispatcherInterface::class);
|
|
$previewRenderer = GeneralUtility::makeInstance(StandardPreviewRendererResolver::class)->resolveRendererFor($this->record);
|
|
$previewHeader = $previewRenderer->renderPageModulePreviewHeader($this);
|
|
|
|
// Dispatch event to allow listeners adding an alternative content type
|
|
// specific preview or to manipulate the content elements' record data.
|
|
$event = $eventDispatcher->dispatch(
|
|
new PageContentPreviewRenderingEvent($this->table, $this->getRecordType(), $this->record, $this->context)
|
|
);
|
|
|
|
// Update the modified record data
|
|
$this->record = $event->getRecord();
|
|
|
|
// Get specific preview from listeners. In case non was added,
|
|
// fall back to the standard preview rendering workflow.
|
|
$previewContent = $event->getPreviewContent();
|
|
if ($previewContent === null) {
|
|
$previewContent = $previewRenderer->renderPageModulePreviewContent($this);
|
|
}
|
|
|
|
if (!$this->backendLayoutView->isCTypeAllowedInColPosByPage(
|
|
$this->getRecordType(),
|
|
$this->getColumn()->getColumnNumber() ?? 0,
|
|
$this->getRecord()->getPid()
|
|
)) {
|
|
return '<span class="badge badge-warning">' . sprintf($this->getLanguageService()->sL('core.core:labels.typeNotAllowedInColumn'), $this->getContentTypeLabel()) . '</span>';
|
|
}
|
|
|
|
$previewContent = $previewRenderer->wrapPageModulePreview($previewHeader, $previewContent, $this);
|
|
$event = $eventDispatcher->dispatch(
|
|
new AfterPageContentPreviewRenderedEvent($this->table, $this->getRecordType(), $this->record, $this->context, $previewContent)
|
|
);
|
|
return $event->getPreviewContent();
|
|
}
|
|
|
|
public function getWrapperClassName(): string
|
|
{
|
|
$wrapperClassNames = [];
|
|
if ($this->isDisabled()) {
|
|
$wrapperClassNames[] = 't3-page-ce-hidden t3js-hidden-record';
|
|
}
|
|
if ($this->isInconsistentLanguage()
|
|
|| !$this->backendLayoutView->isCTypeAllowedInColPosByPage(
|
|
$this->getRecordType(),
|
|
$this->getColumn()->getColumnNumber() ?? 0,
|
|
$this->getRecord()->getPid()
|
|
)
|
|
) {
|
|
$wrapperClassNames[] = 't3-page-ce-warning';
|
|
}
|
|
|
|
return implode(' ', $wrapperClassNames);
|
|
}
|
|
|
|
public function isDelible(): bool
|
|
{
|
|
$backendUser = $this->getBackendUser();
|
|
if (!$backendUser->doesUserHaveAccess($this->context->getPageRecord(), Permission::CONTENT_EDIT)) {
|
|
return false;
|
|
}
|
|
return !($backendUser->getTSConfig()['options.']['disableDelete.'][$this->table] ?? $backendUser->getTSConfig()['options.']['disableDelete'] ?? false);
|
|
}
|
|
|
|
public function getDeleteUrl(): string
|
|
{
|
|
return (string)GeneralUtility::makeInstance(UriBuilder::class)->buildUriFromRoute(
|
|
'tce_db',
|
|
[
|
|
'cmd' => [
|
|
$this->table => [
|
|
$this->record->getUid() => [
|
|
'delete' => 1,
|
|
],
|
|
],
|
|
],
|
|
'redirect' => $this->context->getReturnUrl(),
|
|
]
|
|
);
|
|
}
|
|
|
|
public function getDeleteMessage(): string
|
|
{
|
|
$recordInfo = BackendUtility::cropToTitleLength(BackendUtility::getRecordTitle($this->table, $this->getRow()));
|
|
if ($this->getBackendUser()->shallDisplayDebugInformation()) {
|
|
$recordInfo .= ' [' . $this->table . ':' . $this->record->getUid() . ']';
|
|
}
|
|
|
|
$refCountMsg = BackendUtility::referenceCount(
|
|
$this->table,
|
|
$this->record->getUid(),
|
|
LF . $this->getLanguageService()->sL('core.core:labels.referencesToRecord'),
|
|
(string)$this->getReferenceCount($this->record->getUid())
|
|
);
|
|
$translationCount = count(GeneralUtility::makeInstance(LocalizationRepository::class)->getRecordTranslations($this->table, $this->record->getUid()));
|
|
if ($translationCount > 0) {
|
|
$refCountMsg .= LF . sprintf(
|
|
$this->getLanguageService()->sL('core.core:labels.translationsOfRecord'),
|
|
$translationCount
|
|
);
|
|
}
|
|
|
|
return sprintf($this->getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang_layout.xlf:deleteWarning'), trim($recordInfo)) . $refCountMsg;
|
|
}
|
|
|
|
public function getFooterInfo(): string
|
|
{
|
|
$previewRenderer = GeneralUtility::makeInstance(StandardPreviewRendererResolver::class)->resolveRendererFor($this->record);
|
|
return $previewRenderer->renderPageModulePreviewFooter($this);
|
|
}
|
|
|
|
public function getContentTypeLabel(): string
|
|
{
|
|
if (($recordType = $this->getRecordType()) === '') {
|
|
return '';
|
|
}
|
|
|
|
$contentTypeLabels = $this->context->getContentTypeLabels();
|
|
$contentTypeLabel = $contentTypeLabels[$recordType] ?? '';
|
|
if ($contentTypeLabel === '') {
|
|
$contentTypeLabel = $this->getLabelFromItemListMerged();
|
|
$contentTypeLabel = $this->getLanguageService()->sL($contentTypeLabel);
|
|
}
|
|
return $contentTypeLabel;
|
|
}
|
|
|
|
public function getIcons(): string
|
|
{
|
|
$row = $this->record->getRawRecord()?->toArray() ?? [];
|
|
$icons = [];
|
|
|
|
$icon = $this->iconFactory
|
|
->getIconForRecord($this->table, $row, IconSize::SMALL)
|
|
->setTitle(BackendUtility::getRecordIconAltText($row, $this->table, false))
|
|
->render();
|
|
if ($this->getBackendUser()->checkRecordEditAccess($this->table, $this->getRow())->isAllowed) {
|
|
$icon = BackendUtility::wrapClickMenuOnIcon($icon, $this->table, $this->record->getUid());
|
|
}
|
|
$icons[] = $icon;
|
|
|
|
if ($lockInfo = BackendUtility::isRecordLocked($this->table, $this->record->getUid())) {
|
|
$icons[] = '<a href="#" title="' . htmlspecialchars($lockInfo['msg']) . '">'
|
|
. $this->iconFactory->getIcon('status-user-backend', IconSize::SMALL, 'overlay-edit')->render() . '</a>';
|
|
}
|
|
return implode(' ', $icons);
|
|
}
|
|
|
|
public function getSiteLanguage(): SiteLanguage
|
|
{
|
|
return $this->context->getSiteLanguage((int)($this->getRow()[$this->schema->getCapability(TcaSchemaCapability::Language)->getLanguageField()->getName()] ?? 0));
|
|
}
|
|
|
|
public function getRecord(): RecordInterface
|
|
{
|
|
return $this->record;
|
|
}
|
|
|
|
public function getRow(): array
|
|
{
|
|
return $this->record->getRawRecord()?->toArray(true) ?? [];
|
|
}
|
|
|
|
public function setRecord(RecordInterface $record): void
|
|
{
|
|
$this->record = $record;
|
|
}
|
|
|
|
public function getColumn(): GridColumn
|
|
{
|
|
return $this->column;
|
|
}
|
|
|
|
public function getTranslations(): array
|
|
{
|
|
return $this->translations;
|
|
}
|
|
|
|
public function addTranslation(int $languageId, GridColumnItem $translation): GridColumnItem
|
|
{
|
|
$this->translations[$languageId] = $translation;
|
|
return $this;
|
|
}
|
|
|
|
public function isDisabled(): bool
|
|
{
|
|
$row = $this->getRow();
|
|
return (
|
|
$this->schema->hasCapability(TcaSchemaCapability::RestrictionDisabledField)
|
|
&& ($row[(string)$this->schema->getCapability(TcaSchemaCapability::RestrictionDisabledField)] ?? false)
|
|
)
|
|
|| (
|
|
$this->schema->hasCapability(TcaSchemaCapability::RestrictionStartTime)
|
|
&& ($row[(string)$this->schema->getCapability(TcaSchemaCapability::RestrictionStartTime)] ?? 0) > $GLOBALS['EXEC_TIME']
|
|
)
|
|
|| (
|
|
$this->schema->hasCapability(TcaSchemaCapability::RestrictionEndTime)
|
|
&& (($endTime = ($row[(string)$this->schema->getCapability(TcaSchemaCapability::RestrictionEndTime)] ?? 0)) !== 0 && $endTime < $GLOBALS['EXEC_TIME'])
|
|
);
|
|
}
|
|
|
|
public function isEditable(): bool
|
|
{
|
|
$backendUser = $this->getBackendUser();
|
|
if ($backendUser->isAdmin()) {
|
|
return true;
|
|
}
|
|
$pageRecord = $this->context->getPageRecord();
|
|
return $backendUser->doesUserHaveAccess($pageRecord, Permission::CONTENT_EDIT)
|
|
&& $backendUser->checkRecordEditAccess($this->table, $this->record)->isAllowed
|
|
&& (
|
|
!($pagesSchema = GeneralUtility::makeInstance(TcaSchemaFactory::class)->get('pages'))->hasCapability(TcaSchemaCapability::EditLock)
|
|
|| !($pageRecord[$pagesSchema->getCapability(TcaSchemaCapability::EditLock)->getFieldName()] ?? false)
|
|
);
|
|
}
|
|
|
|
public function isDragAndDropAllowed(): bool
|
|
{
|
|
$pageRecord = $this->context->getPageRecord();
|
|
$typeColumn = $this->getTypeColumn();
|
|
return (int)($this->getRow()[$this->schema->getCapability(TcaSchemaCapability::Language)->getTranslationOriginPointerField()->getName()] ?? 0) === 0
|
|
&& (
|
|
$this->getBackendUser()->isAdmin()
|
|
|| (
|
|
(
|
|
!($this->getRow()[$this->schema->getCapability(TcaSchemaCapability::EditLock)->getFieldName()] ?? false)
|
|
&& (
|
|
!($pagesSchema = GeneralUtility::makeInstance(TcaSchemaFactory::class)->get('pages'))->hasCapability(TcaSchemaCapability::EditLock)
|
|
|| !($pageRecord[$pagesSchema->getCapability(TcaSchemaCapability::EditLock)->getFieldName()] ?? false)
|
|
)
|
|
)
|
|
&& $this->getBackendUser()->doesUserHaveAccess($pageRecord, Permission::CONTENT_EDIT)
|
|
&& $this->getBackendUser()->checkAuthMode($this->table, $typeColumn, $this->getRecordType())
|
|
)
|
|
)
|
|
;
|
|
}
|
|
|
|
public function isInconsistentLanguage(): bool
|
|
{
|
|
$allowInconsistentLanguageHandling = $this->context->getDrawingConfiguration()->getAllowInconsistentLanguageHandling();
|
|
return !$allowInconsistentLanguageHandling
|
|
&& $this->getSiteLanguage()->getLanguageId() !== 0
|
|
&& $this->context->getLanguageModeIdentifier() === 'mixed'
|
|
&& (int)($this->getRow()[$this->schema->getCapability(TcaSchemaCapability::Language)->getTranslationOriginPointerField()->getName()] ?? 0) === 0;
|
|
}
|
|
|
|
public function getNewContentAfterUrl(): string
|
|
{
|
|
$uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
|
|
return (string)$uriBuilder->buildUriFromRoute('new_content_element_wizard', [
|
|
'id' => $this->context->getPageId(),
|
|
'sys_language_uid' => $this->context->getSiteLanguage()->getLanguageId(),
|
|
'colPos' => $this->column->getColumnNumber(),
|
|
'uid_pid' => -$this->record->getUid(),
|
|
'returnUrl' => $this->context->getReturnUrl(),
|
|
]);
|
|
}
|
|
|
|
public function getVisibilityToggleUrl(): string
|
|
{
|
|
$disabledFieldName = $this->getDisabledFieldName();
|
|
if ($this->getRow()[$disabledFieldName] ?? false) {
|
|
$value = 0;
|
|
} else {
|
|
$value = 1;
|
|
}
|
|
return GeneralUtility::makeInstance(UriBuilder::class)->buildUriFromRoute(
|
|
'tce_db',
|
|
[
|
|
'data' => [
|
|
$this->table => [
|
|
$this->record->getComputedProperties()->getVersionedUid() ?: $this->record->getUid() => [
|
|
$disabledFieldName => $value,
|
|
],
|
|
],
|
|
],
|
|
'redirect' => $this->context->getReturnUrl(),
|
|
]
|
|
) . '#element-' . $this->table . '-' . $this->record->getUid();
|
|
}
|
|
|
|
public function getVisibilityToggleTitle(): string
|
|
{
|
|
if ($this->getRow()[$this->getDisabledFieldName()] ?? false) {
|
|
return $this->getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang_layout.xlf:unHide');
|
|
}
|
|
return $this->getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang_layout.xlf:hide');
|
|
}
|
|
|
|
public function getVisibilityToggleIconName(): string
|
|
{
|
|
return ($this->getRow()[$this->getDisabledFieldName()] ?? false) ? 'unhide' : 'hide';
|
|
}
|
|
|
|
public function isVisibilityToggling(): bool
|
|
{
|
|
$disabledFieldName = $this->getDisabledFieldName();
|
|
return $disabledFieldName
|
|
&& $this->schema->hasField($disabledFieldName)
|
|
&& (
|
|
!$this->schema->getField($disabledFieldName)->supportsAccessControl()
|
|
|| $this->getBackendUser()->check('non_exclude_fields', $this->table . ':' . $disabledFieldName)
|
|
)
|
|
;
|
|
}
|
|
|
|
public function getEditUrl(): string
|
|
{
|
|
$urlParameters = [
|
|
'edit' => [
|
|
$this->table => [
|
|
$this->record->getUid() => 'edit',
|
|
],
|
|
],
|
|
'module' => 'web_layout',
|
|
'returnUrl' => $this->context->getReturnUrl() . '#element-' . $this->table . '-' . $this->record->getUid(),
|
|
];
|
|
$uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
|
|
return $uriBuilder->buildUriFromRoute('record_edit', $urlParameters) . '#element-' . $this->table . '-' . $this->record->getUid();
|
|
}
|
|
|
|
public function getContextualEditUrl(): string
|
|
{
|
|
$urlParameters = [
|
|
'edit' => [
|
|
$this->table => [
|
|
$this->record->getUid() => 'edit',
|
|
],
|
|
],
|
|
'module' => 'web_layout',
|
|
'returnUrl' => $this->context->getReturnUrl() . '#element-' . $this->table . '-' . $this->record->getUid(),
|
|
];
|
|
return (string)GeneralUtility::makeInstance(UriBuilder::class)->buildUriFromRoute('record_edit_contextual', $urlParameters);
|
|
}
|
|
|
|
public function getTypeColumn(): string
|
|
{
|
|
// @todo This only supports local record types due to usages in this class
|
|
return $this->schema->supportsSubSchema() && !$this->schema->getSubSchemaTypeInformation()->isPointerToForeignFieldInForeignSchema()
|
|
? $this->schema->getSubSchemaTypeInformation()->getFieldName()
|
|
: '';
|
|
}
|
|
|
|
public function getRecordType(): string
|
|
{
|
|
return $this->record->getRecordType() ?? '';
|
|
}
|
|
|
|
public function getTable(): string
|
|
{
|
|
return $this->table;
|
|
}
|
|
|
|
/**
|
|
* Gets the number of records referencing the record with the UID $uid in
|
|
* the current table.
|
|
*
|
|
* @return int The number of references to record $uid in table
|
|
*/
|
|
protected function getReferenceCount(int $uid): int
|
|
{
|
|
return GeneralUtility::makeInstance(ReferenceIndex::class)->getNumberOfReferencedRecords($this->table, $uid);
|
|
}
|
|
|
|
protected function getLabelFromItemListMerged(): string
|
|
{
|
|
$table = $this->table;
|
|
$typeColumn = $this->getTypeColumn();
|
|
$recordType = $this->getRecordType();
|
|
$columnTsConfig = BackendUtility::getPagesTSconfig($this->record->getPid())['TCEFORM.'][$table . '.'][$typeColumn . '.'] ?? [];
|
|
return GeneralUtility::makeInstance(SchemaLabelResolver::class)
|
|
->getLabelForFieldValue($table, $typeColumn, $recordType, $this->getRow(), is_array($columnTsConfig) ? $columnTsConfig : []);
|
|
}
|
|
|
|
protected function getDisabledFieldName(): ?string
|
|
{
|
|
return $this->schema->hasCapability(TcaSchemaCapability::RestrictionDisabledField) ? (string)$this->schema->getCapability(TcaSchemaCapability::RestrictionDisabledField) : null;
|
|
}
|
|
|
|
protected function getLanguageService(): LanguageService
|
|
{
|
|
return $GLOBALS['LANG'];
|
|
}
|
|
|
|
protected function getBackendUser(): BackendUserAuthentication
|
|
{
|
|
return $GLOBALS['BE_USER'];
|
|
}
|
|
}
|