diff --git a/core/components/pdotools/docs/changelog.txt b/core/components/pdotools/docs/changelog.txt index 65ca7a3a..acdb8fb8 100644 --- a/core/components/pdotools/docs/changelog.txt +++ b/core/components/pdotools/docs/changelog.txt @@ -1,5 +1,9 @@ Changelog for pdoTools. +Unreleased +============== +- Fixed infinite recursion (memory exhaustion) when a plugin attached to the pdoToolsOnFenomInit event re-enters CoreTools::getFenom() while Fenom is being constructed (e.g. loading a static element with the pdoParser active). The event is now fired once from getFenom() after the Fenom instance is registered in the services container. + 3.0.2-pl (11.04.2023) ============== - Support for file snippets in the pdoPage class. diff --git a/core/components/pdotools/src/CoreTools.php b/core/components/pdotools/src/CoreTools.php index 98ea5922..3ec5d9d9 100644 --- a/core/components/pdotools/src/CoreTools.php +++ b/core/components/pdotools/src/CoreTools.php @@ -20,6 +20,8 @@ class CoreTools protected $modx; /** @var Fenom $fenom */ protected $fenom; + /** @var bool $fenomInitialized Whether the pdoToolsOnFenomInit event has been fired */ + protected $fenomInitialized = false; /** @var array $config Array with class config */ protected $config = []; /** @var array $store Array for cache elements and user data */ @@ -146,7 +148,22 @@ public function getFenom() $this->modx->services->add('fenom', new $class($this->modx, $this)); } - return $this->modx->services->get('fenom'); + /** @var Fenom $fenom */ + $fenom = $this->modx->services->get('fenom'); + + // Fire pdoToolsOnFenomInit only once, and only AFTER the instance has been + // stored in the services container. If the event were fired from the Fenom + // constructor (i.e. before the container memoizes the instance), a plugin + // attached to pdoToolsOnFenomInit that re-enters getFenom() - for example the + // pdoParser parsing media source properties while a static element is being + // loaded - would build another Fenom and recurse until the memory limit is + // exhausted. + if (!$this->fenomInitialized) { + $this->fenomInitialized = true; + $fenom->invokeInitEvent(); + } + + return $fenom; } /** diff --git a/core/components/pdotools/src/Parsing/Fenom/Fenom.php b/core/components/pdotools/src/Parsing/Fenom/Fenom.php index ae09ed34..a67ec269 100644 --- a/core/components/pdotools/src/Parsing/Fenom/Fenom.php +++ b/core/components/pdotools/src/Parsing/Fenom/Fenom.php @@ -60,12 +60,24 @@ public function __construct(modX $modx, CoreTools $pdoTools) $this->setOptions($options); $this->_addDefaultModifiers(); + } + /** + * Fire the pdoToolsOnFenomInit event so plugins can extend Fenom + * (register custom modifiers, functions, etc). + * + * This is intentionally NOT called from the constructor. It must be invoked + * by CoreTools::getFenom() AFTER the instance has been registered in the + * services container - otherwise a plugin attached to pdoToolsOnFenomInit + * that re-enters getFenom() would build another Fenom and recurse endlessly. + */ + public function invokeInitEvent() + { $this->modx->invokeEvent( 'pdoToolsOnFenomInit', [ 'fenom' => $this, - 'config' => $pdoTools->config(), + 'config' => $this->pdoTools->config(), ] ); }