HEX
Server: Apache/2.4.68 (Debian)
System: Linux as-cs-widget-demo-us-central1 6.1.0-44-cloud-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.164-1 (2026-03-09) x86_64
User: root (0)
PHP: 8.2.32
Disabled: NONE
Upload Files
File: /var/www/html/wp-content/plugins/wp-recipe-maker/includes/public/class-wprm-blocks.php
<?php
/**
 * Handle Gutenberg Blocks.
 *
 * @link       https://bootstrapped.ventures
 * @since      3.1.2
 *
 * @package    WP_Recipe_Maker
 * @subpackage WP_Recipe_Maker/includes/public
 */

/**
 * Handle Gutenberg Blocks.
 *
 * @since      3.1.2
 * @package    WP_Recipe_Maker
 * @subpackage WP_Recipe_Maker/includes/public
 * @author     Brecht Vandersmissen <brecht@bootstrapped.ventures>
 */
class WPRM_Blocks {

	/**
	 * Register actions and filters.
	 *
	 * @since	3.1.2
	 */
	public static function init() {
		add_action( 'init', array( __CLASS__, 'register_recipe_block' ) );

		// Deprecation notice after 5.8.0.
		global $wp_version;
		if ( $wp_version && version_compare( $wp_version, '5.8', '<' ) ) {
			add_filter( 'block_categories', array( __CLASS__, 'block_categories' ) );
		} else {
			add_filter( 'block_categories_all', array( __CLASS__, 'block_categories' ) );
		}
	}

	/**
	 * Register block categories.
	 *
	 * @since	3.2.0
	 * @param	array $categories Existing block categories.
	 */
	public static function block_categories( $categories ) {
		return array_merge(
			$categories,
			array(
				array(
					'slug' => 'wp-recipe-maker',
					'title' => 'WP Recipe Maker',
				),
			)
		);
	}

	/**
	 * Register recipe block.
	 *
	 * @since	3.1.2
	 */
	public static function register_recipe_block() {
		if ( function_exists( 'register_block_type' ) ) {
			$block_settings = array(
				'attributes' => array(
					'id' => array(
						'type' => 'number',
						'default' => 0,
					),
					'align' => array(
						'type' => 'string',
						'default' => '',
					),
					'template' => array(
						'type' => 'string',
						'default' => '',
					),
					'updated' => array(
						'type' => 'number',
						'default' => 0,
					),
				),
				'render_callback' => array( __CLASS__, 'render_recipe_block' ),
			);
			register_block_type( 'wp-recipe-maker/recipe', $block_settings );

			$block_settings = array(
				'attributes' => array(
					'id' => array(
						'type' => 'number',
						'default' => 0,
					),
					'align' => array(
						'type' => 'string',
						'default' => '',
					),
					'updated' => array(
						'type' => 'number',
						'default' => 0,
					),
				),
				'render_callback' => array( __CLASS__, 'render_list_block' ),
			);
			register_block_type( 'wp-recipe-maker/list', $block_settings );


			$block_settings = array(
				'attributes' => array(
					'id' => array(
						'type' => 'number',
						'default' => 0,
					),
					'align' => array(
						'type' => 'string',
						'default' => '',
					),
					'link' => array(
						'type' => 'string',
						'default' => '',
					),
					'nofollow' => array(
						'type' => 'string',
						'default' => '',
					),
					'newtab' => array(
						'type' => 'string',
						'default' => '1',
					),
					'image' => array(
						'type' => 'number',
						'default' => 0,
					),
					'image_url' => array(
						'type' => 'string',
						'default' => '',
					),
					'credit' => array(
						'type' => 'string',
						'default' => '',
					),
					'name' => array(
						'type' => 'string',
						'default' => '',
					),
					'summary' => array(
						'type' => 'string',
						'default' => '',
					),
					'button' => array(
						'type' => 'string',
						'default' => '',
					),
					'template' => array(
						'type' => 'string',
						'default' => '',
					),
				),
				'render_callback' => array( __CLASS__, 'render_recipe_roundup_item_block' ),
			);
			register_block_type( 'wp-recipe-maker/recipe-roundup-item', $block_settings );

			$block_settings = array(
				'attributes' => array(
					'id' => array(
						'type' => 'number',
						'default' => 0,
					),
					'template' => array(
						'type' => 'string',
						'default' => '',
					),
				),
				'render_callback' => array( __CLASS__, 'render_recipe_snippet_block' ),
			);
			register_block_type( 'wp-recipe-maker/recipe-snippet', $block_settings );

			$block_settings = array(
				'attributes' => array(
					'id' => array(
						'type' => 'number',
						'default' => 0,
					),
					'part' => array(
						'type' => 'string',
						'default' => 'recipe-name',
					),
				),
				'render_callback' => array( __CLASS__, 'render_recipe_part_block' ),
			);
			register_block_type( 'wp-recipe-maker/recipe-part', $block_settings );

			$block_settings = array(
				'attributes' => array(
					'id' => array(
						'type' => 'number',
						'default' => 0,
					),
					'align' => array(
						'type' => 'string',
						'default' => 'left',
					),
				),
				'render_callback' => array( __CLASS__, 'render_nutrition_label_block' ),
			);
			register_block_type( 'wp-recipe-maker/nutrition-label', $block_settings );

			$block_settings = array(
				'attributes' => array(
					'id' => array(
						'type' => 'number',
						'default' => 0,
					),
					'text' => array(
						'type' => 'string',
						'default' => __( 'Jump to Recipe', 'wp-recipe-maker' ),
					),
				),
				'render_callback' => array( __CLASS__, 'render_jump_to_recipe_block' ),
			);
			register_block_type( 'wp-recipe-maker/jump-to-recipe', $block_settings );
			
			$block_settings = array(
				'attributes' => array(
					'id' => array(
						'type' => 'number',
						'default' => 0,
					),
					'text' => array(
						'type' => 'string',
						'default' => __( 'Jump to Video', 'wp-recipe-maker' ),
					),
				),
				'render_callback' => array( __CLASS__, 'render_jump_to_video_block' ),
			);
			register_block_type( 'wp-recipe-maker/jump-to-video', $block_settings );

			$block_settings = array(
				'attributes' => array(
					'id' => array(
						'type' => 'number',
						'default' => 0,
					),
					'text' => array(
						'type' => 'string',
						'default' => __( 'Print Recipe', 'wp-recipe-maker' ),
					),
				),
				'render_callback' => array( __CLASS__, 'render_print_recipe_block' ),
			);
			register_block_type( 'wp-recipe-maker/print-recipe', $block_settings );
		}
	}

