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,86 @@
<?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\TypoScript\AST\Node;
/**
* Generic child node. Implements common methods of NodeInterface used
* in all Node classes.
*
* @internal: Internal AST structure.
*/
abstract class AbstractChildNode extends AbstractNode implements ChildNodeInterface
{
public function __construct(protected string $name) {}
/**
* Dereference children on clone().
* Used with '<' operator to create a deep-copy of the tree to copy.
*/
public function __clone(): void
{
foreach ($this->children as $childName => $child) {
$this->children[$childName] = clone $child;
}
}
public function getName(): string
{
return $this->name;
}
public function toArray(): ?array
{
if (!$this->hasChildren()) {
return null;
}
$result = [];
foreach ($this->getNextChild() as $child) {
$childName = $child->getName();
$childValue = $child->getValue();
if ($child instanceof ReferenceChildNode) {
// Hack for b/w compat parsing of `=<` operator. See ContentObjectRenderer cObjGetSingle() and mergeTSRef()
// @todo: adding the whitespace after '<' is another bit of a hack here ... maybe solve in tokenizer?
// compare this for what happens when doing 'foo = bar' in old parser: Is the whitespace kept for
// value to not trigger the ref lookup to often if doing 'foo = <div...' ?
// @todo: same situation in RootNode!
$childValue = '< ' . $child->getReferenceSourceStream();
}
if ($childValue !== null) {
$result[$child->getName()] = $childValue;
}
$grandChildren = $child->toArray();
if ($grandChildren !== null) {
$result[$childName . '.'] = $grandChildren;
}
}
return $result;
}
public function flatten(string $prefix = ''): array
{
$flatArray = [];
$prefixedQuotedNodeName = $prefix . addcslashes($this->getName(), '.');
if (!$this->isValueNull()) {
$flatArray[$prefixedQuotedNodeName] = $this->getValue();
}
foreach ($this->getNextChild() as $child) {
$flatArray = array_merge($flatArray, $child->flatten($prefixedQuotedNodeName . '.'));
}
return $flatArray;
}
}
@@ -0,0 +1,185 @@
<?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\TypoScript\AST\Node;
use TYPO3\CMS\Core\TypoScript\Tokenizer\Token\TokenStreamInterface;
/**
* Generic node. Implements common methods of NodeInterface used
* in all Node classes.
*
* @internal: Internal AST structure.
*/
abstract class AbstractNode implements NodeInterface
{
private ?string $identifier = null;
protected string $name;
private ?string $value = null;
private ?string $previousValue = null;
/**
* @var array<string, ChildNodeInterface>
*/
protected array $children = [];
private ?TokenStreamInterface $originalValueTokenStream = null;
private array $comments = [];
/**
* When storing to cache, we only store FE relevant properties and skip
* various BE related properties which then default to class defaults when
* unserialized. This is done to create smaller php cache files.
*/
final public function __serialize(): array
{
return $this->serialize();
}
protected function serialize(): array
{
$result = [
'name' => $this->name,
'children' => $this->children,
];
if ($this->value !== null) {
$result['value'] = $this->value;
}
return $result;
}
public function setIdentifier(string $identifier): void
{
$this->identifier = hash('xxh3', $identifier);
$childCounter = 0;
foreach ($this->getNextChild() as $child) {
$child->setIdentifier($this->identifier . $childCounter);
$childCounter++;
}
}
/**
* This forces $this->name NOT to be readonly.
* Used with '<' operator on tree root to copy:
* foo = value
* bar < foo
* The 'foo' object node is copied, but added to AST as name 'bar'
*/
public function updateName(string $name): void
{
$this->name = $name;
}
public function getIdentifier(): string
{
if ($this->identifier === null) {
throw new \RuntimeException(
'Identifier has not been initialized. This happens when getIdentifier() is called on'
. ' trees retrieved from cache. The identifier is not supposed to be used in this context.',
1674620169
);
}
return $this->identifier;
}
public function addChild(ChildNodeInterface $node): void
{
$this->children[$node->getName()] = $node;
}
public function getChildByName(string $name): ?ChildNodeInterface
{
return $this->children[$name] ?? null;
}
/**
* Note this does *not* choke if that child does not exist, so we can "blindly" remove without error.
*/
public function removeChildByName(string $name): void
{
unset($this->children[$name]);
}
public function hasChildren(): bool
{
return !empty($this->children);
}
public function getNextChild(): iterable
{
foreach ($this->children as $child) {
yield $child;
}
}
public function sortChildren(): void
{
ksort($this->children, SORT_FLAG_CASE | SORT_STRING);
}
public function setValue(?string $value): void
{
$this->value = $value;
}
public function appendValue(string $value): void
{
$this->value .= $value;
}
public function getValue(): ?string
{
return $this->value;
}
public function isValueNull(): bool
{
return $this->value === null;
}
public function setPreviousValue(?string $value): void
{
$this->previousValue = $value;
}
public function getPreviousValue(): ?string
{
return $this->previousValue;
}
public function setOriginalValueTokenStream(?TokenStreamInterface $tokenStream): void
{
$this->originalValueTokenStream = $tokenStream;
}
public function getOriginalValueTokenStream(): ?TokenStreamInterface
{
return $this->originalValueTokenStream;
}
public function addComment(TokenStreamInterface $tokenStream): void
{
$this->comments[] = $tokenStream;
}
/**
* @return TokenStreamInterface[]
*/
public function getComments(): array
{
return $this->comments;
}
}
+23
View File
@@ -0,0 +1,23 @@
<?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\TypoScript\AST\Node;
/**
* A generic child node that is not the node root.
*/
final class ChildNode extends AbstractChildNode {}
@@ -0,0 +1,26 @@
<?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\TypoScript\AST\Node;
/**
* The created AST consists of a NodeRoot object with nested NodeObject children.
* This is the interface implemented by all children nodes (not RootNode).
*
* @internal: Internal AST structure.
*/
interface ChildNodeInterface extends NodeInterface {}
@@ -0,0 +1,125 @@
<?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\TypoScript\AST\Node;
use TYPO3\CMS\Core\TypoScript\Tokenizer\Token\TokenStreamInterface;
/**
* The created AST consists of a NodeRoot object with nested NodeObject children.
* This is the main interface to any node type.
*
* Example TypoScript:
* "foo = fooValue"
* "foo.bar = barValue"
* This creates a RootNode with one ChildNode name "foo" and value "fooValue",
* that has a child ChildNode name "bar" and value "barValue".
*
* @internal: Internal AST structure.
*/
interface NodeInterface
{
/**
* An identifier for this node. Typically, a hash of some kind. This identifier
* is unique within the tree, by being created from the parent identifier plus
* the name. This identifier is used in the backend, when referencing single nodes.
* Calculating identifiers is initiated by calling setIdentifier() on RootNode, which
* will recurse the tree. Call this on the final tree, after AST calculation finished,
* so AST building itself does not need to fiddle with identifier updates when for
* instance tree parts are cloned using '<' operator.
* Note this value is skipped when persisting to caches since it's a Backend related
* thing that does not use cached context: When retrieving nodes from cache (e.g. in Frontend),
* the identifier is null and calling the getter will throw an exception.
*/
public function setIdentifier(string $identifier): void;
public function getIdentifier(): string;
/**
* Helper methods for node name.
*/
public function getName(): ?string;
public function updateName(string $name): void;
/**
* Helper methods to operate on children.
*/
public function addChild(ChildNodeInterface $node): void;
public function getChildByName(string $name): ?ChildNodeInterface;
public function removeChildByName(string $name): void;
public function hasChildren(): bool;
/**
* @return iterable<ChildNodeInterface>
*/
public function getNextChild(): iterable;
public function sortChildren(): void;
/**
* Helper methods for value.
*/
public function setValue(?string $value): void;
public function appendValue(string $value): void;
public function getValue(): ?string;
public function isValueNull(): bool;
/**
* Previous value is only set by comment aware ast builder. It is used in
* constant editor to see if a value has been changed.
*/
public function setPreviousValue(?string $value): void;
public function getPreviousValue(): ?string;
/**
* Helper method for backend object browser to retrieve the original
* stream when a constant substitution happened, only set by CommentAwareAstBuilder.
*/
public function setOriginalValueTokenStream(?TokenStreamInterface $tokenStream): void;
public function getOriginalValueTokenStream(): ?TokenStreamInterface;
/**
* Helper methods to attach TypoScript tokens to a node.
* This is used in ext:tstemplate "Constant Editor" and "Object Browser" and handled
* by CommentAwareAstBuilder.
*/
public function addComment(TokenStreamInterface $tokenStream): void;
/**
* @return TokenStreamInterface[]
*/
public function getComments(): array;
/**
* b/w compat method to turn AST into an array.
* Note we're NOT using magic __toArray() here to avoid calling array-cast of AST by
* accident: toArray() should be called explicitly if needed, which makes it much easier
* to drop this b/w compat method when we later want to drop that layer.
*
* Note RootNode *always* returns an array, while ObjectNode's may return null.
*/
public function toArray(): ?array;
/**
* Flatten the tree. A RootNode with a ChildNode "foo" and value "fooValue", with this
* ChildNode again having a ChildNode "bar" and value "barValue" becomes:
* [
* 'foo' => 'fooValue',
* 'foo.bar' => 'barValue',
* ]
*
* Flattening a TypoScript tree is especially used for constants to quickly look
* up constants when parsing setup node value streams that use T_CONSTANT tokens.
*/
public function flatten(string $prefix = ''): array;
}
@@ -0,0 +1,60 @@
<?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\TypoScript\AST\Node;
use TYPO3\CMS\Core\TypoScript\Tokenizer\Token\IdentifierTokenStream;
/**
* A node object created for LineIdentifierReference lines which use the T_OPERATOR_REFERENCE
* operator and have a TokenStreamIdentifier stream for "the right side" of the expression.
*
* The reference operator is nasty, since it's no "true" reference / pointer:
* foo.bar = barValue1
* baz =< foo
* baz.bar = barValue2
* This ends up with "barValue1" for "foo.bar", and "barValue2" for "baz.bar". "barValue1"
* for "foo.bar" is kept!
*
* Note the reference operator *only* works for TS "setup" code, not for "constants", and it
* is only resolved in these cases. See ContentObjectRenderer->cObjGetSingle() for details.
*
* @internal: Internal AST structure.
*/
final class ReferenceChildNode extends AbstractChildNode
{
private ?IdentifierTokenStream $referenceSourceStream;
protected function serialize(): array
{
$result = parent::serialize();
if ($this->referenceSourceStream !== null) {
$result['referenceSourceStream'] = $this->referenceSourceStream;
}
return $result;
}
public function setReferenceSourceStream(?IdentifierTokenStream $referenceSourceStream): void
{
$this->referenceSourceStream = $referenceSourceStream;
}
public function getReferenceSourceStream(): IdentifierTokenStream
{
return $this->referenceSourceStream;
}
}
+109
View File
@@ -0,0 +1,109 @@
<?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\TypoScript\AST\Node;
/**
* AST entry node.
*
* @internal: Internal AST structure.
*/
final class RootNode extends AbstractNode
{
/**
* Attempting to clone the RootNode indicates a bug in AstBuilder.
* It should never happen.
*/
public function __clone(): void
{
throw new \LogicException('Can not clone RootNode', 1655988945);
}
/**
* RootNode has no properties to cache, just children.
*/
protected function serialize(): array
{
return [
'children' => $this->children,
];
}
public function getName(): ?string
{
return null;
}
public function updateName(string $name): void
{
throw new \RuntimeException('RootNode has no name. Don\'t call updateName().', 1653743453);
}
public function setValue(?string $value): void
{
throw new \RuntimeException('RootNode has no value. Don\'t call setValue().', 1653743454);
}
public function appendValue(string $value): void
{
throw new \RuntimeException('RootNode has no value. Don\'t call appendValue().', 1653743455);
}
public function getValue(): ?string
{
return null;
}
public function isValueNull(): bool
{
return true;
}
/**
* @return array<string, mixed>
*/
public function toArray(): array
{
$result = [];
foreach ($this->getNextChild() as $child) {
$childName = $child->getName();
if ($child instanceof ReferenceChildNode) {
// Hack for b/w compat parsing of `=<` operator. See ContentObjectRenderer cObjGetSingle() and mergeTSRef()
$childValue = '< ' . $child->getReferenceSourceStream();
} else {
$childValue = $child->getValue();
}
if ($childValue !== null) {
$result[$childName] = $childValue;
}
$grandChildren = $child->toArray();
if ($grandChildren !== null) {
$result[$childName . '.'] = $grandChildren;
}
}
return $result;
}
public function flatten(string $prefix = ''): array
{
$flatArray = [];
foreach ($this->getNextChild() as $child) {
$flatArray = array_merge($flatArray, $child->flatten(''));
}
return $flatArray;
}
}