Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,8 @@ parameters:
level: 9
paths:
- ../Classes
ignoreErrors:
- identifier: trait.unused
path: ../Classes/DataProviding/Traits/ContentObjectRendererTrait.php
- identifier: trait.unused
path: ../Classes/DataProviding/Traits/RenderHtml.php
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
- name: PHPUnit Tests
uses: php-actions/phpunit@master
with:
version: 10.5
version: 11.5
bootstrap: vendor/autoload.php
configuration: .github/phpunit.xml

Expand Down
23 changes: 12 additions & 11 deletions Classes/ContentObject/WebcomponentContentObject.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

use Psr\Log\LoggerInterface;
use Sinso\Webcomponents\DataProviding\AssertionFailedException;
use Sinso\Webcomponents\DataProviding\ComponentInterface;
use Sinso\Webcomponents\Dto\ComponentRenderingData;
use Sinso\Webcomponents\Dto\InputData;
use Sinso\Webcomponents\Rendering\ComponentRenderer;
Expand All @@ -24,12 +23,15 @@ public function __construct(
*/
public function render($conf = []): string
{
$contentObjectRenderer = $this->getContentObjectRenderer();
/** @var array<string, mixed> $record */
$record = $contentObjectRenderer->data;

$inputData = new InputData(
$this->cObj?->data ?? [],
$this->cObj?->getCurrentTable() ?? '',
$record,
$contentObjectRenderer->getCurrentTable(),
);

$contentObjectRenderer = $this->getContentObjectRenderer();
if (is_array($conf['additionalInputData.'] ?? null)) {
// apply stdWrap to all additionalInputData properties
foreach ($conf['additionalInputData.'] as $key => $value) {
Expand All @@ -44,9 +46,8 @@ public function render($conf = []): string
$inputData->additionalData = $conf['additionalInputData.'];
}
$componentClassName = $contentObjectRenderer->stdWrapValue('component', $conf, null);
if (!empty($componentClassName)) {
if (is_string($componentClassName) && $componentClassName !== '') {
try {
/** @var class-string<ComponentInterface> $componentClassName */
$componentRenderingData = $this->componentRenderer->evaluateComponent($inputData, $componentClassName, $contentObjectRenderer);
} catch (AssertionFailedException $e) {
$this->logger->info('Component evaluation failed', ['conf' => $conf, 'data' => $inputData->record, 'exception' => $e]);
Expand All @@ -56,7 +57,7 @@ public function render($conf = []): string
$componentRenderingData = new ComponentRenderingData();
}

$componentRenderingData = $this->evaluateTypoScriptConfiguration($componentRenderingData, $conf);
$componentRenderingData = $this->evaluateTypoScriptConfiguration($componentRenderingData, $contentObjectRenderer, $conf);

$markup = $this->componentRenderer->renderComponent($componentRenderingData, $contentObjectRenderer);

Expand All @@ -71,17 +72,17 @@ public function render($conf = []): string
/**
* @param array<string, mixed> $conf
*/
private function evaluateTypoScriptConfiguration(ComponentRenderingData $componentRenderingData, array $conf): ComponentRenderingData
private function evaluateTypoScriptConfiguration(ComponentRenderingData $componentRenderingData, \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer $contentObjectRenderer, array $conf): ComponentRenderingData
{
if (is_array($conf['properties.'] ?? null)) {
foreach ($conf['properties.'] as $key => $value) {
if (is_array($value)) {
if (!is_scalar($value)) {
continue;
}
$componentRenderingData = $componentRenderingData->withTagProperty($key, $this->cObj?->stdWrap($value, $conf['properties.'][$key . '.'] ?? []));
$componentRenderingData = $componentRenderingData->withTagProperty((string)$key, $contentObjectRenderer->stdWrap((string)$value, $conf['properties.'][$key . '.'] ?? []));
}
}
$tagName = $this->cObj?->stdWrapValue('tagName', $conf);
$tagName = $contentObjectRenderer->stdWrapValue('tagName', $conf);
if (is_string($tagName) && $tagName !== '') {
return $componentRenderingData->withTagName($tagName);
}
Expand Down
4 changes: 3 additions & 1 deletion Classes/DataProviding/AssertionFailedException.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ class AssertionFailedException extends \RuntimeException
{
public function getRenderingPlaceholder(): string
{
if ($GLOBALS['TYPO3_CONF_VARS']['FE']['debug'] ?? false) {
/** @var array{FE?: array{debug?: bool}} $confVars */
$confVars = $GLOBALS['TYPO3_CONF_VARS'] ?? [];
if ($confVars['FE']['debug'] ?? false) {
return '<!-- ' . $this->getMessage() . ' -->';
}
return '';
Expand Down
8 changes: 5 additions & 3 deletions Classes/DataProviding/Helpers/DateTimeFormatHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,13 @@ public function formatTime(\DateTimeInterface|int $timestamp, int $dateType = \I

private function getLocaleFromRequest(): ?Locale
{
return $this->getRequest()->getAttribute('language')?->getLocale();
return $this->getRequest()?->getAttribute('language')?->getLocale();
}

private function getRequest(): ServerRequestInterface
private function getRequest(): ?ServerRequestInterface
{
return $GLOBALS['TYPO3_REQUEST'];
/** @var ServerRequestInterface|null $request */
$request = $GLOBALS['TYPO3_REQUEST'] ?? null;
return $request;
}
}
62 changes: 52 additions & 10 deletions Classes/DataProviding/Helpers/InlineItemsHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public function __construct(

/**
* @param array<string, int|string> $parentRecord
* @return list<array<string, int|string>>
* @return list<array<string, mixed>>
*/
public function loadInlineItems(array $parentRecord, string $inlineFieldName, string $parentTable = 'tt_content'): array
{
Expand All @@ -49,8 +49,8 @@ public function loadInlineItems(array $parentRecord, string $inlineFieldName, st
if (!is_string($foreignField) || $foreignField === '') {
$foreignField = null;
}
$foreignTableTca = $GLOBALS['TCA'][$foreignTable] ?? [];
$foreignSortby = $inlineFieldConfig['foreign_sortby'] ?? $foreignTableTca['ctrl']['sortby'] ?? null;
$foreignTableCtrl = $this->getTableCtrl($foreignTable);
$foreignSortby = $inlineFieldConfig['foreign_sortby'] ?? $foreignTableCtrl['sortby'] ?? null;
if (!is_string($foreignSortby) || $foreignSortby === '') {
$foreignSortby = null;
}
Expand All @@ -68,7 +68,7 @@ public function loadInlineItems(array $parentRecord, string $inlineFieldName, st
}
$constraints['uidList'] = $queryBuilder->expr()->in('uid', $queryBuilder->createNamedParameter($itemsUidList, ArrayParameterType::INTEGER));
}
$languageField = $foreignTableTca['ctrl']['languageField'] ?? null;
$languageField = $foreignTableCtrl['languageField'] ?? null;
if (is_string($languageField) && $languageField !== '') {
$constraints['language'] = $queryBuilder->expr()->in($languageField, $queryBuilder->createNamedParameter([-1, $parentRecord['sys_language_uid'] ?? 0], ArrayParameterType::INTEGER));
}
Expand All @@ -80,11 +80,11 @@ public function loadInlineItems(array $parentRecord, string $inlineFieldName, st
$processedItems = [];
foreach ($queriedItems as $item) {
// workspace overlay:
$this->pageRepository->versionOL($foreignTable, $item);
if ($item === false) {
$workspaceItem = $this->applyVersionOverlay($foreignTable, $item);
if (!is_array($workspaceItem)) {
continue;
}
$processedItems[] = $item;
$processedItems[] = $workspaceItem;
}

if (empty($foreignField)) {
Expand All @@ -101,13 +101,55 @@ public function loadInlineItems(array $parentRecord, string $inlineFieldName, st
}

/**
* @return array{config?: array<string, mixed>}
* @param array<string, mixed> $item
* @return mixed
*/
private function applyVersionOverlay(string $table, array $item): mixed
{
$this->pageRepository->versionOL($table, $item);
return $item;
}

/**
* @return array<string, mixed>
*/
private function getTableCtrl(string $table): array
{
$tableTca = $this->getGlobalTca()[$table] ?? null;
if (!is_array($tableTca)) {
return [];
}

$ctrl = $tableTca['ctrl'] ?? null;
return is_array($ctrl) ? $ctrl : [];
}

/**
* @return array<string, mixed>
*/
private function getGlobalTca(): array
{
$tca = $GLOBALS['TCA'] ?? null;
return is_array($tca) ? $tca : [];
}

/**
* @return array{config?: array<array-key, mixed>}
*/
protected function getInlineFieldTca(string $inlineFieldName, string $localTableName = 'tt_content'): array
{
if (!isset($GLOBALS['TCA'][$localTableName]['columns'][$inlineFieldName])) {
$tableTca = $this->getGlobalTca()[$localTableName] ?? null;
$columns = is_array($tableTca) ? ($tableTca['columns'] ?? null) : null;
$fieldTca = is_array($columns) ? ($columns[$inlineFieldName] ?? null) : null;
if (!is_array($fieldTca)) {
throw new \Exception('Tried to process inline records for non existing field ' . $localTableName . '.' . $inlineFieldName, 1587038305);
}
return $GLOBALS['TCA'][$localTableName]['columns'][$inlineFieldName];

$config = $fieldTca['config'] ?? null;
if (!is_array($config)) {
return [];
}

return ['config' => $config];
}
}
4 changes: 3 additions & 1 deletion Classes/DataProviding/Helpers/LabelHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ private function isFrontendTypoScriptAvailable(): bool

private function getRequest(): ?ServerRequestInterface
{
return $GLOBALS['TYPO3_REQUEST'] ?? null;
/** @var ServerRequestInterface|null $request */
$request = $GLOBALS['TYPO3_REQUEST'] ?? null;
return $request;
}
}
6 changes: 4 additions & 2 deletions Classes/DataProviding/Helpers/LinkHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,12 @@ private function htmlSanitizationIsActive(ContentObjectRenderer $contentObjectRe
['parseFunc' => '< lib.parseFunc'],
'parseFunc'
);
if (empty($configuration['parseFunc.']['htmlSanitize'])) {

$htmlSanitize = $configuration['parseFunc.']['htmlSanitize'] ?? null;
if ($htmlSanitize === null) {
return true;
}

return $configuration['parseFunc.']['htmlSanitize'] !== '0';
return (string)$htmlSanitize !== '0';
}
}
2 changes: 1 addition & 1 deletion Classes/Dto/InputData.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
final class InputData
{
/**
* @param array<string, string|integer> $record
* @param array<string, mixed> $record
* @param array<string, mixed> $additionalData
*/
public function __construct(
Expand Down
16 changes: 10 additions & 6 deletions Classes/Rendering/ComponentRenderer.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,18 @@ public function renderComponent(ComponentRenderingData $componentRenderingData,
return $this->renderMarkup($event->getComponentRenderingData(), $tagBuilder);
}

/**
* @param class-string<ComponentInterface> $componentClassName
*/
public function evaluateComponent(InputData $inputData, string $componentClassName, ?ContentObjectRenderer $contentObjectRenderer = null): ComponentRenderingData
{
/** @var ComponentInterface $component */
if (!class_exists($componentClassName)) {
throw new AssertionFailedException(
'Configured component class "' . $componentClassName . '" does not exist',
1729064011
);
}

$component = GeneralUtility::makeInstance($componentClassName);
$this->assert($component instanceof ComponentInterface, 'Component must implement ComponentInterface');
$this->assert($component instanceof ComponentInterface, 'Configured component class "' . $componentClassName . '" must implement ' . ComponentInterface::class);
/** @var ComponentInterface $component */
if ($contentObjectRenderer === null) {
$contentObjectRenderer = GeneralUtility::makeInstance(ContentObjectRenderer::class);
$contentObjectRenderer->start([]);
Expand All @@ -52,7 +56,7 @@ public function evaluateComponent(InputData $inputData, string $componentClassNa
ArrayUtility::removeNullValuesRecursive($componentRenderingData->getTagProperties())
);

$event = new ComponentEvaluated($componentRenderingData, $contentObjectRenderer, $inputData, $componentClassName);
$event = new ComponentEvaluated($componentRenderingData, $contentObjectRenderer, $inputData, get_class($component));
$this->eventDispatcher->dispatch($event);

return $event->getComponentRenderingData();
Expand Down
18 changes: 9 additions & 9 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,17 @@
},
"require": {
"ext-json": "*",
"typo3/cms-core": "^12.0 || ^13.0",
"typo3/cms-frontend": "*"
"typo3/cms-core": "^13.4",
"typo3/cms-frontend": "^13.4"
},
"require-dev": {
"phpstan/phpstan": "^1.11.9",
"saschaegerer/phpstan-typo3": "^1.10",
"friendsofphp/php-cs-fixer": "^3.61",
"phpunit/phpunit": "^10.5.35",
"phpstan/phpstan": "^2.1",
"saschaegerer/phpstan-typo3": "^2.1",
"friendsofphp/php-cs-fixer": "^3.94",
"phpunit/phpunit": "^11.5",
"typo3/coding-standards": "^0.8.0",
"typo3/testing-framework": "^8.2.3",
"ssch/typo3-rector": "^2.6"
"typo3/testing-framework": "^9.3",
"ssch/typo3-rector": "^3.12"
},
"suggest": {
"contentblocks/content-blocks": "Define webcomponents as content blocks"
Expand All @@ -40,7 +40,7 @@
"@rector"
],
"php-cs-fixer": "php-cs-fixer fix --config=./.github/.php-cs-fixer.dist.php",
"phpstan": "phpstan analyse --configuration=./.github/phpstan.neon",
"phpstan": "phpstan analyse --configuration=./.github/phpstan.neon --memory-limit=1G",
"rector": "rector process --config=./.github/rector.php"
}
}