diff --git a/typo3/sysext/core/Documentation/Changelog/master/Deprecation-94956-PublicCObj.rst b/typo3/sysext/core/Documentation/Changelog/master/Deprecation-94956-PublicCObj.rst new file mode 100644 index 0000000000000000000000000000000000000000..c50910267e0703d754807a155a3b8e0357c11ba0 --- /dev/null +++ b/typo3/sysext/core/Documentation/Changelog/master/Deprecation-94956-PublicCObj.rst @@ -0,0 +1,77 @@ +.. include:: ../../Includes.txt + +================================== +Deprecation: #94956 - Public $cObj +================================== + +See :issue:`94956` + +Description +=========== + +Frontend plugins receive an instance of :php:`ContentObjectRenderer` when +called via :php:`ContentObjectRenderer->callUserFunction()`. This is +typically the case for plugins called as :typoscript:`USER` or indirectly +as :typoscript:`USER_INT` type. + +The instance of :php:`ContentObjectRenderer` has previously been set by +declaring a public (!) property :php:`cObj` in the consuming class. + +Handing a :php:`ContentObjectRenderer` instance around this way is hard to +follow and has thus been deprecated: Declaring :php:`public $cObj` should +be avoided. Frontend plugins that need the current :php:`ContentObjectRenderer` +should have a :php:`public setContentObjectRenderer()` method instead. + + +Impact +====== + +Declaring :php:`public $cObj` in a class called by +:php:`ContentObjectRenderer->callUserFunction()` triggers a PHP :php:`E_USER_DEPRECATED` error. + + +Affected Installations +====================== + +Frontend extension classes that neither extend :php:`TYPO3\CMS\Frontend\Plugin\AbstractPlugin` +("pibase") nor extbase :php:`TYPO3\CMS\Extbase\Mvc\Controller\ActionController` +and have a public property :php:`cObj` are affected. + + +Migration +========= + +When instantiating the frontend plugin, :php:`ContentObjectRenderer->callUserFunction()` +now checks for a public method :php:`setContentObjectRenderer()` to explicitly set +an instance of the :php:`ContentObjectRenderer`. + +Many plugins may not need this instance at all. If the ContentObjectRenderer instance +used within the plugin does not rely on further ContentObjectRenderer state, for instance +if it only calls :php:`stdWrap()` or similar without using state like :typoscript:`LOAD_REGISTER`, +the :php:`cObj` class property should be avoided and an own instance of ContentObjectRenderer +should be created. + +Classes that do rely on current ContentObjectRenderer state should adapt their code. + +Before:: + + class Foo + { + public $cObj; + } + + +After:: + + class Foo + { + protected $cObj; + + public function setContentObjectRenderer(ContentObjectRenderer $cObj): void + { + $this->cObj = $cObj; + } + } + + +.. index:: Frontend, PHP-API, NotScanned, ext:frontend diff --git a/typo3/sysext/extbase/Classes/Core/Bootstrap.php b/typo3/sysext/extbase/Classes/Core/Bootstrap.php index a7f6223c818051affcb391b639816cee9699fc83..9c66953e10c3e3ba3a07528d1cc3867ebf7cd84f 100644 --- a/typo3/sysext/extbase/Classes/Core/Bootstrap.php +++ b/typo3/sysext/extbase/Classes/Core/Bootstrap.php @@ -50,12 +50,11 @@ class Bootstrap public static $persistenceClasses = []; /** - * Back reference to the parent content object - * This has to be public as it is set directly from TYPO3 + * Set by UserContentObject (USER) via setContentObjectRenderer() in frontend * - * @var \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer + * @var ContentObjectRenderer|null */ - public $cObj; + protected ?ContentObjectRenderer $cObj = null; protected ContainerInterface $container; protected ConfigurationManagerInterface $configurationManager; @@ -83,6 +82,16 @@ class Bootstrap $this->extbaseRequestBuilder = $extbaseRequestBuilder; } + /** + * Called for frontend plugins from UserContentObject via ContentObjectRenderer->callUserFunction(). + * + * @param ContentObjectRenderer $cObj + */ + public function setContentObjectRenderer(ContentObjectRenderer $cObj) + { + $this->cObj = $cObj; + } + /** * Explicitly initializes all necessary Extbase objects by invoking the various initialize* methods. * diff --git a/typo3/sysext/frontend/Classes/ContentObject/ContentObjectRenderer.php b/typo3/sysext/frontend/Classes/ContentObject/ContentObjectRenderer.php index 3cc0f750f62e82ef802fb23d62a951998af21dba..91dd439a767ed64aeb9c0d31c6d05dc47f0f2822 100644 --- a/typo3/sysext/frontend/Classes/ContentObject/ContentObjectRenderer.php +++ b/typo3/sysext/frontend/Classes/ContentObject/ContentObjectRenderer.php @@ -5233,8 +5233,22 @@ class ContentObjectRenderer implements LoggerAwareInterface } $methodName = (string)$parts[1]; $callable = [$classObj, $methodName]; + if (is_object($classObj) && method_exists($classObj, $parts[1]) && is_callable($callable)) { - $classObj->cObj = $this; + if (method_exists($classObj, 'setContentObjectRenderer') && is_callable([$classObj, 'setContentObjectRenderer'])) { + $classObj->setContentObjectRenderer($this); + } elseif (property_exists($classObj, 'cObj')) { + trigger_error( + 'Setting public property "cObj" is deprecated since v11 and will be removed in v12. Use explicit setter' + . ' "public function setContentObjectRenderer(ContentObjectRenderer $cObj)" if your plugin needs an instance of ContentObjectRenderer instead.', + E_USER_DEPRECATED + ); + // Note this will still fatal if that property is protected. There is no way to + // detect property visibility in PHP without reflection, so we'll deal with this in v11. + // Extensions should either drop the property altogether if they don't need current instance + // of ContentObjectRenderer, or set the property to protected and use the setter above. + $classObj->cObj = $this; + } $content = $callable($content, $conf, $this->getRequest()); } else { $this->getTimeTracker()->setTSlogMessage('Method "' . $parts[1] . '" did not exist in class "' . $parts[0] . '"', LogLevel::ERROR); diff --git a/typo3/sysext/frontend/Classes/DataProcessing/LanguageMenuProcessor.php b/typo3/sysext/frontend/Classes/DataProcessing/LanguageMenuProcessor.php index 19b83dd1019e6cde846f8449e105c36343c99f67..fbd6714c91dca91a503cbf3c5be34a36fa086166 100644 --- a/typo3/sysext/frontend/Classes/DataProcessing/LanguageMenuProcessor.php +++ b/typo3/sysext/frontend/Classes/DataProcessing/LanguageMenuProcessor.php @@ -50,7 +50,7 @@ class LanguageMenuProcessor implements DataProcessorInterface * * @var ContentObjectRenderer */ - public $cObj; + protected ?ContentObjectRenderer $cObj = null; /** * The processor configuration @@ -257,6 +257,17 @@ class LanguageMenuProcessor implements DataProcessorInterface $this->contentDataProcessor = GeneralUtility::makeInstance(ContentDataProcessor::class); } + /** + * This is called from UserContentObject via ContentObjectRenderer->callUserFunction() + * for nested menu items - those use a USER content object for getFieldAsJson(). + * + * @param ContentObjectRenderer $cObj + */ + public function setContentObjectRenderer(ContentObjectRenderer $cObj): void + { + $this->cObj = $cObj; + } + /** * Get configuration value from processorConfiguration * diff --git a/typo3/sysext/frontend/Classes/DataProcessing/MenuProcessor.php b/typo3/sysext/frontend/Classes/DataProcessing/MenuProcessor.php index 4f1afe907117103b52c7aed29aefe3ff8d60d1d7..a364eff3cd4a6e400c418eaec26487fd27d82249 100644 --- a/typo3/sysext/frontend/Classes/DataProcessing/MenuProcessor.php +++ b/typo3/sysext/frontend/Classes/DataProcessing/MenuProcessor.php @@ -68,7 +68,7 @@ class MenuProcessor implements DataProcessorInterface * * @var ContentObjectRenderer */ - public $cObj; + protected ?ContentObjectRenderer $cObj = null; /** * The processor configuration @@ -257,6 +257,17 @@ class MenuProcessor implements DataProcessorInterface $this->contentDataProcessor = GeneralUtility::makeInstance(ContentDataProcessor::class); } + /** + * This is called from UserContentObject via ContentObjectRenderer->callUserFunction() + * for nested menu items - those use a USER content object for getDataAsJson(). + * + * @param ContentObjectRenderer $cObj + */ + public function setContentObjectRenderer(ContentObjectRenderer $cObj): void + { + $this->cObj = $cObj; + } + /** * Get configuration value from processorConfiguration * diff --git a/typo3/sysext/frontend/Classes/Imaging/GifBuilder.php b/typo3/sysext/frontend/Classes/Imaging/GifBuilder.php index 9d50af6594885f05320e743598d49f1da0774aad..cbb47849f6cdbc5c19d7ccbd146f7c4b33e18dd8 100644 --- a/typo3/sysext/frontend/Classes/Imaging/GifBuilder.php +++ b/typo3/sysext/frontend/Classes/Imaging/GifBuilder.php @@ -97,6 +97,8 @@ class GifBuilder extends GraphicalFunctions /** * @var ContentObjectRenderer + * @deprecated Set to protected in v12. + * @todo: Signature in v12: protected ?ContentObjectRenderer $cObj = null; */ public $cObj; diff --git a/typo3/sysext/frontend/Classes/Plugin/AbstractPlugin.php b/typo3/sysext/frontend/Classes/Plugin/AbstractPlugin.php index 938c3ba5ea82ab124a88ad07a2c366ae9ab2ef29..ac6ef3b52fe09fd5e8b6717008e6a50772452940 100644 --- a/typo3/sysext/frontend/Classes/Plugin/AbstractPlugin.php +++ b/typo3/sysext/frontend/Classes/Plugin/AbstractPlugin.php @@ -44,6 +44,8 @@ class AbstractPlugin * The backReference to the mother cObj object set at call time * * @var ContentObjectRenderer + * @deprecated Set to protected in v12. + * @todo: Signature in v12: protected ?ContentObjectRenderer $cObj = null; */ public $cObj; @@ -246,6 +248,17 @@ class AbstractPlugin } } + /** + * This setter is called when the plugin is called from UserContentObject (USER) + * via ContentObjectRenderer->callUserFunction(). + * + * @param ContentObjectRenderer $cObj + */ + public function setContentObjectRenderer(ContentObjectRenderer $cObj): void + { + $this->cObj = $cObj; + } + /** * Recursively looks for stdWrap and executes it * diff --git a/typo3/sysext/frontend/Tests/Functional/SiteHandling/Fixtures/LinkHandlingController.php b/typo3/sysext/frontend/Tests/Functional/SiteHandling/Fixtures/LinkHandlingController.php index 7495bb1903992a3d8e6c4cff3fc92c95589ffd25..ba3033c213f31b7415ce6b51d4c19bdcbaaa1ff6 100644 --- a/typo3/sysext/frontend/Tests/Functional/SiteHandling/Fixtures/LinkHandlingController.php +++ b/typo3/sysext/frontend/Tests/Functional/SiteHandling/Fixtures/LinkHandlingController.php @@ -30,9 +30,19 @@ use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\RequestBootstrap; class LinkHandlingController { /** - * @var ContentObjectRenderer + * @var ContentObjectRenderer|null */ - public $cObj; + protected ?ContentObjectRenderer $cObj = null; + + /** + * This is called from UserContentObject via ContentObjectRenderer->callUserFunction(). + * + * @param ContentObjectRenderer $cObj + */ + public function setContentObjectRenderer(ContentObjectRenderer $cObj): void + { + $this->cObj = $cObj; + } /** * @return string diff --git a/typo3/sysext/seo/Classes/HrefLang/HrefLangGenerator.php b/typo3/sysext/seo/Classes/HrefLang/HrefLangGenerator.php index e6fc4805b3c8a648f3e59fa5836410c94b2acc1b..741129faa95fe89e9b99912ab96b748953a7fefb 100644 --- a/typo3/sysext/seo/Classes/HrefLang/HrefLangGenerator.php +++ b/typo3/sysext/seo/Classes/HrefLang/HrefLangGenerator.php @@ -41,7 +41,7 @@ class HrefLangGenerator * * @var ContentObjectRenderer */ - public $cObj; + protected $cObj; /** * @var LanguageMenuProcessor