Skip to content

Commit

Permalink
[FEATURE] Introduce cObject for Extbase plugins
Browse files Browse the repository at this point in the history
A new TypoScript cObject "EXTBASEPLUGIN" is
added for Extbase plugins to allow
Extbase authors to not reference the Extbase
Bootstrap class anymore. This way, newcomers
do not have to worry about running defining
USER/USER_INT things, which is not relevant
for them.

As most existing users use the API, it does not
matter much, except for places.

In the future, Extbase logic can / should
be moved to the Extbase Plugin Content Object,
while removing the dependency on the
ConfigurationManager and the cacheable actions
from the Bootstrap and RequestBuilder classes.

At the current point, this is very much convenience
and helps to further document to distinguish
between Plugins (= PHP code) and regular Content Types.

Resolves: #100293
Releases: main
Change-Id: Ia3a7cceddd9f93dc606c3194828d26bed5a8f2b2
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/76517
Tested-by: Andreas Fernandez <a.fernandez@scripting-base.de>
Reviewed-by: Jochen <rothjochen@gmail.com>
Tested-by: Jochen <rothjochen@gmail.com>
Reviewed-by: Andreas Fernandez <a.fernandez@scripting-base.de>
Tested-by: core-ci <typo3@b13.com>
Tested-by: Georg Ringer <georg.ringer@gmail.com>
Reviewed-by: Georg Ringer <georg.ringer@gmail.com>
  • Loading branch information
bmack authored and georgringer committed Mar 27, 2023
1 parent a4fb013 commit f7df5aa
Show file tree
Hide file tree
Showing 7 changed files with 144 additions and 12 deletions.
@@ -0,0 +1,55 @@
.. include:: /Includes.rst.txt

.. _feature-100293-1679673289:

================================================================
Feature: #100293 - New ContentObject EXTBASEPLUGIN in TypoScript
================================================================

See :issue:`100293`

Description
===========

In order to lower the barrier for newcomers in the TYPO3 world, TYPO3 now has
a custom ContentObject in TypoScript called :typoscript:`EXTBASEPLUGIN`.

Previously, TypoScript code for Extbase plugins looked like this:

.. code-block:: typoscript
page.10 = USER
page.10 {
userFunc = TYPO3\\CMS\\Extbase\\Core\\Bootstrap->run
extensionName = shop
pluginName = cart
}
The new way, which Extbase Plugin Registration uses under the hood now, looks
like this:

.. code-block:: typoscript
page.10 = EXTBASEPLUGIN
page.10.extensionName = shop
page.10.pluginName = cart
The old way still works, but it is recommended to use the :typoscript:`EXTBASEPLUGIN`
ContentObject, as the direct reference to a PHP class (Bootstrap) might be
optimized in future versions.


Impact
======

This change is an effort to distinguish between plugins and regular other
more static content.

Extbase is the de-facto standard for Plugins, which serve dynamic content by
custom PHP code divided in controllers and actions by extension developers.

Regular other content can be written in pure TypoScript, such as ContentObjects
like FLUIDTEMPLATE, HMENU, COA or TEXT is used for other kind of renderings
in the frontend.

.. index:: TypoScript, ext:extbase
@@ -0,0 +1,65 @@
<?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\Extbase\ContentObject;

use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Core\Bootstrap;
use TYPO3\CMS\Frontend\ContentObject\AbstractContentObject;
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;