	/**
	 * Parse the block attributes.
	 *
	 * @since	3.8.1
	 * @param	mixed $atts Block attributes.
	 */
	public static function parse_atts( $atts ) {
		// Account for demo recipe.
		if ( isset ( $atts['id'] ) ) {
			$atts['id'] = -1 == $atts['id'] ? 'demo' : intval( $atts['id'] );
		}

		return $atts;
	}

	/**
	 * Render the recipe block.
	 *
	 * @since	3.1.2
	 * @param	mixed $atts Block attributes.
	 */
	public static function render_recipe_block( $atts ) {
		$atts = self::parse_atts( $atts );
		$output = '';

		// Only do this for the Gutenberg Preview.
		if ( WPRM_Context::is_gutenberg_preview() ) {
			$recipe = WPRM_Recipe_Manager::get_recipe( $atts['id'] );

			// No recipe find? ID is incorrect => show warning.
			if ( ! $recipe ) {
				return '<div class="wprm-recipe-block-invalid">' . __( 'This is a "WPRM Recipe" block with a non-existing recipe ID.', 'wp-recipe-maker' ) . '</div>';
			}

			if ( isset( $atts['template'] ) && $atts['template'] ) {
				$template = WPRM_Template_Manager::get_template_by_slug( $atts['template'] );
			} else {
				// Get recipe type.
				$type = $recipe ? $recipe->type() : 'food';

				// Use default single recipe template.
				$template = WPRM_Template_Manager::get_template_by_type( 'single', $type );
				$atts['template'] = $template['slug'];
			}

			// Output style.
			if ( 'modern' === $template['mode'] ) {
				$output .= '<style type="text/css">' . WPRM_Template_Manager::get_template_css( $template ) . '</style>';
			} else {
				$output .= '<style type="text/css">' . WPRM_Assets::get_custom_css( 'recipe' ) . '</style>';
			}
		}

		$output .= WPRM_Shortcode::recipe_shortcode( $atts );

		return $output;
	}

	/**
	 * Render the list block.
	 *
	 * @since	3.1.2
	 * @param	mixed $atts Block attributes.
	 */
	public static function render_list_block( $atts ) {
		$atts = self::parse_atts( $atts );
		$output = '';

		// Only do this for the Gutenberg Preview.
		if ( WPRM_Context::is_gutenberg_preview() ) {
			$list = WPRM_List_Manager::get_list( $atts['id'] );

			// No list found? ID is incorrect => show warning.
			if ( ! $list ) {
				return '<div class="wprm-recipe-block-invalid">' . __( 'This is a "WPRM List" block with a non-existing recipe ID.', 'wp-recipe-maker' ) . '</div>';
			}

			if ( 'default' !== $list->template() ) {
				$template = WPRM_Template_Manager::get_template_by_slug( $list->template() );
			} else {
				// Use default single recipe template.
				$template = WPRM_Template_Manager::get_template_by_type( 'roundup' );
			}
			$output .= '<style type="text/css">' . WPRM_Template_Manager::get_template_css( $template ) . '</style>';
		}

		$output .= WPRM_List_Shortcode::shortcode( $atts );

		return $output;
	}

