Infinite recursion in Content SEO module causes fatal memory exhaustion on WooCommerce single product pages
<div><strong>Plugin:</strong> Nexter Extension <strong>Version:</strong> 4.7.1 <strong>Module:</strong> Content SEO <strong>Severity:</strong> Critical. Site-breaking fatal error. <strong>Status:</strong> Reproducible. Resolved by disabling the Content SEO module.</div><div><strong><br>Summary</strong></div><div>The Content SEO module enters infinite recursion when rendering WooCommerce single product pages. Nexter_Content_SEO_Title::filter_pre_document_title() hooks the pre_get_document_title filter, and within that filter chain Nexter_Content_SEO_Schema::get_replacements() calls wp_get_document_title(), which fires pre_get_document_title again. The cycle repeats until PHP memory is exhausted.</div><div>The result is a 500 error on every single product page. Raising the memory limit does not help; it only delays the crash.</div><div><strong><br>Environment</strong></div><div><strong>ItemValue</strong>WordPress | 7.0.2<br>PHP | 8.1 (also reproduced on 8.3)<br>Web server | LiteSpeed<br>Theme | Kadence 1.5.2<br>WooCommerce | 10.9.4<br>Nexter Extension | 4.7.1<br>Nexter Blocks | 5.0.1<br>PHP memory limit | Reproduced at 512M, 1G, and 2G</div><div><strong><br>Steps to reproduce</strong></div><ol><li>Install WordPress with WooCommerce and at least one published product.</li><li>Install and activate Nexter Extension 4.7.1.</li><li>Enable the Content SEO module.</li><li>Visit any single product page on the front end.</li></ol><div><strong>Expected:</strong> the product page renders.</div><div><strong>Actual:</strong> the page hangs for several minutes, then returns a 500 error. PHP logs a memory exhaustion fatal.</div><div><strong><br>Scope of the failure</strong></div><ul><li>Affects <strong>single product pages only</strong> (is_product()).</li><li>The <strong>shop archive</strong> page renders normally.</li><li>Product category archives render normally.</li><li>Regular pages and posts render normally.</li><li>WooCommerce product list in wp-admin renders normally.</li><li>Occurs on a <strong>newly created, empty simple product</strong> with no images, no custom fields, and no description. It is not data-specific.</li></ul><div><strong><br>Diagnostic evidence</strong></div><div><strong><br>Hook volume</strong></div><div>Instrumenting the all hook recorded <strong>over 1.8 million hook fires</strong> on a single product page request before the process died. A normal page on the same site completes in roughly 20,000.</div><div><strong><br>Backtrace</strong></div><div>Captured by halting execution on the 500th call to get_the_date:</div><pre>#0 WPCode_Snippet_Execute->{closure}() wp-includes/class-wp-hook.php:343
#1 WP_Hook->apply_filters() wp-includes/plugin.php:205
#2 apply_filters() wp-includes/general-template.php:2747
#3 get_the_date() nexter-extension/include/panel-settings/extensions/content-seo/class-seo-schema.php:1730
#4 Nexter_Content_SEO_Schema::get_replacements() .../class-seo-settings.php:87
#5 Nexter_Content_SEO_Settings::replace_variables() .../class-seo-title.php:198
#6 Nexter_Content_SEO_Title::resolve_string_with_context() .../class-seo-title.php:118
#7 Nexter_Content_SEO_Title::filter_document_title() .../class-seo-title.php:50
#8 Nexter_Content_SEO_Title::filter_pre_document_title() wp-includes/class-wp-hook.php:341
#9 WP_Hook->apply_filters() wp-includes/plugin.php:205
#10 apply_filters() wp-includes/general-template.php:1192
#11 wp_get_document_title() nexter-extension/.../class-seo-schema.php:1812
#12 Nexter_Content_SEO_Schema::get_replacements() .../class-seo-settings.php:87
#13 Nexter_Content_SEO_Settings::replace_variables() .../class-seo-title.php:198
#14 Nexter_Content_SEO_Title::resolve_string_with_context() .../class-seo-title.php:118
#15 Nexter_Content_SEO_Title::filter_document_title() .../class-seo-title.php:50
#16 Nexter_Content_SEO_Title::filter_pre_document_title() wp-includes/class-wp-hook.php:341
#17 WP_Hook->apply_filters() wp-includes/plugin.php:205
#18 apply_filters() wp-includes/general-template.php:1192
#19 wp_get_document_title() nexter-extension/.../class-seo-schema.php:1812</pre><div>Frames 11 through 19 repeat identically for the remaining depth of the trace.</div><div><strong><br>The recursion cycle</strong></div><pre>class-seo-title.php:50 filter_pre_document_title() hooks pre_get_document_title
-> class-seo-title.php:118 resolve_string_with_context()
-> class-seo-settings.php:87 replace_variables()
-> class-seo-schema.php:1812 get_replacements() calls wp_get_document_title()
-> wp_get_document_title() fires pre_get_document_title
-> back to filter_pre_document_title() [LOOP]</pre><div>class-seo-schema.php:1730 calling get_the_date() sits inside the same get_replacements() method and is what made the loop visible in profiling.</div><div><strong><br>Fatal errors logged</strong></div><pre>PHP Fatal error: Allowed memory size of 536870912 bytes exhausted
(tried to allocate 20480 bytes)
in wp-content/plugins/woocommerce/includes/data-stores/class-wc-product-data-store-cpt.php on line 489
PHP Fatal error: Allowed memory size of 2122317824 bytes exhausted
(tried to allocate 20480 bytes)
in wp-content/plugins/woocommerce/includes/data-stores/class-wc-product-data-store-cpt.php on line 489</pre><div>The crash location varies depending on where the allocation happens to fail. It is not meaningful. The allocation sizes are small (12KB to 262KB) even at a 2GB limit, which confirms unbounded recursion rather than a large single allocation.</div><div><strong><br>Related symptom</strong></div><div>The following also appears in the error log and may share a root cause:</div><pre>Cron unschedule event error for hook: nxt_seo_recalc_score
Error code: could_not_set
Error message: The cron event list could not be saved.</pre><div><strong><br>Workaround</strong></div><div>Disabling the <strong>Content SEO</strong> module resolves the issue immediately. Product pages render normally. All other Nexter Extension and Nexter Blocks functionality is unaffected.</div><div><strong><br>Suggested fix</strong></div><div>Add a re-entry guard to Nexter_Content_SEO_Title::filter_pre_document_title() so the filter returns early if it is already executing, for example:</div><div>php</div><pre>private static $generating_title = false;
public static function filter_pre_document_title( $title ) {
if ( self::$generating_title ) {
return $title;
}
self::$generating_title = true;
$result = self::filter_document_title( $title );
self::$generating_title = false;
return $result;
}</pre><div>Alternatively, avoid calling wp_get_document_title() from within Nexter_Content_SEO_Schema::get_replacements() when that method is reached via the title filter chain, or resolve the title replacement variable from a cached value rather than re-invoking the full title generation.</div>
---
*Originally had 1 votes on FeedBear.*