Skip to content

Commit

Permalink
[EXPERIMENTAL] Integrate with EXT:content_blocks
Browse files Browse the repository at this point in the history
WIP: Move the new rendering logic to WEBCOMPONENT_CONTENTBLOCK cObj and restore previous behaviour
  • Loading branch information
smichaelsen committed Aug 3, 2024
1 parent e5e4a20 commit 1a5fd47
Show file tree
Hide file tree
Showing 11 changed files with 200 additions and 57 deletions.
49 changes: 14 additions & 35 deletions Classes/ContentObject/WebcomponentContentObject.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use Sinso\Webcomponents\DataProviding\AssertionFailedException;
use Sinso\Webcomponents\DataProviding\Traits\RenderComponent;
use Sinso\Webcomponents\Dto\Events\ComponentFolderIsApplied;
use Sinso\Webcomponents\Dto\Events\ComponentWillBeRendered;
use Sinso\Webcomponents\Dto\ComponentRenderingData;
use TYPO3\CMS\Core\EventDispatcher\EventDispatcher;
Expand All @@ -16,39 +17,26 @@ class WebcomponentContentObject extends AbstractContentObject
{
use RenderComponent;

public function __construct(
private readonly EventDispatcher $eventDispatcher,
) {
}

public function render($conf = []): string
{
$componentRenderingData = GeneralUtility::makeInstance(ComponentRenderingData::class);
if ($this->cObj->getCurrentTable() === 'tt_content') {
$componentRenderingData->setContentRecord($this->cObj->data);
}
if (isset($conf['additionalInputData.'])) {
// apply stdWrap to all additionalInputData properties
foreach ($conf['additionalInputData.'] as $key => $value) {
$key = (string) $key;
if (!str_ends_with($key, '.')) {
continue;
}
$keyWithoutDot = substr($key, 0, -1);
$conf['additionalInputData.'][$keyWithoutDot] = $this->cObj->stdWrapValue($keyWithoutDot, $conf['additionalInputData.']);
unset($conf['additionalInputData.'][$key]);
}
$componentRenderingData->setAdditionalInputData($conf['additionalInputData.']);
}

$componentFolder = $this->cObj->stdWrapValue('componentFolder', $conf);
try {
$componentRenderingData = self::evaluateComponent($componentRenderingData, $conf['component'] ?? '', $this->cObj);
$componentRenderingData = $this->applyComponentFolder($componentRenderingData, $componentFolder);
$event = GeneralUtility::makeInstance(ComponentWillBeRendered::class, $this->cObj, $componentRenderingData);
$this->eventDispatcher->dispatch($event);
} catch (AssertionFailedException $e) {
return $e->getRenderingPlaceholder();
}
$componentRenderingData = $this->evaluateTypoScriptConfiguration($componentRenderingData, $conf);

$event = GeneralUtility::makeInstance(ComponentWillBeRendered::class, $this->cObj, $componentRenderingData);
$eventDispatcher = GeneralUtility::makeInstance(EventDispatcher::class);
$eventDispatcher->dispatch($event);

if (!$componentRenderingData->isRenderable()) {
return '';
}

// render with tag builder
$markup = $this->renderMarkup($componentRenderingData);
Expand All @@ -59,19 +47,10 @@ public function render($conf = []): string
return $markup;
}

private function evaluateTypoScriptConfiguration(ComponentRenderingData $componentRenderingData, array $conf): ComponentRenderingData
private function applyComponentFolder(ComponentRenderingData $componentRenderingData, string $componentFolder): ComponentRenderingData
{
if (isset($conf['properties.'])) {
foreach ($conf['properties.'] as $key => $value) {
if (is_array($value)) {
continue;
}
$componentRenderingData->setTagProperty($key, $this->cObj->cObjGetSingle($value, $conf['properties.'][$key . '.']));
}
}
if (($conf['tagName'] ?? '') || ($conf['tagName.'] ?? [])) {
$componentRenderingData->setTagName($this->cObj->stdWrap($conf['tagName'] ?? '', $conf['tagName.'] ?? []) ?: null);
}
$event = GeneralUtility::makeInstance(ComponentFolderIsApplied::class, $componentRenderingData, $this->cObj, $componentFolder);
$this->eventDispatcher->dispatch($event);
return $componentRenderingData;
}

Expand Down
13 changes: 4 additions & 9 deletions Classes/DataProviding/Traits/Image.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,12 @@

use TYPO3\CMS\Core\Imaging\ImageManipulation\CropVariantCollection;
use TYPO3\CMS\Core\Resource\FileReference;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Domain\Model\FileReference as ExtbaseFileReference;
use TYPO3\CMS\Extbase\Service\ImageService;

trait Image
{
private ImageService $imageService;

public function injectImageService(ImageService $imageService): void
{
$this->imageService = $imageService;
}

public function getImageUri($image, $width, $height, string $cropVariant = 'default', bool $absolute = false): string
{
if ($image instanceof ExtbaseFileReference) {
Expand All @@ -41,7 +35,8 @@ public function getImageUri($image, $width, $height, string $cropVariant = 'defa
$processingInstructions['fileExtension'] = $arguments['fileExtension'];
}

$processedImage = $this->imageService->applyProcessingInstructions($image, $processingInstructions);
return $this->imageService->getImageUri($processedImage, $absolute);
$imageService = GeneralUtility::makeInstance(ImageService::class);
$processedImage = $imageService->applyProcessingInstructions($image, $processingInstructions);
return $imageService->getImageUri($processedImage, $absolute);
}
}
4 changes: 0 additions & 4 deletions Classes/DataProviding/Traits/RenderSubComponent.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,6 @@ protected function renderSubComponent(string $componentClassName, $additionalInp
return null;
}

if (!$componentRenderingData->isRenderable()) {
return null;
}

$properties = $componentRenderingData->getTagProperties();
if ($slot !== null) {
$properties['slot'] = $slot;
Expand Down
18 changes: 18 additions & 0 deletions Classes/Dto/Events/ComponentFolderIsApplied.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

declare(strict_types=1);

namespace Sinso\Webcomponents\Dto\Events;

use Sinso\Webcomponents\Dto\ComponentRenderingData;
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;

readonly class ComponentFolderIsApplied

Check failure on line 10 in Classes/Dto/Events/ComponentFolderIsApplied.php

View workflow job for this annotation

GitHub Actions / phpstan

Readonly classes are supported only on PHP 8.2 and later.
{
public function __construct(
public ComponentRenderingData $componentRenderingData,
public ContentObjectRenderer $contentObjectRenderer,
public string $componentFolder,
) {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

declare(strict_types=1);

namespace Sinso\Webcomponents\EventListener\ComponentFolderIsApplied;

use Praetorius\ViteAssetCollector\Service\ViteService;
use Sinso\Webcomponents\Dto\Events\ComponentFolderIsApplied;
use TYPO3\CMS\Core\Attribute\AsEventListener;
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;

#[AsEventListener(identifier: 'webcomponents/addEntryPointToAssetCollector')]
class AddEntryPointToAssetCollector
{
protected const SUPPORTED_ENTRY_POINT_FILE_NAMES = [
'entry.ts',
'entry.js',
];

public function __construct(
private readonly ViteService $viteService

Check failure on line 21 in Classes/EventListener/ComponentFolderIsApplied/AddEntryPointToAssetCollector.php

View workflow job for this annotation

GitHub Actions / phpstan

Parameter $viteService of method Sinso\Webcomponents\EventListener\ComponentFolderIsApplied\AddEntryPointToAssetCollector::__construct() has invalid type Praetorius\ViteAssetCollector\Service\ViteService.

Check failure on line 21 in Classes/EventListener/ComponentFolderIsApplied/AddEntryPointToAssetCollector.php

View workflow job for this annotation

GitHub Actions / phpstan

Property Sinso\Webcomponents\EventListener\ComponentFolderIsApplied\AddEntryPointToAssetCollector::$viteService has unknown class Praetorius\ViteAssetCollector\Service\ViteService as its type.
) {
}

public function __invoke(ComponentFolderIsApplied $event): void
{
foreach (self::SUPPORTED_ENTRY_POINT_FILE_NAMES as $fileName) {
$this->findAndAddEntryPoint($event->componentFolder . '/Source/' . $fileName);
}
}

protected function findAndAddEntryPoint(string $entryPointPath): void
{
$absoluteEntryPointFile = ExtensionManagementUtility::resolvePackagePath($entryPointPath);
if (!file_exists($absoluteEntryPointFile)) {
return;
}

$this->viteService->addAssetsFromManifest(

Check failure on line 39 in Classes/EventListener/ComponentFolderIsApplied/AddEntryPointToAssetCollector.php

View workflow job for this annotation

GitHub Actions / phpstan

Call to method addAssetsFromManifest() on an unknown class Praetorius\ViteAssetCollector\Service\ViteService.
$this->viteService->getDefaultManifestFile(),

Check failure on line 40 in Classes/EventListener/ComponentFolderIsApplied/AddEntryPointToAssetCollector.php

View workflow job for this annotation

GitHub Actions / phpstan

Call to method getDefaultManifestFile() on an unknown class Praetorius\ViteAssetCollector\Service\ViteService.
$entryPointPath,
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

declare(strict_types=1);

namespace Sinso\Webcomponents\EventListener\ComponentFolderIsApplied;

use Sinso\Webcomponents\Dto\Events\ComponentFolderIsApplied;
use TYPO3\CMS\Core\Attribute\AsEventListener;

#[AsEventListener(identifier: 'webcomponents/deriveTagNameFromComponentFolder')]
class DeriveTagNameFromComponentFolder
{
public function __invoke(ComponentFolderIsApplied $event): void
{
$componentFolderPath = rtrim($event->componentFolder, '/');
$lastFolderName = basename($componentFolderPath);
$event->componentRenderingData->setTagName($lastFolderName);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

declare(strict_types=1);

namespace Sinso\Webcomponents\EventListener\ComponentFolderIsApplied;

use Sinso\Webcomponents\DataProviding\ComponentInterface;
use Sinso\Webcomponents\Dto\Events\ComponentFolderIsApplied;
use TYPO3\CMS\Core\Attribute\AsEventListener;
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;

#[AsEventListener(identifier: 'webcomponents/executeProviderFile')]
class ExecuteProviderFile
{
public function __invoke(ComponentFolderIsApplied $event): void
{
$absoluteComponentFolder = ExtensionManagementUtility::resolvePackagePath($event->componentFolder);
$providerFile = $absoluteComponentFolder . '/Source/provide.php';
if (!file_exists($providerFile)) {
return;
}

$class = require $providerFile;
$provider = new $class();
if (!$provider instanceof ComponentInterface) {
throw new \Exception('Provider must implement ComponentInterface', 1722526311);
}
$provider->setContentObjectRenderer($event->contentObjectRenderer);

$provider->provide($event->componentRenderingData);
}
}
59 changes: 59 additions & 0 deletions Classes/ServiceProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?php

declare(strict_types=1);

namespace Sinso\Webcomponents;

use Psr\Container\ContainerInterface;
use TYPO3\CMS\ContentBlocks\Definition\ContentType\ContentType;
use TYPO3\CMS\ContentBlocks\Definition\TableDefinitionCollection;
use TYPO3\CMS\ContentBlocks\Registry\ContentBlockRegistry;
use TYPO3\CMS\Core\Package\AbstractServiceProvider;

class ServiceProvider extends AbstractServiceProvider
{

protected static function getPackagePath(): string
{
return __DIR__ . '/../';
}

protected static function getPackageName(): string
{
return 'sinso/webcomponents';
}

public function getFactories(): array
{
return [];
}

public function getExtensions(): array
{
return [
'content-blocks.typoscript' => static::overwriteContentBlocksTypoScript(...),
] + parent::getExtensions();
}

public static function overwriteContentBlocksTypoScript(ContainerInterface $container, \ArrayObject $typoScriptArrayObject): \ArrayObject
{
$contentBlockRegistry = $container->get(ContentBlockRegistry::class);

Check failure on line 40 in Classes/ServiceProvider.php

View workflow job for this annotation

GitHub Actions / phpstan

Class TYPO3\CMS\ContentBlocks\Registry\ContentBlockRegistry not found.
$tableDefinitionCollection = $container->get(TableDefinitionCollection::class);

Check failure on line 41 in Classes/ServiceProvider.php

View workflow job for this annotation

GitHub Actions / phpstan

Class TYPO3\CMS\ContentBlocks\Definition\TableDefinitionCollection not found.
foreach ($tableDefinitionCollection as $tableDefinition) {
foreach ($tableDefinition->getContentTypeDefinitionCollection() ?? [] as $typeDefinition) {
if ($tableDefinition->getContentType() !== ContentType::CONTENT_ELEMENT) {

Check failure on line 44 in Classes/ServiceProvider.php

View workflow job for this annotation

GitHub Actions / phpstan

Access to constant CONTENT_ELEMENT on an unknown class TYPO3\CMS\ContentBlocks\Definition\ContentType\ContentType.
continue;
}
$contentBlockExtPath = $contentBlockRegistry->getContentBlockExtPath($typeDefinition->getName());
$typoScript = <<<HEREDOC
tt_content.{$typeDefinition->getTypeName()} >
tt_content.{$typeDefinition->getTypeName()} = WEBCOMPONENT
tt_content.{$typeDefinition->getTypeName()}.componentFolder = $contentBlockExtPath
HEREDOC;
$typoScriptArrayObject->append($typoScript);
}
}

return $typoScriptArrayObject;
}
}
11 changes: 3 additions & 8 deletions Classes/ViewHelpers/RenderViewHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,18 +42,13 @@ public static function renderStatic(array $arguments, \Closure $renderChildrenCl
$componentRenderingData->setContentRecord($contentObjectRenderer->data);
try {
$componentRenderingData = self::evaluateComponent($componentRenderingData, $arguments['component'], $contentObjectRenderer);
$event = GeneralUtility::makeInstance(ComponentWillBeRendered::class, $contentObjectRenderer, $componentRenderingData);
$eventDispatcher = GeneralUtility::makeInstance(EventDispatcher::class);
$eventDispatcher->dispatch($event);
} catch (AssertionFailedException $e) {
return $e->getRenderingPlaceholder();
}

$event = GeneralUtility::makeInstance(ComponentWillBeRendered::class, $contentObjectRenderer, $componentRenderingData);
$eventDispatcher = GeneralUtility::makeInstance(EventDispatcher::class);
$eventDispatcher->dispatch($event);

if (!$componentRenderingData->isRenderable()) {
return '';
}

$tagName = $componentRenderingData->getTagName();
$content = $componentRenderingData->getTagContent();
$properties = $componentRenderingData->getTagProperties();
Expand Down
3 changes: 3 additions & 0 deletions Configuration/Services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ services:
autoconfigure: true
public: false

Sinso\Webcomponents\:
resource: '../Classes/*'

Sinso\Webcomponents\ContentObject\WebcomponentContentObject:
tags:
- name: frontend.contentobject
Expand Down
5 changes: 4 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@
"description": "Render Web Components",
"extra": {
"typo3/cms": {
"extension-key": "webcomponents"
"extension-key": "webcomponents",
"Package": {
"serviceProvider": "Sinso\\Webcomponents\\ServiceProvider"
}
}
},
"autoload": {
Expand Down

0 comments on commit 1a5fd47

Please sign in to comment.