/**
* Contains EXTBASEPLUGIN class object.
*
* Creates a request and dispatches it to the controller which was specified
* by TS Setup and returns the content, currently handed over to the
* Extbase Bootstrap.
*
* This class is the main entry point for extbase extensions in the TYPO3 Frontend.
*/
class ExtbasePluginContentObject extends AbstractContentObject
{
public function render($conf = [])
{
$extbaseBootstrap = GeneralUtility::makeInstance(Bootstrap::class);
$extbaseBootstrap->setContentObjectRenderer($this->getContentObjectRenderer());
if ($this->cObj->getUserObjectType() === false) {
// Come here only if we are not called from $TSFE->processNonCacheableContentPartsAndSubstituteContentMarkers()!
$this->cObj->setUserObjectType(ContentObjectRenderer::OBJECTTYPE_USER);
}
$extbaseBootstrap->initialize($conf);
$content = $extbaseBootstrap->handleFrontendRequest($this->request);
// Rendering is deferred, as the action should not be cached, we pump this now to TSFE to be executed later-on
if ($this->cObj->doConvertToUserIntObject) {
$this->cObj->doConvertToUserIntObject = false;
// @todo: this should be removed in the future in TSFE to allow more "uncacheables" than USER_INTs
// also, the handleFrontendRequest() should return the full response in the future
$conf['userFunc'] = Bootstrap::class . '->run';
$this->cObj->setUserObjectType(ContentObjectRenderer::OBJECTTYPE_USER_INT);
$tsfe = $this->getTypoScriptFrontendController();
$substKey = 'INT_SCRIPT.' . $tsfe->uniqueHash();
$content = '<!--' . $substKey . '-->';
$tsfe->config['INTincScript'][$substKey] = [
'conf' => $conf,
'cObj' => serialize($this->cObj),
'type' => 'FUNC',
];
}
$this->cObj->setUserObjectType(false);
return $content;
}
}
11 changes: 10 additions & 1 deletion typo3/sysext/extbase/Classes/Core/Bootstrap.php
Expand Up @@ -35,6 +35,8 @@
* by TS Setup and returns the content.
*
* This class is the main entry point for extbase extensions.
*
* @todo: Please note that this class will become internal in TYPO3 v13.0
*/
class Bootstrap
{
Expand Down Expand Up @@ -121,6 +123,8 @@ public function initializeConfiguration(array $configuration): void
* If the Framework is not initialized yet, it will be initialized.
*
* This is usually used in Frontend plugins.
* This method will be marked as internal in the future, use EXTBASEPLUGIN in TypoScript to execute a Extbase plugin
* instead.
*
* @param string $content The content. Not used
* @param array $configuration The TS configuration array
Expand All @@ -133,7 +137,12 @@ public function run(string $content, array $configuration, ServerRequestInterfac
return $this->handleFrontendRequest($request);
}

protected function handleFrontendRequest(ServerRequestInterface $request): string
/**
* Used for any Extbase Plugin in the Frontend, be sure to run $this->initialize() before.
*
* @internal
*/
public function handleFrontendRequest(ServerRequestInterface $request): string
{
$extbaseRequest = $this->extbaseRequestBuilder->build($request);
if (!$this->isExtbaseRequestCacheable($extbaseRequest)) {
Expand Down
6 changes: 2 additions & 4 deletions typo3/sysext/extbase/Classes/Utility/ExtensionUtility.php
Expand Up @@ -73,9 +73,8 @@ public static function configurePlugin($extensionName, $pluginName, array $contr
switch ($pluginType) {
case self::PLUGIN_TYPE_PLUGIN:
$pluginContent = trim('
tt_content.list.20.' . $pluginSignature . ' = USER
tt_content.list.20.' . $pluginSignature . ' = EXTBASEPLUGIN
tt_content.list.20.' . $pluginSignature . ' {
userFunc = TYPO3\\CMS\\Extbase\\Core\\Bootstrap->run
extensionName = ' . $extensionName . '
pluginName = ' . $pluginName . '
}');
Expand All @@ -85,9 +84,8 @@ public static function configurePlugin($extensionName, $pluginName, array $contr
tt_content.' . $pluginSignature . ' =< lib.contentElement
tt_content.' . $pluginSignature . ' {
templateName = Generic
20 = USER
20 = EXTBASEPLUGIN
20 {
userFunc = TYPO3\\CMS\\Extbase\\Core\\Bootstrap->run
extensionName = ' . $extensionName . '
pluginName = ' . $pluginName . '
}
Expand Down
6 changes: 6 additions & 0 deletions typo3/sysext/extbase/Configuration/Services.yaml
Expand Up @@ -89,6 +89,12 @@ services:
- name: event.listener
identifier: 'extbase/add-default-extbase-module-icon'

# Content Object for Extbase Plugins
TYPO3\CMS\Extbase\ContentObject\ExtbasePluginContentObject:
tags:
- name: frontend.contentobject
identifier: 'EXTBASEPLUGIN'

# Type Converters
TYPO3\CMS\Extbase\Property\TypeConverter\ArrayConverter:
tags:
Expand Down
11 changes: 5 additions & 6 deletions typo3/sysext/extbase/Tests/Unit/Utility/ExtensionUtilityTest.php
Expand Up @@ -34,9 +34,8 @@ public function configurePluginWorksForMinimalisticSetup(): void
$GLOBALS['TYPO3_CONF_VARS']['FE']['defaultTypoScript_setup.'] = [];
ExtensionUtility::configurePlugin('MyExtension', 'Pi1', [FirstController::class => 'index']);
$staticTypoScript = $GLOBALS['TYPO3_CONF_VARS']['FE']['defaultTypoScript_setup.']['defaultContentRendering'];
self::assertStringContainsString('tt_content.list.20.myextension_pi1 = USER', $staticTypoScript);
self::assertStringContainsString('tt_content.list.20.myextension_pi1 = EXTBASEPLUGIN', $staticTypoScript);
self::assertStringContainsString('
userFunc = TYPO3\\CMS\\Extbase\\Core\\Bootstrap->run
extensionName = MyExtension
pluginName = Pi1', $staticTypoScript);
self::assertStringNotContainsString('USER_INT', $staticTypoScript);
Expand All @@ -50,7 +49,7 @@ public function configurePluginCreatesCorrectDefaultTypoScriptSetup(): void
$GLOBALS['TYPO3_CONF_VARS']['FE']['defaultTypoScript_setup.'] = [];
ExtensionUtility::configurePlugin('MyExtension', 'Pi1', [FirstController::class => 'index']);
$staticTypoScript = $GLOBALS['TYPO3_CONF_VARS']['FE']['defaultTypoScript_setup.']['defaultContentRendering'];
self::assertStringContainsString('tt_content.list.20.myextension_pi1 = USER', $staticTypoScript);
self::assertStringContainsString('tt_content.list.20.myextension_pi1 = EXTBASEPLUGIN', $staticTypoScript);
}

/**
Expand All @@ -63,7 +62,7 @@ public function configurePluginWorksForASingleControllerAction(): void
FirstController::class => 'index',
]);
$staticTypoScript = $GLOBALS['TYPO3_CONF_VARS']['FE']['defaultTypoScript_setup.']['defaultContentRendering'];
self::assertStringContainsString('tt_content.list.20.myextension_pi1 = USER', $staticTypoScript);
self::assertStringContainsString('tt_content.list.20.myextension_pi1 = EXTBASEPLUGIN', $staticTypoScript);
self::assertStringContainsString('
extensionName = MyExtension
pluginName = Pi1', $staticTypoScript);
Expand Down Expand Up @@ -116,7 +115,7 @@ public function configurePluginRespectsDefaultActionAsANonCacheableAction(): voi
FirstController::class => 'index,show',
]);
$staticTypoScript = $GLOBALS['TYPO3_CONF_VARS']['FE']['defaultTypoScript_setup.']['defaultContentRendering'];
self::assertStringContainsString('tt_content.list.20.myextension_pi1 = USER', $staticTypoScript);
self::assertStringContainsString('tt_content.list.20.myextension_pi1 = EXTBASEPLUGIN', $staticTypoScript);
self::assertStringContainsString('
extensionName = MyExtension
pluginName = Pi1', $staticTypoScript);
Expand Down Expand Up @@ -146,7 +145,7 @@ public function configurePluginRespectsNonDefaultActionAsANonCacheableAction():
FirstController::class => 'new,show',
]);
$staticTypoScript = $GLOBALS['TYPO3_CONF_VARS']['FE']['defaultTypoScript_setup.']['defaultContentRendering'];
self::assertStringContainsString('tt_content.list.20.myextension_pi1 = USER', $staticTypoScript);
self::assertStringContainsString('tt_content.list.20.myextension_pi1 = EXTBASEPLUGIN', $staticTypoScript);
self::assertStringContainsString('
extensionName = MyExtension
pluginName = Pi1', $staticTypoScript);
Expand Down
Expand Up @@ -41,7 +41,7 @@ public function render($conf = [])
// Come here only if we are not called from $TSFE->processNonCacheableContentPartsAndSubstituteContentMarkers()!
$this->cObj->setUserObjectType(ContentObjectRenderer::OBJECTTYPE_USER);
}
$tempContent = $this->cObj->callUserFunction($conf['userFunc'], $conf, '');
$tempContent = $this->cObj->callUserFunction($conf['userFunc'] ?? '', $conf, '');
if ($this->cObj->doConvertToUserIntObject) {
$this->cObj->doConvertToUserIntObject = false;
$content = $this->cObj->cObjGetSingle('USER_INT', $conf);
Expand Down

0 comments on commit f7df5aa

Please sign in to comment.