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
+53
View File
@@ -0,0 +1,53 @@
<?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\Utility\String;
/**
* @internal
*/
class StringFragment implements \Stringable
{
public readonly int $length;
public readonly string $ident;
public static function raw(string $value): self
{
return new self($value, StringFragmentSplitter::TYPE_RAW);
}
public static function expression(string $value): self
{
return new self($value, StringFragmentSplitter::TYPE_EXPRESSION);
}
public function __construct(
public readonly string $value,
public readonly string $type
) {
if ($this->value === '') {
throw new \LogicException('Value must not be empty', 1651671582);
}
$this->length = strlen($this->value);
$this->ident = md5($this->type . '::' . ($this->value));
}
public function __toString(): string
{
return $this->value;
}
}
@@ -0,0 +1,120 @@
<?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\Utility\String;
/**
* @internal
*/
class StringFragmentCollection implements \Stringable, \Countable
{
/**
* @var list<StringFragment>
*/
protected array $fragments;
/**
* Length of all fragment strings
*/
protected int $length = 0;
public function __construct(StringFragment ...$fragments)
{
$lengths = array_map(static fn(StringFragment $fragment) => $fragment->length, $fragments);
$this->length = array_sum($lengths);
$this->fragments = $fragments;
}
public function __toString(): string
{
return implode('', array_map('strval', $this->fragments));
}
public function count(): int
{
return count($this->fragments);
}
public function with(StringFragment ...$fragments): self
{
$target = clone $this;
foreach ($fragments as $fragment) {
$target->length += $fragment->length;
$target->fragments[] = $fragment;
}
return $target;
}
public function withOnlyType(string $type): self
{
$fragments = array_filter(
$this->fragments,
static fn(StringFragment $item) => $item->type === $type
);
return new self(...$fragments);
}
public function withoutType(string $type): self
{
$fragments = array_filter(
$this->fragments,
static fn(StringFragment $item) => $item->type !== $type
);
return new self(...$fragments);
}
/**
* @return list<StringFragment>
*/
public function getFragments(): array
{
return $this->fragments;
}
public function getLength(): int
{
return $this->length;
}
public function diff(self $other): self
{
$otherFragmentIdents = $other->getFragmentIdents();
$differentFragments = array_filter(
$this->fragments,
static fn(StringFragment $item) => !in_array($item->ident, $otherFragmentIdents, true)
);
return new self(...$differentFragments);
}
public function intersect(self $other): self
{
$otherFragmentIdents = $other->getFragmentIdents();
$sameFragments = array_filter(
$this->fragments,
static fn(StringFragment $item) => in_array($item->ident, $otherFragmentIdents, true)
);
return new self(...$sameFragments);
}
/**
* @return list<string>
*/
protected function getFragmentIdents(): array
{
return array_map(static fn(StringFragment $item) => $item->ident, $this->fragments);
}
}
@@ -0,0 +1,41 @@
<?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\Utility\String;
/**
* @internal
*/
readonly class StringFragmentPattern
{
public function __construct(
public string $type,
public string $pattern
) {}
/**
* Compiles this string fragment pattern to a scoped PCRE pattern string.
*/
public function compilePattern(): string
{
return sprintf(
'(?P<%s>%s)',
$this->type . '_' . bin2hex(random_bytes(5)),
$this->pattern
);
}
}
@@ -0,0 +1,110 @@
<?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\Utility\String;
/**
* Splits a string into RAW and EXPRESSION fragments.
* EXPRESSION fragments are resolved by provided arbitrary regex pattern.
*
* @internal
*/
class StringFragmentSplitter
{
/**
* Raw string literals
*/
public const TYPE_RAW = 'raw';
/**
* Literals used as expression
*/
public const TYPE_EXPRESSION = 'expression';
/**
* Returns `null` in case there have not been any pattern matches,
* if omitted an array containing only `raw` fragments is returned
*/
public const FLAG_UNMATCHED_AS_NULL = 1;
/**
* @var list<StringFragmentPattern>
*/
protected readonly array $patterns;
public function __construct(StringFragmentPattern ...$patterns)
{
$this->patterns = $patterns;
}
/**
* @param string $value to be split into `raw` and `expression` fragments
* @param int $flags (optional) `FLAG_UNMATCHED_AS_NULL`
*/
public function split(string $value, int $flags = 0): ?StringFragmentCollection
{
$pattern = chr(1) . implode('|', $this->preparePatterns()) . chr(1);
$options = PREG_UNMATCHED_AS_NULL | PREG_OFFSET_CAPTURE | PREG_SET_ORDER;
if (!preg_match_all($pattern, $value, $matches, $options)) {
if (($flags & self::FLAG_UNMATCHED_AS_NULL) === self::FLAG_UNMATCHED_AS_NULL) {
return null;
}
return new StringFragmentCollection(StringFragment::raw($value));
}
$collection = new StringFragmentCollection();
foreach ($matches as $match) {
// filters string keys (e.g. `expression_a1b2c3d4e5`) from matches, skips numeric indexes
$types = array_filter(
array_keys($match),
static fn(int|string $type) => is_string($type) && $type !== ''
);
foreach ($types as $type) {
$matchOffset = $match[$type][1];
if ($matchOffset < 0) {
continue;
}
$matchValue = $match[$type][0];
// matches contain only pattern matches, but no raw string literals - by comparing the
// position of the collection with the current offset, missing raw literals are synchronized
if ($collection->getLength() < $matchOffset) {
$gapValue = substr($value, $collection->getLength(), $matchOffset - $collection->getLength());
$collection = $collection->with(StringFragment::raw($gapValue));
}
$collection = $collection->with(StringFragment::expression($matchValue));
}
}
// synchronize missing raw string literals
// (at the end of the given value after previous expression)
if ($collection->getLength() < strlen($value)) {
$gapValue = substr($value, $collection->getLength());
$collection = $collection->with(StringFragment::raw($gapValue));
}
return $collection;
}
/**
* @return list<string>
*/
protected function preparePatterns(): array
{
return array_map(
static fn(StringFragmentPattern $pattern) => $pattern->compilePattern(),
$this->patterns
);
}
}