	/**
	 * Render the recipe roundup item block.
	 *
	 * @since	4.3.0
	 * @param	mixed $atts Block attributes.
	 */
	public static function render_recipe_roundup_item_block( $atts ) {
		$atts = self::parse_atts( $atts );
		$output = '';

		// Only do this for the Gutenberg Preview.
		if ( WPRM_Context::is_gutenberg_preview() ) {
			if ( isset( $atts['template'] ) && $atts['template'] ) {
				$template = WPRM_Template_Manager::get_template_by_slug( $atts['template'] );
			} else {
				// Use default single recipe template.
				$template = WPRM_Template_Manager::get_template_by_type( 'roundup' );
				$atts['template'] = $template['slug'];
			}

			// Output style.
			if ( 'modern' === $template['mode'] ) {
				$output .= '<style type="text/css">' . WPRM_Template_Manager::get_template_css( $template ) . '</style>';
			} else {
				$output .= '<style type="text/css">' . WPRM_Assets::get_custom_css( 'recipe' ) . '</style>';
			}

			// Calculate and store the position of this roundup item block for counter display.
			// This ensures the counter shows the correct number in the backend editor.
			self::calculate_roundup_block_position( $atts );
		}

		$output .= WPRM_Recipe_Roundup::shortcode( $atts );

		return $output;
	}

	/**
	 * Calculate and store the position of a roundup item block in the post content.
	 * This is used to display the correct counter number in the backend editor.
	 *
	 * @since	10.0.0
	 * @param	array $atts Block attributes.
	 */
	private static function calculate_roundup_block_position( $atts ) {
		// Initialize static variables to track block positions.
		static $position_cache = null;
		static $current_position = 0;

		// Get post content if we haven't cached positions yet.
		if ( null === $position_cache ) {
			$position_cache = array();
			$current_position = 0;

			// Try to get post content from various sources.
			$post_content = '';
			$post_id = 0;
			
			// Try to get from global post.
			if ( isset( $GLOBALS['post'] ) && $GLOBALS['post'] ) {
				$post_id = $GLOBALS['post']->ID;
				$post_content = $GLOBALS['post']->post_content;
			}
			
			// Try to get from request parameters (REST API).
			if ( empty( $post_content ) ) {
				// Check various possible parameter names.
				$possible_params = array( 'post_id', 'postId', 'context[postId]' );
				foreach ( $possible_params as $param ) {
					if ( isset( $_REQUEST[ $param ] ) ) {
						$post_id = intval( $_REQUEST[ $param ] );
						break;
					}
				}
				
				// Also check JSON body for REST API requests.
				if ( ! $post_id && ! empty( $_SERVER['REQUEST_METHOD'] ) && 'POST' === $_SERVER['REQUEST_METHOD'] ) {
					$json_input = file_get_contents( 'php://input' );
					if ( ! empty( $json_input ) ) {
						$json_data = json_decode( $json_input, true );
						if ( $json_data ) {
							if ( isset( $json_data['post_id'] ) ) {
								$post_id = intval( $json_data['post_id'] );
							} elseif ( isset( $json_data['context']['postId'] ) ) {
								$post_id = intval( $json_data['context']['postId'] );
							}
						}
					}
				}
				
				if ( $post_id ) {
					$post = get_post( $post_id );
					if ( $post ) {
						$post_content = $post->post_content;
					}
				}
			}

			// Parse blocks to find all roundup item blocks.
			if ( ! empty( $post_content ) && function_exists( 'parse_blocks' ) ) {
				$blocks = parse_blocks( $post_content );
				if ( ! empty( $blocks ) ) {
					self::extract_roundup_blocks( $blocks, $position_cache );
				}
			}
		}

		// Create a unique identifier for this block based on its attributes.
		// Use a combination of id, link, and other distinguishing attributes.
		$block_key = '';
		if ( ! empty( $atts['id'] ) ) {
			$block_key = 'id_' . $atts['id'];
		} elseif ( ! empty( $atts['link'] ) ) {
			$block_key = 'link_' . md5( $atts['link'] );
		} else {
			// Fallback: use a hash of all attributes.
			$block_key = 'hash_' . md5( serialize( $atts ) );
		}

		// If we have cached positions, use them.
		if ( isset( $position_cache[ $block_key ] ) ) {
			$GLOBALS['wprm_roundup_block_position'] = $position_cache[ $block_key ];
			return;
		}

		// Otherwise, increment position counter (for blocks not found in cache).
		$current_position++;
		$GLOBALS['wprm_roundup_block_position'] = $current_position;
	}

