diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d3d97e..3b7fdc4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.4.2] - 2026-02-02 + +### Fixed + +- **Complete fix for memory leak** - v0.4.1 fix was incomplete + - Added `$in_shortcode_context` flag to TemplateLoader to track when we're rendering shortcodes + - All shortcode render methods now call `enter_shortcode_context()` before loading data + - When in shortcode context, `the_content` filter is always skipped to prevent recursive shortcode processing + - This prevents infinite recursion when post content contains FediStream shortcodes + ## [0.4.1] - 2026-02-02 ### Fixed @@ -207,7 +217,8 @@ Initial release of WP FediStream - a WordPress plugin for streaming music over A --- -[Unreleased]: https://src.bundespruefstelle.ch/magdev/wp-fedistream/compare/v0.4.1...HEAD +[Unreleased]: https://src.bundespruefstelle.ch/magdev/wp-fedistream/compare/v0.4.2...HEAD +[0.4.2]: https://src.bundespruefstelle.ch/magdev/wp-fedistream/compare/v0.4.1...v0.4.2 [0.4.1]: https://src.bundespruefstelle.ch/magdev/wp-fedistream/compare/v0.4.0...v0.4.1 [0.4.0]: https://src.bundespruefstelle.ch/magdev/wp-fedistream/compare/v0.3.0...v0.4.0 [0.3.0]: https://src.bundespruefstelle.ch/magdev/wp-fedistream/compare/v0.2.0...v0.3.0 diff --git a/includes/Frontend/Shortcodes.php b/includes/Frontend/Shortcodes.php index 411456f..f4665b5 100644 --- a/includes/Frontend/Shortcodes.php +++ b/includes/Frontend/Shortcodes.php @@ -81,6 +81,9 @@ class Shortcodes { * @return string */ public function render_artist( array $atts ): string { + // Enter shortcode context to prevent recursive shortcode processing during data loading. + TemplateLoader::enter_shortcode_context(); + $atts = shortcode_atts( array( 'id' => 0, @@ -95,6 +98,7 @@ class Shortcodes { $post = $this->get_post( $atts, 'fedistream_artist' ); if ( ! $post ) { + TemplateLoader::exit_shortcode_context(); return ''; } @@ -119,6 +123,9 @@ class Shortcodes { * @return string */ public function render_album( array $atts ): string { + // Enter shortcode context to prevent recursive shortcode processing during data loading. + TemplateLoader::enter_shortcode_context(); + $atts = shortcode_atts( array( 'id' => 0, @@ -132,6 +139,7 @@ class Shortcodes { $post = $this->get_post( $atts, 'fedistream_album' ); if ( ! $post ) { + TemplateLoader::exit_shortcode_context(); return ''; } @@ -155,6 +163,9 @@ class Shortcodes { * @return string */ public function render_track( array $atts ): string { + // Enter shortcode context to prevent recursive shortcode processing during data loading. + TemplateLoader::enter_shortcode_context(); + $atts = shortcode_atts( array( 'id' => 0, @@ -168,6 +179,7 @@ class Shortcodes { $post = $this->get_post( $atts, 'fedistream_track' ); if ( ! $post ) { + TemplateLoader::exit_shortcode_context(); return ''; } @@ -191,6 +203,9 @@ class Shortcodes { * @return string */ public function render_playlist( array $atts ): string { + // Enter shortcode context to prevent recursive shortcode processing during data loading. + TemplateLoader::enter_shortcode_context(); + $atts = shortcode_atts( array( 'id' => 0, @@ -204,6 +219,7 @@ class Shortcodes { $post = $this->get_post( $atts, 'fedistream_playlist' ); if ( ! $post ) { + TemplateLoader::exit_shortcode_context(); return ''; } @@ -227,6 +243,9 @@ class Shortcodes { * @return string */ public function render_latest_releases( array $atts ): string { + // Enter shortcode context to prevent recursive shortcode processing during data loading. + TemplateLoader::enter_shortcode_context(); + $atts = shortcode_atts( array( 'count' => 6, @@ -292,6 +311,9 @@ class Shortcodes { * @return string */ public function render_popular_tracks( array $atts ): string { + // Enter shortcode context to prevent recursive shortcode processing during data loading. + TemplateLoader::enter_shortcode_context(); + $atts = shortcode_atts( array( 'count' => 10, @@ -359,6 +381,9 @@ class Shortcodes { * @return string */ public function render_artists_grid( array $atts ): string { + // Enter shortcode context to prevent recursive shortcode processing during data loading. + TemplateLoader::enter_shortcode_context(); + $atts = shortcode_atts( array( 'count' => 12, @@ -426,6 +451,9 @@ class Shortcodes { * @return string */ public function render_player( array $atts ): string { + // Enter shortcode context to prevent recursive shortcode processing during data loading. + TemplateLoader::enter_shortcode_context(); + $atts = shortcode_atts( array( 'track' => 0, @@ -471,6 +499,7 @@ class Shortcodes { } if ( empty( $tracks ) ) { + TemplateLoader::exit_shortcode_context(); return ''; } @@ -528,13 +557,20 @@ class Shortcodes { return $this->get_unlicensed_message(); } + // Enter shortcode context to prevent recursive shortcode processing. + TemplateLoader::enter_shortcode_context(); + try { - return $this->plugin->render( $template, $context ); + $result = $this->plugin->render( $template, $context ); } catch ( \Exception $e ) { + TemplateLoader::exit_shortcode_context(); if ( WP_DEBUG ) { return '
' . esc_html( $e->getMessage() ) . '
'; } return ''; } + + TemplateLoader::exit_shortcode_context(); + return $result; } } diff --git a/includes/Frontend/TemplateLoader.php b/includes/Frontend/TemplateLoader.php index 32be4c7..eb0e1cc 100644 --- a/includes/Frontend/TemplateLoader.php +++ b/includes/Frontend/TemplateLoader.php @@ -35,6 +35,43 @@ class TemplateLoader { */ private const MAX_RECURSION_DEPTH = 3; + /** + * Whether we're currently in a shortcode rendering context. + * When true, the_content filter is skipped to prevent recursive shortcode processing. + * + * @var bool + */ + private static bool $in_shortcode_context = false; + + /** + * Enter shortcode rendering context. + * Call this before rendering shortcode content to prevent recursive shortcode processing. + * + * @return void + */ + public static function enter_shortcode_context(): void { + self::$in_shortcode_context = true; + } + + /** + * Exit shortcode rendering context. + * Call this after shortcode rendering is complete. + * + * @return void + */ + public static function exit_shortcode_context(): void { + self::$in_shortcode_context = false; + } + + /** + * Check if we're in a shortcode rendering context. + * + * @return bool + */ + public static function is_in_shortcode_context(): bool { + return self::$in_shortcode_context; + } + /** * Constructor. */ @@ -213,13 +250,15 @@ class TemplateLoader { // Track recursion to prevent infinite loops from shortcodes in content. ++self::$recursion_depth; - // At depth > 1, skip the_content filter to prevent shortcode recursion. - $is_nested = self::$recursion_depth > 1; + // Skip the_content filter if: + // 1. We're in a shortcode context (prevents recursive shortcode processing) + // 2. We're at depth > 1 (nested data loading) + $skip_content_filter = self::$in_shortcode_context || self::$recursion_depth > 1; $data = array( 'id' => $post->ID, 'title' => get_the_title( $post ), - 'content' => $is_nested ? wp_kses_post( $post->post_content ) : apply_filters( 'the_content', $post->post_content ), + 'content' => $skip_content_filter ? wp_kses_post( $post->post_content ) : apply_filters( 'the_content', $post->post_content ), 'excerpt' => get_the_excerpt( $post ), 'permalink' => get_permalink( $post ), 'thumbnail' => get_the_post_thumbnail_url( $post->ID, 'large' ), diff --git a/wp-fedistream.php b/wp-fedistream.php index 91d3fc9..514eb40 100644 --- a/wp-fedistream.php +++ b/wp-fedistream.php @@ -3,7 +3,7 @@ * Plugin Name: WP FediStream * Plugin URI: https://src.bundespruefstelle.ch/magdev/wp-fedistream * Description: Stream music over ActivityPub - Build your own music streaming platform for Musicians and Labels. - * Version: 0.4.1 + * Version: 0.4.2 * Requires at least: 6.4 * Requires PHP: 8.3 * Author: Marco Graetsch @@ -26,7 +26,7 @@ if ( ! defined( 'ABSPATH' ) ) { * * @var string */ -define( 'WP_FEDISTREAM_VERSION', '0.4.1' ); +define( 'WP_FEDISTREAM_VERSION', '0.4.2' ); /** * Plugin file path.