diff --git a/src/php/front-end/class-front-end.php b/src/php/front-end/class-front-end.php index 4a3407d4..91aae904 100644 --- a/src/php/front-end/class-front-end.php +++ b/src/php/front-end/class-front-end.php @@ -28,6 +28,11 @@ class Front_End { */ const PRISM_HANDLE = 'code-snippets-prism'; + /** + * Maximum depth for shortcode recursion. + */ + const MAX_SHORTCODE_DEPTH = 5; + /** * Class constructor */ @@ -235,7 +240,7 @@ protected function convert_boolean_attribute_flags( array $atts, array $boolean_ * * @return string Evaluated shortcode content. */ - protected function evaluate_shortcode_content( Snippet $snippet, array $atts ): string { + protected function evaluate_shortcode_content( Snippet $snippet, array $atts ): string { if ( empty( $atts['php'] ) ) { return $snippet->code; } @@ -325,8 +330,25 @@ public function render_content_shortcode( array $atts ): string { // Remove this shortcode from the list to prevent recursion. remove_shortcode( self::CONTENT_SHORTCODE ); - // Evaluate shortcodes. - $content = do_shortcode( $atts['format'] ? shortcode_unautop( $content ) : $content ); + // Recursion depth is limited to prevent infinite loops. + static $depth = 0; + $max_depth = self::MAX_SHORTCODE_DEPTH; + + // Find the shortcode in the content and replace it with the evaluated content. + $content = preg_replace_callback( + '/\[' . self::CONTENT_SHORTCODE . '([^\]]*)\]/', + function ($matches) use (&$depth, $max_depth) { + if ($depth >= $max_depth) { + return ''; + } + $depth++; + $atts = shortcode_parse_atts($matches[1]); + $result = $this->render_content_shortcode($atts); + $depth--; + return $result; + }, + $content + ); // Add this shortcode back to the list. add_shortcode( self::CONTENT_SHORTCODE, [ $this, 'render_content_shortcode' ] );