	/**
	 * Recursively extract roundup item blocks from parsed blocks.
	 *
	 * @since	10.0.0
	 * @param	array $blocks Parsed block list.
	 * @param	array $position_cache Reference to position cache array.
	 */
	private static function extract_roundup_blocks( $blocks, &$position_cache ) {
		$position = 0;

		foreach ( $blocks as $block ) {
			if ( ! is_array( $block ) ) {
				continue;
			}

			// Check if this is a roundup item block.
			if ( isset( $block['blockName'] ) && 'wp-recipe-maker/recipe-roundup-item' === $block['blockName'] ) {
				$position++;
				$attrs = isset( $block['attrs'] ) ? $block['attrs'] : array();

				// Create a unique identifier for this block.
				$block_key = '';
				if ( ! empty( $attrs['id'] ) ) {
					$block_key = 'id_' . $attrs['id'];
				} elseif ( ! empty( $attrs['link'] ) ) {
					$block_key = 'link_' . md5( $attrs['link'] );
				} else {
					$block_key = 'hash_' . md5( serialize( $attrs ) );
				}

				// Store position for this block.
				$position_cache[ $block_key ] = $position;
			}

			// Recursively check inner blocks.
			if ( ! empty( $block['innerBlocks'] ) && is_array( $block['innerBlocks'] ) ) {
				self::extract_roundup_blocks( $block['innerBlocks'], $position_cache );
			}
		}
	}

	/**
	 * Render the recipe snippet block.
	 *
	 * @since	6.9.0
	 * @param	mixed $atts Block attributes.
	 */
	public static function render_recipe_snippet_block( $atts ) {
		$atts = self::parse_atts( $atts );
		$output = '';

		// Only do this for the Gutenberg Preview.
		if ( WPRM_Context::is_gutenberg_preview() ) {
			if ( isset( $atts['template'] ) && $atts['template'] ) {
				$template = WPRM_Template_Manager::get_template_by_slug( $atts['template'] );
			} else {
				// Use default single recipe template.
				$template = WPRM_Template_Manager::get_template_by_type( 'snippet' );
				$atts['template'] = $template['slug'];
			}

			// Output style.
			if ( 'modern' === $template['mode'] ) {
				$output .= '<style type="text/css">' . WPRM_Template_Manager::get_template_css( $template ) . '</style>';
			} else {
				$output .= '<style type="text/css">' . WPRM_Assets::get_custom_css( 'recipe' ) . '</style>';
			}
		}

		$output .= WPRM_Shortcode_Snippets::recipe_snippet_shortcode( $atts );

		return $output;
	}

	/**
	 * Render the recipe part block.
	 *
	 * @since	6.9.0
	 * @param	mixed $atts Block attributes.
	 */
	public static function render_recipe_part_block( $atts ) {
		$atts = self::parse_atts( $atts );
		$output = '';

		$shortcode_tag = 'wprm-' . $atts['part'];

		if ( shortcode_exists( $shortcode_tag ) ) {
			$output .= do_shortcode( '[' . $shortcode_tag . ' id="' . $atts['id'] . '"]' );
		}

		return $output;
	}

	/**
	 * Render the nutrition label block.
	 *
	 * @since	3.1.2
	 * @param	mixed $atts Block attributes.
	 */
	public static function render_nutrition_label_block( $atts ) {
		$atts = self::parse_atts( $atts );
		return WPRM_SC_Nutrition_Label::shortcode( $atts );
	}

	/**
	 * Render the jump to recipe block.
	 *
	 * @since	3.1.2
	 * @param	mixed $atts Block attributes.
	 */
	public static function render_jump_to_recipe_block( $atts ) {
		$atts = self::parse_atts( $atts );
		return WPRM_SC_Jump::shortcode( $atts );
	}

	/**
	 * Render the jump to video block.
	 *
	 * @since	3.2.0
	 * @param	mixed $atts Block attributes.
	 */
	public static function render_jump_to_video_block( $atts ) {
		$atts = self::parse_atts( $atts );
		return WPRM_SC_Jump_Video::shortcode( $atts );
	}

	/**
	 * Render the print recipe block.
	 *
	 * @since	3.1.2
	 * @param	mixed $atts Block attributes.
	 */
	public static function render_print_recipe_block( $atts ) {
		$atts = self::parse_atts( $atts );
		return WPRM_SC_Print::shortcode( $atts );
	}
}

WPRM_Blocks::init();