TYPO3 v15 dev-main snapshot ()

This commit is contained in:
2026-08-10 22:31:09 +02:00
commit af8cc155b5
6818 changed files with 642608 additions and 0 deletions
@@ -0,0 +1,79 @@
<?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\Core\Domain\Access;
use Psr\EventDispatcher\StoppableEventInterface;
use TYPO3\CMS\Core\Context\Context;
/**
* Event to modify records to be checked against "enableFields".
* Listeners are able to grant access or to modify the record itself to
* continue to use the native access check functionality with a modified dataset.
*/
final class RecordAccessGrantedEvent implements StoppableEventInterface
{
private ?bool $accessGranted = null;
public function __construct(
private readonly string $tableName,
private array $record,
private readonly Context $context
) {}
public function isPropagationStopped(): bool
{
return $this->accessGranted !== null;
}
/**
* @internal
*/
public function accessGranted(): bool
{
if ($this->accessGranted === null) {
throw new \RuntimeException('Access was not yet defined.', 1645506529);
}
return $this->accessGranted;
}
public function setAccessGranted(bool $accessGranted): void
{
$this->accessGranted = $accessGranted;
}
public function getTable(): string
{
return $this->tableName;
}
public function getRecord(): array
{
return $this->record;
}
public function updateRecord(array $record): void
{
$this->record = $record;
}
public function getContext(): Context
{
return $this->context;
}
}
+143
View File
@@ -0,0 +1,143 @@
<?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\Core\Domain\Access;
use Psr\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\DependencyInjection\Attribute\Autoconfigure;
use TYPO3\CMS\Core\Context\Context;
use TYPO3\CMS\Core\Schema\Capability\TcaSchemaCapability;
use TYPO3\CMS\Core\Schema\TcaSchemaFactory;
/**
* Checks if a record can be accessed (usually in TYPO3 Frontend) due to various "enableFields" or group access checks.
*
* Not related to "write permissions" etc.
*/
#[Autoconfigure(public: true)]
readonly class RecordAccessVoter
{
public function __construct(
protected EventDispatcherInterface $eventDispatcher,
protected TcaSchemaFactory $tcaSchemaFactory,
) {}
/**
* Checks page record for enableFields
* Returns TRUE if enableFields does not disable the page record.
* Takes notice of the includeHiddenPages visibility aspect flag and uses SIM_ACCESS_TIME for start/endtime evaluation
*
* @param string $table the TCA table to check for
* @param array $record The record to evaluate (needs fields: hidden, starttime, endtime, fe_group)
* @param Context $context Context API to check against
* @return bool TRUE, if record is viewable.
*/
public function accessGranted(string $table, array $record, Context $context): bool
{
$event = new RecordAccessGrantedEvent($table, $record, $context);
$this->eventDispatcher->dispatch($event);
if ($event->isPropagationStopped()) {
return $event->accessGranted();
}
$record = $event->getRecord();
$schema = $this->tcaSchemaFactory->get($table);
$visibilityAspect = $context->getAspect('visibility');
$includeHidden = $table === 'pages'
? $visibilityAspect->includeHiddenPages()
: $visibilityAspect->includeHiddenContent();
// Hidden field is active and hidden records should not be included
if ($schema->hasCapability(TcaSchemaCapability::RestrictionDisabledField)) {
$fieldName = $schema->getCapability(TcaSchemaCapability::RestrictionDisabledField)->getFieldName();
if (($record[$fieldName] ?? false) && !$includeHidden) {
return false;
}
}
// Records' starttime set AND is HIGHER than the current access time
if ($schema->hasCapability(TcaSchemaCapability::RestrictionStartTime)) {
$fieldName = $schema->getCapability(TcaSchemaCapability::RestrictionStartTime)->getFieldName();
if (isset($record[$fieldName])
&& (int)$record[$fieldName] > $GLOBALS['SIM_ACCESS_TIME']
&& !$visibilityAspect->includeScheduledRecords()
) {
return false;
}
}
// Records' endtime is set AND NOT "0" AND LOWER than the current access time
if ($schema->hasCapability(TcaSchemaCapability::RestrictionEndTime)) {
$fieldName = $schema->getCapability(TcaSchemaCapability::RestrictionEndTime)->getFieldName();
if (isset($record[$fieldName])
&& ((int)$record[$fieldName] !== 0)
&& ((int)$record[$fieldName] < $GLOBALS['SIM_ACCESS_TIME'])
&& !$visibilityAspect->includeScheduledRecords()
) {
return false;
}
}
// Insufficient group access
if ($this->groupAccessGranted($table, $record, $context) === false) {
return false;
}
// Record is available
return true;
}
/**
* Check group access against a record, if the current users' groups match the fe_group values of the record.
*
* @param string $table the TCA table to check for
* @param array $record The record to evaluate (needs enableField: fe_group)
* @param Context $context Context API to check against
* @return bool TRUE, if group access is granted.
*/
public function groupAccessGranted(string $table, array $record, Context $context): bool
{
if (!$this->tcaSchemaFactory->has($table)) {
return true;
}
$schema = $this->tcaSchemaFactory->get($table);
// No fe_group field in TCA, so no group access check
if (!$schema->hasCapability(TcaSchemaCapability::RestrictionUserGroup)) {
return true;
}
$fieldName = $schema->getCapability(TcaSchemaCapability::RestrictionUserGroup)->getFieldName();
// Field not given, so no group access check
if (!($record[$fieldName] ?? false)) {
return true;
}
// No frontend user, but 'fe_group' is not empty, so shut this down.
if (!$context->hasAspect('frontend.user')) {
return false;
}
$pageGroupList = explode(',', (string)$record[$fieldName]);
return count(array_intersect($context->getAspect('frontend.user')->getGroupIds(), $pageGroupList)) > 0;
}
/**
* Checks if the current page of the root line is visible.
*
* If the field extendToSubpages is 0, access is granted,
* else the fields hidden, starttime, endtime, fe_group are evaluated.
*
* @internal this is a special use case and should only be used with care, not part of TYPO3's Public API.
*/
public function accessGrantedForPageInRootLine(array $pageRecord, Context $context): bool
{
return !($pageRecord['extendToSubpages'] ?? false) || $this->accessGranted('pages', $pageRecord, $context);
}
}