first commit

This commit is contained in:
salam committed 2026-10-19 08:18:39 +02:00
commit 1218dd1517
12716 files changed
+989233

No files matched your search

@@ -0,0 +1,572 @@
<?php
namespace TYPO3\CMS\Extbase\Utility;
/*
* 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!
*/
/**
* This class is a backport of the corresponding class of TYPO3 Flow.
* All credits go to the TYPO3 Flow team.
*
* A debugging utility class
*/
class DebuggerUtility
{
const PLAINTEXT_INDENT = ' ';
const HTML_INDENT = '&nbsp;&nbsp;&nbsp;';
/**
* @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage
*/
protected static $renderedObjects;
/**
* Hardcoded list of Extbase class names (regex) which should not be displayed during debugging
*
* @var array
*/
protected static $blacklistedClassNames = [
'PHPUnit_Framework_MockObject_InvocationMocker',
\TYPO3\CMS\Extbase\Reflection\ReflectionService::class,
\TYPO3\CMS\Extbase\Object\ObjectManager::class,
\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapper::class,
\TYPO3\CMS\Extbase\Persistence\Generic\PersistenceManager::class,
\TYPO3\CMS\Extbase\Persistence\Generic\Qom\QueryObjectModelFactory::class,
\TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer::class
];
/**
* Hardcoded list of property names (regex) which should not be displayed during debugging
*
* @var array
*/
protected static $blacklistedPropertyNames = ['warning'];
/**
* Is set to TRUE once the CSS file is included in the current page to prevent double inclusions of the CSS file.
*
* @var bool
*/
protected static $stylesheetEchoed = false;
/**
* Defines the max recursion depth of the dump, set to 8 due to common memory limits
*
* @var int
*/
protected static $maxDepth = 8;
/**
* Clear the state of the debugger
*/
protected static function clearState()
{
self::$renderedObjects = new \TYPO3\CMS\Extbase\Persistence\ObjectStorage();
}
/**
* Renders a dump of the given value
*
* @param mixed $value
* @param int $level
* @param bool $plainText
* @param bool $ansiColors
* @return string
*/
protected static function renderDump($value, $level, $plainText, $ansiColors)
{
$dump = '';
if (is_string($value)) {
$croppedValue = strlen($value) > 2000 ? substr($value, 0, 2000) . '...' : $value;
if ($plainText) {
$dump = self::ansiEscapeWrap('"' . implode(PHP_EOL . str_repeat(self::PLAINTEXT_INDENT, $level + 1), str_split($croppedValue, 76)) . '"', '33', $ansiColors) . ' (' . strlen($value) . ' chars)';
} else {
$lines = str_split($croppedValue, 76);
$lines = array_map('htmlspecialchars', $lines);
$dump = sprintf('\'<span class="extbase-debug-string">%s</span>\' (%s chars)', implode('<br />' . str_repeat(self::HTML_INDENT, $level + 1), $lines), strlen($value));
}
} elseif (is_numeric($value)) {
$dump = sprintf('%s (%s)', self::ansiEscapeWrap($value, '35', $ansiColors), gettype($value));
} elseif (is_bool($value)) {
$dump = $value ? self::ansiEscapeWrap('TRUE', '32', $ansiColors) : self::ansiEscapeWrap('FALSE', '32', $ansiColors);
} elseif ($value === null || is_resource($value)) {
$dump = gettype($value);
} elseif (is_array($value)) {
$dump = self::renderArray($value, $level + 1, $plainText, $ansiColors);
} elseif (is_object($value)) {
if ($value instanceof \Closure) {
$dump = self::renderClosure($value, $level + 1, $plainText, $ansiColors);
} else {
$dump = self::renderObject($value, $level + 1, $plainText, $ansiColors);
}
}
return $dump;
}
/**
* Renders a dump of the given array
*
* @param array|\Traversable $array
* @param int $level
* @param bool $plainText
* @param bool $ansiColors
* @return string
*/
protected static function renderArray($array, $level, $plainText = false, $ansiColors = false)
{
$content = '';
$count = count($array);
if ($plainText) {
$header = self::ansiEscapeWrap('array', '36', $ansiColors);
} else {
$header = '<span class="extbase-debug-type">array</span>';
}
$header .= $count > 0 ? '(' . $count . ' item' . ($count > 1 ? 's' : '') . ')' : '(empty)';
if ($level >= self::$maxDepth) {
if ($plainText) {
$header .= ' ' . self::ansiEscapeWrap('max depth', '47;30', $ansiColors);
} else {
$header .= '<span class="extbase-debug-filtered">max depth</span>';
}
} else {
$content = self::renderCollection($array, $level, $plainText, $ansiColors);
if (!$plainText) {
$header = ($level > 1 && $count > 0 ? '<input type="checkbox" /><span class="extbase-debug-header" >' : '<span>') . $header . '</span >';
}
}
if ($level > 1 && $count > 0 && !$plainText) {
$dump = '<span class="extbase-debugger-tree">' . $header . '<span class="extbase-debug-content">' . $content . '</span></span>';
} else {
$dump = $header . $content;
}
return $dump;
}
/**
* Renders a dump of the given object
*
* @param object $object
* @param int $level
* @param bool $plainText
* @param bool $ansiColors
* @return string
*/
protected static function renderObject($object, $level, $plainText = false, $ansiColors = false)
{
if ($object instanceof \TYPO3\CMS\Extbase\Persistence\Generic\LazyLoadingProxy) {
$object = $object->_loadRealInstance();
if (!$object) {
return gettype($object);
}
}
$header = self::renderHeader($object, $level, $plainText, $ansiColors);
if ($level < self::$maxDepth && !self::isBlacklisted($object) && !(self::isAlreadyRendered($object) && $plainText !== true)) {
$content = self::renderContent($object, $level, $plainText, $ansiColors);
} else {
$content = '';
}
if ($plainText) {
return $header . $content;
}
return '<span class="extbase-debugger-tree">' . $header . '<span class="extbase-debug-content">' . $content . '</span></span>';
}
/**
* Renders a dump of the given closure
*
* @param \Closure $object
* @param int $level
* @param bool $plainText
* @param bool $ansiColors
* @return string
*/
protected static function renderClosure($object, $level, $plainText = false, $ansiColors = false)
{
$header = self::renderHeader($object, $level, $plainText, $ansiColors);
if ($level < self::$maxDepth && (!self::isAlreadyRendered($object) || $plainText)) {
$content = self::renderContent($object, $level, $plainText, $ansiColors);
} else {
$content = '';
}
if ($plainText) {
return $header . $content;
}
return '<span class="extbase-debugger-tree"><input type="checkbox" /><span class="extbase-debug-header">' . $header . '</span><span class="extbase-debug-content">' . $content . '</span></span>';
}
/**
* Checks if a given object or property should be excluded/filtered
*
* @param object $value An ReflectionProperty or other Object
* @return bool TRUE if the given object should be filtered
*/
protected static function isBlacklisted($value)
{
$result = false;
if ($value instanceof \ReflectionProperty) {
$result = in_array($value->getName(), self::$blacklistedPropertyNames, true);
} elseif (is_object($value)) {
$result = in_array(get_class($value), self::$blacklistedClassNames, true);
}
return $result;
}
/**
* Checks if a given object was already rendered.
*
* @param object $object
* @return bool TRUE if the given object was already rendered
*/
protected static function isAlreadyRendered($object)
{
return self::$renderedObjects->contains($object);
}
/**
* Renders the header of a given object/collection. It is usually the class name along with some flags.
*
* @param object $object
* @param int $level
* @param bool $plainText
* @param bool $ansiColors
* @return string The rendered header with tags
*/
protected static function renderHeader($object, $level, $plainText, $ansiColors)
{
$dump = '';
$persistenceType = '';
$className = get_class($object);
$classReflection = new \ReflectionClass($className);
if ($plainText) {
$dump .= self::ansiEscapeWrap($className, '36', $ansiColors);
} else {
$dump .= '<span class="extbase-debug-type">' . htmlspecialchars($className) . '</span>';
}
if (!$object instanceof \Closure) {
if ($object instanceof \TYPO3\CMS\Core\SingletonInterface) {
$scope = 'singleton';
} else {
$scope = 'prototype';
}
if ($plainText) {
$dump .= ' ' . self::ansiEscapeWrap($scope, '44;37', $ansiColors);
} else {
$dump .= $scope ? '<span class="extbase-debug-scope">' . $scope . '</span>' : '';
}
if ($object instanceof \TYPO3\CMS\Extbase\DomainObject\AbstractDomainObject) {
if ($object->_isDirty()) {
$persistenceType = 'modified';
} elseif ($object->_isNew()) {
$persistenceType = 'transient';
} else {
$persistenceType = 'persistent';
}
}
if ($object instanceof \TYPO3\CMS\Extbase\Persistence\ObjectStorage && $object->_isDirty()) {
$persistenceType = 'modified';
}
if ($object instanceof \TYPO3\CMS\Extbase\DomainObject\AbstractEntity) {
$domainObjectType = 'entity';
} elseif ($object instanceof \TYPO3\CMS\Extbase\DomainObject\AbstractValueObject) {
$domainObjectType = 'valueobject';
} else {
$domainObjectType = 'object';
}
if ($plainText) {
$dump .= ' ' . self::ansiEscapeWrap(($persistenceType ? $persistenceType . ' ' : '') . $domainObjectType, '42;30', $ansiColors);
} else {
$dump .= '<span class="extbase-debug-ptype">' . ($persistenceType ? $persistenceType . ' ' : '') . $domainObjectType . '</span>';
}
}
if (strpos(implode('|', self::$blacklistedClassNames), get_class($object)) > 0) {
if ($plainText) {
$dump .= ' ' . self::ansiEscapeWrap('filtered', '47;30', $ansiColors);
} else {
$dump .= '<span class="extbase-debug-filtered">filtered</span>';
}
} elseif (self::$renderedObjects->contains($object) && !$plainText) {
$dump = '<a href="javascript:;" onclick="document.location.hash=\'#' . spl_object_hash($object) . '\';" class="extbase-debug-seeabove">' . $dump . '<span class="extbase-debug-filtered">see above</span></a>';
} elseif ($level >= self::$maxDepth && !$object instanceof \DateTimeInterface) {
if ($plainText) {
$dump .= ' ' . self::ansiEscapeWrap('max depth', '47;30', $ansiColors);
} else {
$dump .= '<span class="extbase-debug-filtered">max depth</span>';
}
} elseif ($level > 1 && !$object instanceof \DateTimeInterface && !$plainText) {
if (($object instanceof \Countable && empty($object)) || empty($classReflection->getProperties())) {
$dump = '<span>' . $dump . '</span>';
} else {
$dump = '<input type="checkbox" id="' . spl_object_hash($object) . '" /><span class="extbase-debug-header">' . $dump . '</span>';
}
}
if ($object instanceof \Countable) {
$objectCount = count($object);
$dump .= $objectCount > 0 ? ' (' . $objectCount . ' items)' : ' (empty)';
}
if ($object instanceof \DateTimeInterface) {
$dump .= ' (' . $object->format(\DateTimeInterface::RFC3339) . ', ' . $object->getTimestamp() . ')';
}
if ($object instanceof \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface && !$object->_isNew()) {
$dump .= ' (uid=' . $object->getUid() . ', pid=' . $object->getPid() . ')';
}
return $dump;
}
/**
* @param object $object
* @param int $level
* @param bool $plainText
* @param bool $ansiColors
* @return string The rendered body content of the Object(Storage)
*/
protected static function renderContent($object, $level, $plainText, $ansiColors)
{
$dump = '';
if ($object instanceof \TYPO3\CMS\Extbase\Persistence\ObjectStorage || $object instanceof \Iterator || $object instanceof \ArrayObject) {
$dump .= self::renderCollection($object, $level, $plainText, $ansiColors);
} else {
self::$renderedObjects->attach($object);
if (!$plainText) {
$dump .= '<a name="' . spl_object_hash($object) . '" id="' . spl_object_hash($object) . '"></a>';
}
if ($object instanceof \Closure) {
$dump .= PHP_EOL . str_repeat(self::PLAINTEXT_INDENT, $level)
. ($plainText ? '' : '<span class="extbase-debug-closure">')
. self::ansiEscapeWrap('function (', '33', $ansiColors) . ($plainText ? '' : '</span>');
$reflectionFunction = new \ReflectionFunction($object);
$params = [];
foreach ($reflectionFunction->getParameters() as $parameter) {
$parameterDump = '';
if ($parameter->isArray()) {
if ($plainText) {
$parameterDump .= self::ansiEscapeWrap('array ', '36', $ansiColors);
} else {
$parameterDump .= '<span class="extbase-debug-type">array </span>';
}
} elseif ($parameter->getClass()) {
if ($plainText) {
$parameterDump .= self::ansiEscapeWrap($parameter->getClass()->name . ' ', '36', $ansiColors);
} else {
$parameterDump .= '<span class="extbase-debug-type">'
. htmlspecialchars($parameter->getClass()->name) . '</span>';
}
}
if ($parameter->isPassedByReference()) {
$parameterDump .= '&';
}
if ($parameter->isVariadic()) {
$parameterDump .= '...';
}
if ($plainText) {
$parameterDump .= self::ansiEscapeWrap('$' . $parameter->name, '37', $ansiColors);
} else {
$parameterDump .= '<span class="extbase-debug-property">'
. htmlspecialchars('$' . $parameter->name) . '</span>';
}
if ($parameter->isDefaultValueAvailable()) {
$parameterDump .= ' = ';
if ($plainText) {
$parameterDump .= self::ansiEscapeWrap(var_export($parameter->getDefaultValue(), true), '33', $ansiColors);
} else {
$parameterDump .= '<span class="extbase-debug-string">'
. htmlspecialchars(var_export($parameter->getDefaultValue(), true)) . '</span>';
}
}
$params[] = $parameterDump;
}
$dump .= implode(', ', $params);
if ($plainText) {
$dump .= self::ansiEscapeWrap(') {' . PHP_EOL, '33', $ansiColors);
} else {
$dump .= '<span class="extbase-debug-closure">) {' . PHP_EOL . '</span>';
}
$lines = file($reflectionFunction->getFileName());
for ($l = $reflectionFunction->getStartLine(); $l < $reflectionFunction->getEndLine() - 1; ++$l) {
$dump .= $plainText ? $lines[$l] : htmlspecialchars($lines[$l]);
}
$dump .= str_repeat(self::PLAINTEXT_INDENT, $level);
if ($plainText) {
$dump .= self::ansiEscapeWrap('}' . PHP_EOL, '33', $ansiColors);
} else {
$dump .= '<span class="extbase-debug-closure">}</span>';
}
} else {
if (get_class($object) === 'stdClass') {
$objReflection = new \ReflectionObject($object);
$properties = $objReflection->getProperties();
} else {
$classReflection = new \ReflectionClass(get_class($object));
$properties = $classReflection->getProperties();
}
foreach ($properties as $property) {
if (self::isBlacklisted($property)) {
continue;
}
$dump .= PHP_EOL . str_repeat(self::PLAINTEXT_INDENT, $level);
if ($plainText) {
$dump .= self::ansiEscapeWrap($property->getName(), '37', $ansiColors);
} else {
$dump .= '<span class="extbase-debug-property">'
. htmlspecialchars($property->getName()) . '</span>';
}
$dump .= ' => ';
$property->setAccessible(true);
$visibility = ($property->isProtected() ? 'protected' : ($property->isPrivate() ? 'private' : 'public'));
if ($plainText) {
$dump .= self::ansiEscapeWrap($visibility, '42;30', $ansiColors) . ' ';
} else {
$dump .= '<span class="extbase-debug-visibility">' . $visibility . '</span>';
}
$dump .= self::renderDump($property->getValue($object), $level, $plainText, $ansiColors);
if ($object instanceof \TYPO3\CMS\Extbase\DomainObject\AbstractDomainObject && !$object->_isNew() && $object->_isDirty($property->getName())) {
if ($plainText) {
$dump .= ' ' . self::ansiEscapeWrap('modified', '43;30', $ansiColors);
} else {
$dump .= '<span class="extbase-debug-dirty">modified</span>';
}
}
}
}
}
return $dump;
}
/**
* @param mixed $collection
* @param int $level
* @param bool $plainText
* @param bool $ansiColors
* @return string
*/
protected static function renderCollection($collection, $level, $plainText, $ansiColors)
{
$dump = '';
foreach ($collection as $key => $value) {
$dump .= PHP_EOL . str_repeat(self::PLAINTEXT_INDENT, $level);
if ($plainText) {
$dump .= self::ansiEscapeWrap($key, '37', $ansiColors);
} else {
$dump .= '<span class="extbase-debug-property">' . htmlspecialchars($key) . '</span>';
}
$dump .= ' => ';
$dump .= self::renderDump($value, $level, $plainText, $ansiColors);
}
if ($collection instanceof \Iterator && !$collection instanceof \Generator) {
$collection->rewind();
}
return $dump;
}
/**
* Wrap a string with the ANSI escape sequence for colorful output
*
* @param string $string The string to wrap
* @param string $ansiColors The ansi color sequence (e.g. "1;37")
* @param bool $enable If FALSE, the raw string will be returned
* @return string The wrapped or raw string
*/
protected static function ansiEscapeWrap($string, $ansiColors, $enable = true)
{
if ($enable) {
return '[' . $ansiColors . 'm' . $string . '';
}
return $string;
}
/**
* A var_dump function optimized for Extbase's object structures
*
* @param mixed $variable The value to dump
* @param string $title optional custom title for the debug output
* @param int $maxDepth Sets the max recursion depth of the dump. De- or increase the number according to your needs and memory limit.
* @param bool $plainText If TRUE, the dump is in plain text, if FALSE the debug output is in HTML format.
* @param bool $ansiColors If TRUE (default), ANSI color codes is added to the output, if FALSE the debug output not colored.
* @param bool $return if TRUE, the dump is returned for custom post-processing (e.g. embed in custom HTML). If FALSE (default), the dump is directly displayed.
* @param array $blacklistedClassNames An array of class names (RegEx) to be filtered. Default is an array of some common class names.
* @param array $blacklistedPropertyNames An array of property names and/or array keys (RegEx) to be filtered. Default is an array of some common property names.
* @return string if $return is TRUE, the dump is returned. By default, the dump is directly displayed, and nothing is returned.
*/
public static function var_dump($variable, $title = null, $maxDepth = 8, $plainText = false, $ansiColors = true, $return = false, $blacklistedClassNames = null, $blacklistedPropertyNames = null)
{
self::$maxDepth = $maxDepth;
if ($title === null) {
$title = 'Extbase Variable Dump';
}
$ansiColors = $plainText && $ansiColors;
if ($ansiColors === true) {
$title = '' . $title . '';
}
$backupBlacklistedClassNames = self::$blacklistedClassNames;
if (is_array($blacklistedClassNames)) {
self::$blacklistedClassNames = $blacklistedClassNames;
}
$backupBlacklistedPropertyNames = self::$blacklistedPropertyNames;
if (is_array($blacklistedPropertyNames)) {
self::$blacklistedPropertyNames = $blacklistedPropertyNames;
}
self::clearState();
$css = '';
if (!$plainText && self::$stylesheetEchoed === false) {
$css = '
<style type=\'text/css\'>
.extbase-debugger-tree{position:relative}
.extbase-debugger-tree input{position:absolute !important;float: none !important;top:0;left:0;height:14px;width:14px;margin:0 !important;cursor:pointer;opacity:0;z-index:2}
.extbase-debugger-tree input~.extbase-debug-content{display:none}
.extbase-debugger-tree .extbase-debug-header:before{position:relative;top:3px;content:"";padding:0;line-height:10px;height:12px;width:12px;text-align:center;margin:0 3px 0 0;background-image:url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkViZW5lXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgMTIgMTIiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDEyIDEyOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PHN0eWxlIHR5cGU9InRleHQvY3NzIj4uc3Qwe2ZpbGw6Izg4ODg4ODt9PC9zdHlsZT48cGF0aCBpZD0iQm9yZGVyIiBjbGFzcz0ic3QwIiBkPSJNMTEsMTFIMFYwaDExVjExeiBNMTAsMUgxdjloOVYxeiIvPjxnIGlkPSJJbm5lciI+PHJlY3QgeD0iMiIgeT0iNSIgY2xhc3M9InN0MCIgd2lkdGg9IjciIGhlaWdodD0iMSIvPjxyZWN0IHg9IjUiIHk9IjIiIGNsYXNzPSJzdDAiIHdpZHRoPSIxIiBoZWlnaHQ9IjciLz48L2c+PC9zdmc+);display:inline-block}
.extbase-debugger-tree input:checked~.extbase-debug-content{display:inline}
.extbase-debugger-tree input:checked~.extbase-debug-header:before{background-image:url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkViZW5lXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgMTIgMTIiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDEyIDEyOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PHN0eWxlIHR5cGU9InRleHQvY3NzIj4uc3Qwe2ZpbGw6Izg4ODg4ODt9PC9zdHlsZT48cGF0aCBpZD0iQm9yZGVyIiBjbGFzcz0ic3QwIiBkPSJNMTEsMTFIMFYwaDExVjExeiBNMTAsMUgxdjloOVYxeiIvPjxnIGlkPSJJbm5lciI+PHJlY3QgeD0iMiIgeT0iNSIgY2xhc3M9InN0MCIgd2lkdGg9IjciIGhlaWdodD0iMSIvPjwvZz48L3N2Zz4=)}
.extbase-debugger{display:block;text-align:left;background:#2a2a2a;border:1px solid #2a2a2a;box-shadow:0 3px 0 rgba(0,0,0,.5);color:#000;margin:20px;overflow:hidden;border-radius:4px}
.extbase-debugger-floating{position:relative;z-index:999}
.extbase-debugger-top{background:#444;font-size:12px;font-family:monospace;color:#f1f1f1;padding:6px 15px}
.extbase-debugger-center{padding:0 15px;margin:15px 0;background-image:repeating-linear-gradient(to bottom,transparent 0,transparent 20px,#252525 20px,#252525 40px)}
.extbase-debugger-center,.extbase-debugger-center .extbase-debug-string,.extbase-debugger-center a,.extbase-debugger-center p,.extbase-debugger-center pre,.extbase-debugger-center strong{font-size:12px;font-weight:400;font-family:monospace;line-height:20px;color:#f1f1f1}
.extbase-debugger-center pre{background-color:transparent;margin:0;padding:0;border:0;word-wrap:break-word;color:#999}
.extbase-debugger-center .extbase-debug-string{color:#ce9178;white-space:normal}
.extbase-debugger-center .extbase-debug-type{color:#569CD6;padding-right:4px}
.extbase-debugger-center .extbase-debug-unregistered{background-color:#dce1e8}
.extbase-debugger-center .extbase-debug-filtered,.extbase-debugger-center .extbase-debug-proxy,.extbase-debugger-center .extbase-debug-ptype,.extbase-debugger-center .extbase-debug-visibility,.extbase-debugger-center .extbase-debug-scope{color:#fff;font-size:10px;line-height:12px;padding:2px 4px;margin-right:2px;position:relative;top:-1px}
.extbase-debugger-center .extbase-debug-scope{background-color:#497AA2}
.extbase-debugger-center .extbase-debug-ptype{background-color:#698747}
.extbase-debugger-center .extbase-debug-visibility{background-color:#698747}
.extbase-debugger-center .extbase-debug-dirty{background-color:#FFFFB6}
.extbase-debugger-center .extbase-debug-filtered{background-color:#4F4F4F}
.extbase-debugger-center .extbase-debug-seeabove{text-decoration:none;font-style:italic}
.extbase-debugger-center .extbase-debug-property{color:#f1f1f1}
.extbase-debugger-center .extbase-debug-closure{color:#9BA223;}
</style>';
self::$stylesheetEchoed = true;
}
if ($plainText) {
$output = $title . PHP_EOL . self::renderDump($variable, 0, true, $ansiColors) . PHP_EOL . PHP_EOL;
} else {
$output = '
<div class="extbase-debugger ' . ($return ? 'extbase-debugger-inline' : 'extbase-debugger-floating') . '">
<div class="extbase-debugger-top">' . htmlspecialchars($title) . '</div>
<div class="extbase-debugger-center">
<pre dir="ltr">' . self::renderDump($variable, 0, false, false) . '</pre>
</div>
</div>
';
}
self::$blacklistedClassNames = $backupBlacklistedClassNames;
self::$blacklistedPropertyNames = $backupBlacklistedPropertyNames;
if ($return === true) {
return $css . $output;
}
echo $css . $output;
return '';
}
}
@@ -0,0 +1,22 @@
<?php
namespace TYPO3\CMS\Extbase\Utility\Exception;
/*
* 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!
*/
/**
* An Invalid Type Exception
*/
class InvalidTypeException extends \TYPO3\CMS\Extbase\Exception
{
}
@@ -0,0 +1,262 @@
<?php
namespace TYPO3\CMS\Extbase\Utility;
/*
* 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!
*/
/**
* Utilities to manage plugins and modules of an extension. Also useful to auto-generate the autoloader registry
* file ext_autoload.php.
*/
class ExtensionUtility
{
const PLUGIN_TYPE_PLUGIN = 'list_type';
const PLUGIN_TYPE_CONTENT_ELEMENT = 'CType';
/**
* Add auto-generated TypoScript to configure the Extbase Dispatcher.
*
* When adding a frontend plugin you will have to add both an entry to the TCA definition
* of tt_content table AND to the TypoScript template which must initiate the rendering.
* Including the plugin code after "defaultContentRendering" adds the necessary TypoScript
* for calling the appropriate controller and action of your plugin.
* FOR USE IN ext_localconf.php FILES
* Usage: 2
*
* @param string $extensionName The extension name (in UpperCamelCase) or the extension key (in lower_underscore)
* @param string $pluginName must be a unique id for your plugin in UpperCamelCase (the string length of the extension key added to the length of the plugin name should be less than 32!)
* @param array $controllerActions is an array of allowed combinations of controller and action stored in an array (controller name as key and a comma separated list of action names as value, the first controller and its first action is chosen as default)
* @param array $nonCacheableControllerActions is an optional array of controller name and action names which should not be cached (array as defined in $controllerActions)
* @param string $pluginType either \TYPO3\CMS\Extbase\Utility\ExtensionUtility::PLUGIN_TYPE_PLUGIN (default) or \TYPO3\CMS\Extbase\Utility\ExtensionUtility::PLUGIN_TYPE_CONTENT_ELEMENT
* @throws \InvalidArgumentException
*/
public static function configurePlugin($extensionName, $pluginName, array $controllerActions, array $nonCacheableControllerActions = [], $pluginType = self::PLUGIN_TYPE_PLUGIN)
{
self::checkPluginNameFormat($pluginName);
self::checkExtensionNameFormat($extensionName);
// Check if vendor name is prepended to extensionName in the format {vendorName}.{extensionName}
$vendorName = null;
$delimiterPosition = strrpos($extensionName, '.');
if ($delimiterPosition !== false) {
$vendorName = str_replace('.', '\\', substr($extensionName, 0, $delimiterPosition));
$extensionName = substr($extensionName, $delimiterPosition + 1);
if (!empty($vendorName)) {
self::checkVendorNameFormat($vendorName, $extensionName);
}
}
$extensionName = str_replace(' ', '', ucwords(str_replace('_', ' ', $extensionName)));
$pluginSignature = strtolower($extensionName . '_' . $pluginName);
if (!is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['extbase']['extensions'][$extensionName]['plugins'][$pluginName] ?? false)) {
$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['extbase']['extensions'][$extensionName]['plugins'][$pluginName] = [];
}
foreach ($controllerActions as $controllerName => $actionsList) {
$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['extbase']['extensions'][$extensionName]['plugins'][$pluginName]['controllers'][$controllerName] = ['actions' => \TYPO3\CMS\Core\Utility\GeneralUtility::trimExplode(',', $actionsList)];
if (!empty($nonCacheableControllerActions[$controllerName])) {
$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['extbase']['extensions'][$extensionName]['plugins'][$pluginName]['controllers'][$controllerName]['nonCacheableActions'] = \TYPO3\CMS\Core\Utility\GeneralUtility::trimExplode(',', $nonCacheableControllerActions[$controllerName]);
}
}
switch ($pluginType) {
case self::PLUGIN_TYPE_PLUGIN:
$pluginContent = trim('
tt_content.list.20.' . $pluginSignature . ' = USER
tt_content.list.20.' . $pluginSignature . ' {
userFunc = TYPO3\\CMS\\Extbase\\Core\\Bootstrap->run
extensionName = ' . $extensionName . '
pluginName = ' . $pluginName . (null !== $vendorName ? ("\n\t" . 'vendorName = ' . $vendorName) : '') . '
}');
break;
case self::PLUGIN_TYPE_CONTENT_ELEMENT:
$pluginContent = trim('
tt_content.' . $pluginSignature . ' =< lib.contentElement
tt_content.' . $pluginSignature . ' {
templateName = Generic
20 = USER
20 {
userFunc = TYPO3\\CMS\\Extbase\\Core\\Bootstrap->run
extensionName = ' . $extensionName . '
pluginName = ' . $pluginName . (null !== $vendorName ? ("\n\t\t" . 'vendorName = ' . $vendorName) : '') . '
}
}');
break;
default:
throw new \InvalidArgumentException('The pluginType "' . $pluginType . '" is not suported', 1289858856);
}
$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['extbase']['extensions'][$extensionName]['plugins'][$pluginName]['pluginType'] = $pluginType;
\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addTypoScript($extensionName, 'setup', '
# Setting ' . $extensionName . ' plugin TypoScript
' . $pluginContent, 'defaultContentRendering');
}
/**
* Register an Extbase PlugIn into backend's list of plugins
* FOR USE IN Configuration/TCA/Overrides/tt_content.php
*
* @param string $extensionName The extension name (in UpperCamelCase) or the extension key (in lower_underscore)
* @param string $pluginName must be a unique id for your plugin in UpperCamelCase (the string length of the extension key added to the length of the plugin name should be less than 32!)
* @param string $pluginTitle is a speaking title of the plugin that will be displayed in the drop down menu in the backend
* @param string $pluginIcon is an icon identifier or file path prepended with "EXT:", that will be displayed in the drop down menu in the backend (optional)
* @throws \InvalidArgumentException
*/
public static function registerPlugin($extensionName, $pluginName, $pluginTitle, $pluginIcon = null)
{
self::checkPluginNameFormat($pluginName);
self::checkExtensionNameFormat($extensionName);
$delimiterPosition = strrpos($extensionName, '.');
if ($delimiterPosition !== false) {
$extensionName = substr($extensionName, $delimiterPosition + 1);
}
$extensionName = str_replace(' ', '', ucwords(str_replace('_', ' ', $extensionName)));
$pluginSignature = strtolower($extensionName) . '_' . strtolower($pluginName);
// At this point $extensionName is normalized, no matter which format the method was feeded with.
// Calculate the original extensionKey from this again.
$extensionKey = \TYPO3\CMS\Core\Utility\GeneralUtility::camelCaseToLowerCaseUnderscored($extensionName);
// pluginType is usually defined by configurePlugin() in the global array. Use this or fall back to default "list_type".
$pluginType = $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['extbase']['extensions'][$extensionName]['plugins'][$pluginName]['pluginType'] ?? 'list_type';
\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addPlugin(
[$pluginTitle, $pluginSignature, $pluginIcon],
$pluginType,
$extensionKey
);
}
/**
* Registers an Extbase module (main or sub) to the backend interface.
* FOR USE IN ext_tables.php FILES
*
* @param string $extensionName The extension name (in UpperCamelCase) or the extension key (in lower_underscore)
* @param string $mainModuleName The main module key. So $main would be an index in the $TBE_MODULES array and $sub could be an element in the lists there. If $subModuleName is not set a blank $extensionName module is created
* @param string $subModuleName The submodule key.
* @param string $position This can be used to set the position of the $sub module within the list of existing submodules for the main module. $position has this syntax: [cmd]:[submodule-key]. cmd can be "after", "before" or "top" (or blank which is default). If "after"/"before" then submodule will be inserted after/before the existing submodule with [submodule-key] if found. If not found, the bottom of list. If "top" the module is inserted in the top of the submodule list.
* @param array $controllerActions is an array of allowed combinations of controller and action stored in an array (controller name as key and a comma separated list of action names as value, the first controller and its first action is chosen as default)
* @param array $moduleConfiguration The configuration options of the module (icon, locallang.xlf file)
* @throws \InvalidArgumentException
*/
public static function registerModule($extensionName, $mainModuleName = '', $subModuleName = '', $position = '', array $controllerActions = [], array $moduleConfiguration = [])
{
self::checkExtensionNameFormat($extensionName);
// Check if vendor name is prepended to extensionName in the format {vendorName}.{extensionName}
$vendorName = null;
if (false !== $delimiterPosition = strrpos($extensionName, '.')) {
$vendorName = str_replace('.', '\\', substr($extensionName, 0, $delimiterPosition));
$extensionName = substr($extensionName, $delimiterPosition + 1);
if (!empty($vendorName)) {
self::checkVendorNameFormat($vendorName, $extensionName);
}
}
$extensionName = str_replace(' ', '', ucwords(str_replace('_', ' ', $extensionName)));
$defaultModuleConfiguration = [
'access' => 'admin',
'icon' => 'EXT:extbase/Resources/Public/Icons/Extension.png',
'labels' => ''
];
if ($mainModuleName !== '' && !array_key_exists($mainModuleName, $GLOBALS['TBE_MODULES'])) {
$mainModuleName = $extensionName . \TYPO3\CMS\Core\Utility\GeneralUtility::underscoredToUpperCamelCase($mainModuleName);
} else {
$mainModuleName = $mainModuleName !== '' ? $mainModuleName : 'web';
}
// add mandatory parameter to use new pagetree
if ($mainModuleName === 'web') {
$defaultModuleConfiguration['navigationComponentId'] = 'TYPO3/CMS/Backend/PageTree/PageTreeElement';
}
\TYPO3\CMS\Core\Utility\ArrayUtility::mergeRecursiveWithOverrule($defaultModuleConfiguration, $moduleConfiguration);
$moduleConfiguration = $defaultModuleConfiguration;
$moduleSignature = $mainModuleName;
if ($subModuleName !== '') {
$subModuleName = $extensionName . \TYPO3\CMS\Core\Utility\GeneralUtility::underscoredToUpperCamelCase($subModuleName);
$moduleSignature .= '_' . $subModuleName;
}
$moduleConfiguration['name'] = $moduleSignature;
if (null !== $vendorName) {
$moduleConfiguration['vendorName'] = $vendorName;
}
$moduleConfiguration['extensionName'] = $extensionName;
$moduleConfiguration['routeTarget'] = \TYPO3\CMS\Extbase\Core\Bootstrap::class . '::handleBackendRequest';
if (!is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['extbase']['extensions'][$extensionName]['modules'][$moduleSignature] ?? false)) {
$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['extbase']['extensions'][$extensionName]['modules'][$moduleSignature] = [];
}
foreach ($controllerActions as $controllerName => $actions) {
$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['extbase']['extensions'][$extensionName]['modules'][$moduleSignature]['controllers'][$controllerName] = [
'actions' => \TYPO3\CMS\Core\Utility\GeneralUtility::trimExplode(',', $actions)
];
}
\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addModule($mainModuleName, $subModuleName, $position, null, $moduleConfiguration);
}
/**
* Register a type converter by class name.
*
* @param string $typeConverterClassName
*/
public static function registerTypeConverter($typeConverterClassName)
{
if (!isset($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['extbase']['typeConverters']) ||
!is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['extbase']['typeConverters'])
) {
$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['extbase']['typeConverters'] = [];
}
if (!in_array($typeConverterClassName, $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['extbase']['typeConverters'])) {
$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['extbase']['typeConverters'][] = $typeConverterClassName;
}
}
/**
* Check a given vendor name for CGL compliance.
* Log a deprecation message if it is not.
*
* @param string $vendorName The vendor name to check
* @param string $extensionName The extension name that is affected
*/
protected static function checkVendorNameFormat($vendorName, $extensionName)
{
if (preg_match('/^[A-Z]/', $vendorName) !== 1) {
trigger_error('The vendor name from tx_' . $extensionName . ' must begin with a capital letter.', E_USER_DEPRECATED);
}
}
/**
* Check a given extension name for validity.
*
* @param string $extensionName The name of the extension
* @throws \InvalidArgumentException
*/
protected static function checkExtensionNameFormat($extensionName)
{
if (empty($extensionName)) {
throw new \InvalidArgumentException('The extension name must not be empty', 1239891990);
}
}
/**
* Check a given plugin name for validity.
*
* @param string $pluginName The name of the plugin
* @throws \InvalidArgumentException
*/
protected static function checkPluginNameFormat($pluginName)
{
if (empty($pluginName)) {
throw new \InvalidArgumentException('The plugin name must not be empty', 1239891988);
}
}
}
@@ -0,0 +1,54 @@
<?php
namespace TYPO3\CMS\Extbase\Utility;
/*
* 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!
*/
/**
* Utilities to simulate a frontend in backend context.
*
* @internal ONLY USED INTERNALLY, MIGHT CHANGE WITHOUT NOTICE!
*/
class FrontendSimulatorUtility
{
/**
* @var mixed
*/
protected static $tsfeBackup;
/**
* Sets the $TSFE->cObjectDepthCounter in Backend mode
* This somewhat hacky work around is currently needed because the cObjGetSingle() function of \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer relies on this setting
*
* @param \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer|null $cObj
*/
public static function simulateFrontendEnvironment(\TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer $cObj = null)
{
self::$tsfeBackup = $GLOBALS['TSFE'] ?? null;
$GLOBALS['TSFE'] = new \stdClass();
$GLOBALS['TSFE']->cObjectDepthCounter = 100;
$GLOBALS['TSFE']->cObj = $cObj ?? \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer::class);
}
/**
* Resets $GLOBALS['TSFE'] if it was previously changed by simulateFrontendEnvironment()
*
* @see simulateFrontendEnvironment()
*/
public static function resetFrontendEnvironment()
{
if (!empty(self::$tsfeBackup)) {
$GLOBALS['TSFE'] = self::$tsfeBackup;
}
}
}
@@ -0,0 +1,338 @@
<?php
namespace TYPO3\CMS\Extbase\Utility;
/*
* 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!
*/
use Psr\Http\Message\ServerRequestInterface;
use TYPO3\CMS\Core\Localization\Locales;
use TYPO3\CMS\Core\Localization\LocalizationFactory;
use TYPO3\CMS\Core\Site\Entity\SiteLanguage;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface;
use TYPO3\CMS\Extbase\Object\ObjectManager;
/**
* Localization helper which should be used to fetch localized labels.
*/
class LocalizationUtility
{
/**
* @var string
*/
protected static $locallangPath = 'Resources/Private/Language/';
/**
* Local Language content
*
* @var array
*/
protected static $LOCAL_LANG = [];
/**
* Contains those LL keys, which have been set to (empty) in TypoScript.
* This is necessary, as we cannot distinguish between a nonexisting
* translation and a label that has been cleared by TS.
* In both cases ['key'][0]['target'] is "".
*
* @var array
*/
protected static $LOCAL_LANG_UNSET = [];
/**
* @var \TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface
*/
protected static $configurationManager;
/**
* Returns the localized label of the LOCAL_LANG key, $key.
*
* @param string $key The key from the LOCAL_LANG array for which to return the value.
* @param string|null $extensionName The name of the extension
* @param array $arguments The arguments of the extension, being passed over to vsprintf
* @param string $languageKey The language key or null for using the current language from the system
* @param string[] $alternativeLanguageKeys The alternative language keys if no translation was found. If null and we are in the frontend, then the language_alt from TypoScript setup will be used
* @return string|null The value from LOCAL_LANG or null if no translation was found.
*/
public static function translate($key, $extensionName = null, $arguments = null, string $languageKey = null, array $alternativeLanguageKeys = null)
{
if ((string)$key === '') {
// Early return guard: returns null if the key was empty, because the key may be a dynamic value
// (from for example Fluid). Returning null allows null coalescing to a default value when that happens.
return null;
}
$value = null;
if (GeneralUtility::isFirstPartOfStr($key, 'LLL:')) {
$keyParts = explode(':', $key);
unset($keyParts[0]);
$key = array_pop($keyParts);
$languageFilePath = implode(':', $keyParts);
} else {
if (empty($extensionName)) {
throw new \InvalidArgumentException(
'Parameter $extensionName cannot be empty if a fully-qualified key is not specified.',
1498144052
);
}
$languageFilePath = static::getLanguageFilePath($extensionName);
}
$languageKeys = static::getLanguageKeys();
if ($languageKey === null) {
$languageKey = $languageKeys['languageKey'];
}
if (empty($alternativeLanguageKeys)) {
$alternativeLanguageKeys = $languageKeys['alternativeLanguageKeys'];
}
static::initializeLocalization($languageFilePath, $languageKey, $alternativeLanguageKeys, $extensionName);
// The "from" charset of csConv() is only set for strings from TypoScript via _LOCAL_LANG
if (!empty(self::$LOCAL_LANG[$languageFilePath][$languageKey][$key][0]['target'])
|| isset(self::$LOCAL_LANG_UNSET[$languageFilePath][$languageKey][$key])
) {
// Local language translation for key exists
$value = self::$LOCAL_LANG[$languageFilePath][$languageKey][$key][0]['target'];
} elseif (!empty($alternativeLanguageKeys)) {
$languages = array_reverse($alternativeLanguageKeys);
foreach ($languages as $language) {
if (!empty(self::$LOCAL_LANG[$languageFilePath][$language][$key][0]['target'])
|| isset(self::$LOCAL_LANG_UNSET[$languageFilePath][$language][$key])
) {
// Alternative language translation for key exists
$value = self::$LOCAL_LANG[$languageFilePath][$language][$key][0]['target'];
break;
}
}
}
if ($value === null && (!empty(self::$LOCAL_LANG[$languageFilePath]['default'][$key][0]['target'])
|| isset(self::$LOCAL_LANG_UNSET[$languageFilePath]['default'][$key]))
) {
// Default language translation for key exists
// No charset conversion because default is English and thereby ASCII
$value = self::$LOCAL_LANG[$languageFilePath]['default'][$key][0]['target'];
}
if (is_array($arguments) && $value !== null) {
// This unrolls arguments from $arguments - instead of calling vsprintf which receives arguments as an array.
// The reason is that only sprintf() will return an error message if the number of arguments does not match
// the number of placeholders in the format string. Whereas, vsprintf would silently return nothing.
return sprintf($value, ...array_values($arguments)) ?: sprintf('Error: could not translate key "%s" with value "%s" and %d argument(s)!', $key, $value, count($arguments));
}
return $value;
}
/**
* Loads local-language values by looking for a "locallang.xlf" (or "locallang.xml") file in the plugin resources directory and if found includes it.
* Also locallang values set in the TypoScript property "_LOCAL_LANG" are merged onto the values found in the "locallang.xlf" file.
*
* @param string $languageFilePath
* @param string $languageKey
* @param string[] $alternativeLanguageKeys
* @param string $extensionName
*/
protected static function initializeLocalization(string $languageFilePath, string $languageKey, array $alternativeLanguageKeys, string $extensionName = null)
{
$languageFactory = GeneralUtility::makeInstance(LocalizationFactory::class);
if (empty(self::$LOCAL_LANG[$languageFilePath][$languageKey])) {
$parsedData = $languageFactory->getParsedData($languageFilePath, $languageKey);
foreach ($parsedData as $tempLanguageKey => $data) {
if (!empty($data)) {
self::$LOCAL_LANG[$languageFilePath][$tempLanguageKey] = $data;
}
}
}
if ($languageKey !== 'default') {
foreach ($alternativeLanguageKeys as $alternativeLanguageKey) {
if (empty(self::$LOCAL_LANG[$languageFilePath][$alternativeLanguageKey])) {
$tempLL = $languageFactory->getParsedData($languageFilePath, $alternativeLanguageKey);
if (isset($tempLL[$alternativeLanguageKey])) {
self::$LOCAL_LANG[$languageFilePath][$alternativeLanguageKey] = $tempLL[$alternativeLanguageKey];
}
}
}
}
if (!empty($extensionName)) {
static::loadTypoScriptLabels($extensionName, $languageFilePath);
}
}
/**
* Returns the default path and filename for an extension
*
* @param string $extensionName
* @return string
*/
protected static function getLanguageFilePath(string $extensionName): string
{
return 'EXT:' . GeneralUtility::camelCaseToLowerCaseUnderscored($extensionName) . '/' . self::$locallangPath . 'locallang.xlf';
}
/**
* Sets the currently active language/language_alt keys.
* Default values are "default" for language key and an empty array for language_alt key.
*
* @return array
*/
protected static function getLanguageKeys(): array
{
$languageKeys = [
'languageKey' => 'default',
'alternativeLanguageKeys' => [],
];
if (TYPO3_MODE === 'FE') {
$tsfe = static::getTypoScriptFrontendController();
$siteLanguage = self::getCurrentSiteLanguage();
// Get values from site language, which takes precedence over TypoScript settings
if ($siteLanguage instanceof SiteLanguage) {
$languageKeys['languageKey'] = $siteLanguage->getTypo3Language();
} elseif (isset($tsfe->config['config']['language'])) {
$languageKeys['languageKey'] = $tsfe->config['config']['language'];
if (isset($tsfe->config['config']['language_alt'])) {
$languageKeys['alternativeLanguageKeys'][] = $tsfe->config['config']['language_alt'];
}
}
if (empty($languageKeys['alternativeLanguageKeys'])) {
$locales = GeneralUtility::makeInstance(Locales::class);
if (in_array($languageKeys['languageKey'], $locales->getLocales())) {
foreach ($locales->getLocaleDependencies($languageKeys['languageKey']) as $language) {
$languageKeys['alternativeLanguageKeys'][] = $language;
}
}
}
} elseif (!empty($GLOBALS['BE_USER']->uc['lang'])) {
$languageKeys['languageKey'] = $GLOBALS['BE_USER']->uc['lang'];
} elseif (!empty(static::getLanguageService()->lang)) {
$languageKeys['languageKey'] = static::getLanguageService()->lang;
}
return $languageKeys;
}
/**
* Overwrites labels that are set via TypoScript.
* TS locallang labels have to be configured like:
* plugin.tx_myextension._LOCAL_LANG.languageKey.key = value
*
* @param string $extensionName
* @param string $languageFilePath
*/
protected static function loadTypoScriptLabels($extensionName, $languageFilePath)
{
$configurationManager = static::getConfigurationManager();
$frameworkConfiguration = $configurationManager->getConfiguration(ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK, $extensionName);
if (!is_array($frameworkConfiguration['_LOCAL_LANG'] ?? false)) {
return;
}
self::$LOCAL_LANG_UNSET[$languageFilePath] = [];
foreach ($frameworkConfiguration['_LOCAL_LANG'] as $languageKey => $labels) {
if (!is_array($labels)) {
continue;
}
foreach ($labels as $labelKey => $labelValue) {
if (is_string($labelValue)) {
self::$LOCAL_LANG[$languageFilePath][$languageKey][$labelKey][0]['target'] = $labelValue;
if ($labelValue === '') {
self::$LOCAL_LANG_UNSET[$languageFilePath][$languageKey][$labelKey] = '';
}
} elseif (is_array($labelValue)) {
$labelValue = self::flattenTypoScriptLabelArray($labelValue, $labelKey);
foreach ($labelValue as $key => $value) {
self::$LOCAL_LANG[$languageFilePath][$languageKey][$key][0]['target'] = $value;
if ($value === '') {
self::$LOCAL_LANG_UNSET[$languageFilePath][$languageKey][$key] = '';
}
}
}
}
}
}
/**
* Flatten TypoScript label array; converting a hierarchical array into a flat
* array with the keys separated by dots.
*
* Example Input: array('k1' => array('subkey1' => 'val1'))
* Example Output: array('k1.subkey1' => 'val1')
*
* @param array $labelValues Hierarchical array of labels
* @param string $parentKey the name of the parent key in the recursion; is only needed for recursion.
* @return array flattened array of labels.
*/
protected static function flattenTypoScriptLabelArray(array $labelValues, $parentKey = '')
{
$result = [];
foreach ($labelValues as $key => $labelValue) {
if (!empty($parentKey)) {
if ($key === '_typoScriptNodeValue') {
$key = $parentKey;
} else {
$key = $parentKey . '.' . $key;
}
}
if (is_array($labelValue)) {
$labelValue = self::flattenTypoScriptLabelArray($labelValue, $key);
$result = array_merge($result, $labelValue);
} else {
$result[$key] = $labelValue;
}
}
return $result;
}
/**
* Returns instance of the configuration manager
*
* @return \TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface
*/
protected static function getConfigurationManager()
{
if (static::$configurationManager !== null) {
return static::$configurationManager;
}
$objectManager = GeneralUtility::makeInstance(ObjectManager::class);
$configurationManager = $objectManager->get(ConfigurationManagerInterface::class);
static::$configurationManager = $configurationManager;
return $configurationManager;
}
/**
* Returns the currently configured "site language" if a site is configured (= resolved)
* in the current request.
*
* @return SiteLanguage|null
*/
protected static function getCurrentSiteLanguage(): ?SiteLanguage
{
if ($GLOBALS['TYPO3_REQUEST'] instanceof ServerRequestInterface) {
return $GLOBALS['TYPO3_REQUEST']->getAttribute('language', null);
}
return null;
}
/**
* @return \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController
*/
protected static function getTypoScriptFrontendController()
{
return $GLOBALS['TSFE'];
}
/**
* @return \TYPO3\CMS\Core\Localization\LanguageService
*/
protected static function getLanguageService()
{
return $GLOBALS['LANG'];
}
}
@@ -0,0 +1,169 @@
<?php
namespace TYPO3\CMS\Extbase\Utility;
/* *
* This script belongs to the TYPO3 Flow framework. *
* *
* It is free software; you can redistribute it and/or modify it under *
* the terms of the GNU Lesser General Public License, either version 3 *
* of the License, or (at your option) any later version. *
* *
* The TYPO3 project - inspiring people to share! *
* */
/**
* PHP type handling functions
* @internal only to be used within Extbase, not part of TYPO3 Core API.
*/
class TypeHandlingUtility
{
/**
* A property type parse pattern.
*/
const PARSE_TYPE_PATTERN = '/^\\\\?(?P<type>integer|int|float|double|boolean|bool|string|DateTimeImmutable|DateTime|[A-Z][a-zA-Z0-9\\\\]+|object|resource|array|ArrayObject|SplObjectStorage|TYPO3\\\\CMS\\\\Extbase\\\\Persistence\\\\ObjectStorage)(?:<\\\\?(?P<elementType>[a-zA-Z0-9\\\\]+)>)?/';
/**
* A type pattern to detect literal types.
*/
const LITERAL_TYPE_PATTERN = '/^(?:integer|int|float|double|boolean|bool|string)$/';
/**
* @var array
*/
protected static $collectionTypes = ['array', 'ArrayObject', 'SplObjectStorage', \TYPO3\CMS\Extbase\Persistence\ObjectStorage::class];
/**
* Returns an array with type information, including element type for
* collection types (array, SplObjectStorage, ...)
*
* @param string $type Type of the property (see PARSE_TYPE_PATTERN)
* @return array An array with information about the type
* @throws \TYPO3\CMS\Extbase\Utility\Exception\InvalidTypeException
*/
public static function parseType($type)
{
$matches = [];
if (preg_match(self::PARSE_TYPE_PATTERN, $type, $matches)) {
$type = self::normalizeType($matches['type']);
$elementType = isset($matches['elementType']) ? self::normalizeType($matches['elementType']) : null;
if ($elementType !== null && !self::isCollectionType($type)) {
throw new \TYPO3\CMS\Extbase\Utility\Exception\InvalidTypeException('Found an invalid element type declaration in %s. Type "' . $type . '" must not have an element type hint (' . $elementType . ').', 1264093642);
}
return [
'type' => $type,
'elementType' => $elementType
];
}
throw new \TYPO3\CMS\Extbase\Utility\Exception\InvalidTypeException('Found an invalid element type declaration in %s. A type "' . var_export($type, true) . '" does not exist.', 1264093630);
}
/**
* Normalize data types so they match the PHP type names:
* int -> integer
* double -> float
* bool -> boolean
*
* @param string $type Data type to unify
* @return string unified data type
*/
public static function normalizeType($type)
{
switch ($type) {
case 'int':
$type = 'integer';
break;
case 'bool':
$type = 'boolean';
break;
case 'double':
$type = 'float';
break;
}
return $type;
}
/**
* Returns TRUE if the $type is a literal.
*
* @param string $type
* @return bool
*/
public static function isLiteral($type)
{
return preg_match(self::LITERAL_TYPE_PATTERN, $type) === 1;
}
/**
* Returns TRUE if the $type is a simple type.
*
* @param string $type
* @return bool
*/
public static function isSimpleType($type)
{
return in_array(self::normalizeType($type), ['array', 'string', 'float', 'integer', 'boolean'], true);
}
/**
* Returns TRUE if the $type is a CMS core type object.
*
* @param string|object $type
* @return bool
*/
public static function isCoreType($type)
{
return is_subclass_of($type, \TYPO3\CMS\Core\Type\TypeInterface::class);
}
/**
* Returns TRUE if the $type is a collection type.
*
* @param string $type
* @return bool
*/
public static function isCollectionType($type)
{
if (in_array($type, self::$collectionTypes, true)) {
return true;
}
if (class_exists($type) === true || interface_exists($type) === true) {
foreach (self::$collectionTypes as $collectionType) {
if (is_subclass_of($type, $collectionType) === true) {
return true;
}
}
}
return false;
}
/**
* Returns TRUE when the given value can be used in an "in" comparison in a query.
*
* @param mixed $value
* @return bool
*/
public static function isValidTypeForMultiValueComparison($value)
{
return is_array($value) || $value instanceof \Traversable;
}
/**
* Converts a hex encoded string into binary data
*
* @param string $hexadecimalData A hex encoded string of data
* @return string A binary string decoded from the input
*/
public static function hex2bin($hexadecimalData)
{
$binaryData = '';
$length = strlen($hexadecimalData);
for ($i = 0; $i < $length; $i += 2) {
$binaryData .= pack('C', hexdec(substr($hexadecimalData, $i, 2)));
}
return $binaryData;
}
}