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-shortcode.php
<?php
/**
 * Handle the recipe shortcode.
 *
 * @link       https://bootstrapped.ventures
 * @since      1.0.0
 *
 * @package    WP_Recipe_Maker
 * @subpackage WP_Recipe_Maker/includes/public
 */

/**
 * Handle the recipe shortcode.
 *
 * @since      1.0.0
 * @package    WP_Recipe_Maker
 * @subpackage WP_Recipe_Maker/includes/public
 * @author     Brecht Vandersmissen <brecht@bootstrapped.ventures>
 */
class WPRM_Shortcode {
	/**
	 * Type of shortcode we're outputting.
	 *
	 * @since    5.10.0
	 * @access   private
	 * @var      array $shortcode_type Shortcode type we're currently outputting.
	 */
	private static $shortcode_type = array();

	/**
	 * Register actions and filters.
	 *
	 * @since    1.0.0
	 */
	public static function init() {
		add_shortcode( 'wprm-recipe', array( __CLASS__, 'recipe_shortcode' ) );

		add_action( 'admin_init', array( __CLASS__, 'register_rest_prepare' ) );

		add_filter( 'content_edit_pre', array( __CLASS__, 'replace_imported_shortcodes' ) );
		add_filter( 'the_content', array( __CLASS__, 'replace_tasty_shortcode' ), 1 );
		add_filter( 'elementor/frontend/builder_content_data', array( __CLASS__, 'replace_tasty_elementor_widget_filter' ), 10, 2 );
		add_filter( 'elementor/editor/localize_settings', array( __CLASS__, 'replace_tasty_elementor_widget_editor' ), 10, 1 );
		add_filter( 'the_content', array( __CLASS__, 'replace_ziplist_shortcode' ), 1 );

		add_filter( 'render_block', array( __CLASS__, 'replace_blocks' ), 10, 2 );

		add_filter( 'get_the_excerpt', array( __CLASS__, 'before_generating_excerpt' ), 9 );
		add_filter( 'get_the_excerpt', array( __CLASS__, 'after_generating_excerpt' ), 11 );

		add_action( 'init', array( __CLASS__, 'fallback_shortcodes' ), 99 );
	}

	/**
	 * Register REST prepare hooks.
	 *
	 * @since	9.4.0
	 */
	public static function register_rest_prepare() {
		$post_types = get_post_types();
		foreach ( $post_types as $key => $label ) {
			add_filter( 'rest_prepare_' . $key, array( __CLASS__, 'replace_imported_shortcodes_rest_api' ), 10, 3 );
		}
	}

	/**
	 * Set current shortcode type to "excerpt".
	 *
	 * @since    5.10.0
	 * @param	 mixed $excerpt Excerpt that is being output.
	 */
	public static function before_generating_excerpt( $excerpt ) {
		self::$shortcode_type[] = 'excerpt';
		return $excerpt;
	}
	/**
	 * Remove "excerpt" shortcode type again.
	 *
	 * @since    5.10.0
	 * @param	 mixed $excerpt Excerpt that is being output.
	 */
	public static function after_generating_excerpt( $excerpt ) {
		array_pop( self::$shortcode_type );
		return $excerpt;
	}

	/**
	 * Fallback shortcodes for recipe plugins that we imported from.
	 *
	 * @since    1.3.0
	 */
	public static function fallback_shortcodes() {
		if ( ! shortcode_exists( 'seo_recipe' ) ) {
			add_shortcode( 'seo_recipe', array( __CLASS__, 'recipe_shortcode_fallback' ) );
		}

		if ( ! shortcode_exists( 'tasty-recipe' ) ) {
			add_shortcode( 'tasty-recipe', array( __CLASS__, 'recipe_shortcode_fallback' ) );
		}

		if ( ! shortcode_exists( 'ultimate-recipe' ) ) {
			add_shortcode( 'ultimate-recipe', array( __CLASS__, 'recipe_shortcode_fallback' ) );
		}

		if ( ! shortcode_exists( 'cooked-recipe' ) ) {
			add_shortcode( 'cooked-recipe', array( __CLASS__, 'recipe_shortcode_fallback' ) );
		}

		// Recipes by Simmer.
		if ( ! shortcode_exists( 'recipe' ) ) {
			add_shortcode( 'recipe', array( __CLASS__, 'recipe_shortcode_fallback' ) );
		}

		if ( ! shortcode_exists( 'nutrition-label' ) ) {
			add_shortcode( 'nutrition-label', array( __CLASS__, 'remove_shortcode' ) );
			add_shortcode( 'ultimate-nutrition-label', array( __CLASS__, 'remove_shortcode' ) );
		}

		if ( ! shortcode_exists( 'recipe-timer' ) ) {
			add_shortcode( 'recipe-timer', array( __CLASS__, 'timer_shortcode' ) );
		}
	}

	/**
	 * Replace imported shortcode with ours.
	 *
	 * @since	2.1.0
	 * @param	mixed $content Content we want to filter before it gets passed along.
	 */
	public static function replace_imported_shortcodes( $content ) {
		$content = self::replace_wpultimaterecipe_shortcode( $content );
		$content = self::replace_tasty_shortcode( $content );
		$content = self::replace_ziplist_shortcode( $content );
		$content = self::replace_bigoven_shortcode( $content );
		$content = self::replace_wpzoom_shortcode( $content );

		return $content;
	}

	/**
	 * Replace imported shortcodes in the rest API.
	 *
	 * @since	9.4.0
	 * @param WP_REST_Response $response The response object.
	 * @param WP_Post          $post     Post object.
	 * @param WP_REST_Request  $request  Request object.
	 */
	public static function replace_imported_shortcodes_rest_api( $response, $post, $request ) {
		$params = $request->get_params();

		if ( isset( $params['context'] ) && 'edit' === $params['context'] ) {
			if ( isset( $response->data['content']['raw'] ) ) {
				$response->data['content']['raw'] = self::replace_imported_shortcodes( $response->data['content']['raw'] );
			}
		}
		return $response;
	}

	/**
	 * Replace WP Ultimate Recipe shortcode with ours.
	 *
	 * @since    1.3.0
	 * @param		 mixed $content Content we want to filter before it gets passed along.
	 */
	public static function replace_wpultimaterecipe_shortcode( $content ) {
		preg_match_all( "/\[ultimate-recipe\s.*?id='?\"?(\d+).*?]/im", $content, $matches );
		foreach ( $matches[0] as $key => $match ) {
			if ( WPRM_POST_TYPE === get_post_type( $matches[1][ $key ] ) ) {
				$content = str_replace( $match, '[wprm-recipe id="' . $matches[1][ $key ] . '"]', $content );
			}
		}

		return $content;
	}

	/**
	 * Filter wrapper for Elementor widget replacement with cache clearing.
	 *
	 * @since    1.23.0
	 * @param	 array $data Elementor builder content data.
	 * @param	 int   $post_id Post ID.
	 */
	public static function replace_tasty_elementor_widget_filter( $data, $post_id ) {
		$result = self::replace_tasty_elementor_widget( $data, $post_id );
		
		// Clear Elementor cache if any widgets were replaced
		if ( $result['widget_replaced'] ) {
			// Clear Elementor element cache for this post
			delete_post_meta( $post_id, '_elementor_element_cache' );
			
			// Also clear the global Elementor cache if available
			if ( class_exists( 'Elementor\Plugin' ) && isset( \Elementor\Plugin::$instance->files_manager ) ) {
				\Elementor\Plugin::$instance->files_manager->clear_cache();
			}
		}
		
		return $result['data'];
	}

	/**
	 * Replace Tasty Recipes Elementor widget in editor.
	 *
	 * @since    1.23.0
	 * @param	 array $settings Editor settings data.
	 */
	public static function replace_tasty_elementor_widget_editor( $settings ) {
		// Check if we're in the editor and have post data
		if ( ! isset( $settings['initial_document']['elements'] ) ) {
			return $settings;
		}

		$post_id = isset( $settings['initial_document']['id'] ) ? $settings['initial_document']['id'] : 0;
		
		if ( ! $post_id ) {
			return $settings;
		}

		$result = self::replace_tasty_elementor_widget( $settings['initial_document']['elements'], $post_id );
		
		if ( $result['widget_replaced'] ) {
			$settings['initial_document']['elements'] = $result['data'];
		}

		return $settings;
	}

	/**
	 * Replace Tasty Recipes Elementor widget with ours.
	 *
	 * @since    1.23.0
	 * @param	 array $data Elementor builder content data.
	 * @param	 int   $post_id Post ID.
	 */
	public static function replace_tasty_elementor_widget( $data, $post_id ) {
		if ( ! is_array( $data ) ) {
			return $data;
		}

		$widget_replaced = false;

		foreach ( $data as $key => $element ) {
			if ( isset( $element['widgetType'] ) && 'tasty-recipe' === $element['widgetType'] ) {
				$id = isset( $element['settings']['recipe_id'] ) ? intval( $element['settings']['recipe_id'] ) : false;

				if ( $id ) {
					$wprm_recipe_id = self::get_wprm_recipe_id_from_tasty_reference( $id );

					if ( $wprm_recipe_id ) {
						$data[ $key ] = self::convert_elementor_element_to_wprm_widget( $data[ $key ], $wprm_recipe_id );
						unset( $data[ $key ]['settings']['recipe_id'] );
						$widget_replaced = true;
					}
				}
			} elseif ( isset( $element['widgetType'] ) && 'text-editor' === $element['widgetType'] && isset( $element['settings']['editor'] ) ) {
				$widget_id = self::get_wprm_recipe_id_from_single_tasty_shortcode( $element['settings']['editor'] );

				if ( $widget_id ) {
					$data[ $key ] = self::convert_elementor_element_to_wprm_widget( $data[ $key ], $widget_id );
					$widget_replaced = true;
				} else {
					$original_content = $element['settings']['editor'];
					$replaced_content = self::replace_tasty_shortcode( $original_content );

					if ( $replaced_content !== $original_content ) {
						$data[ $key ]['settings']['editor'] = $replaced_content;
						$widget_replaced = true;
					}
				}
			} elseif ( isset( $element['widgetType'] ) && 'shortcode' === $element['widgetType'] && isset( $element['settings']['shortcode'] ) ) {
				$widget_id = self::get_wprm_recipe_id_from_single_tasty_shortcode( $element['settings']['shortcode'] );

				if ( $widget_id ) {
					$data[ $key ] = self::convert_elementor_element_to_wprm_widget( $data[ $key ], $widget_id );
					$widget_replaced = true;
				} else {
					$original_shortcode = $element['settings']['shortcode'];
					$replaced_shortcode = self::replace_tasty_shortcode( $original_shortcode );

					if ( $replaced_shortcode !== $original_shortcode ) {
						$data[ $key ]['settings']['shortcode'] = $replaced_shortcode;
						$widget_replaced = true;
					}
				}
			}

			// Recursively process nested elements
			if ( isset( $element['elements'] ) && is_array( $element['elements'] ) ) {
				$nested_result = self::replace_tasty_elementor_widget( $element['elements'], $post_id );
				$data[ $key ]['elements'] = $nested_result['data'];
				if ( $nested_result['widget_replaced'] ) {
					$widget_replaced = true;
				}
			}
		}

		return array(
			'data' => $data,
			'widget_replaced' => $widget_replaced,
		);
	}

	/**
	 * Replace Tasty Recipes shortcode with ours.
	 *
	 * @since    1.23.0
	 * @param	 mixed $content Content we want to filter before it gets passed along.
	 */
	public static function replace_tasty_shortcode( $content ) {
		preg_match_all( "/\[tasty-recipe\s.*?id='?\"?(\d+).*?]/im", $content, $matches );
		foreach ( $matches[0] as $key => $match ) {
			if ( WPRM_POST_TYPE === get_post_type( $matches[1][ $key ] ) ) {
				$content = str_replace( $match, '[wprm-recipe id="' . $matches[1][ $key ] . '"]', $content );
			}
		}

		if ( function_exists( 'parse_blocks' ) ) {
			preg_match_all( '/<!--(.*?)-->/im', $content, $matches);

			foreach ( $matches[0] as $key => $match ) {
				$blocks = parse_blocks( $match );

				if ( $blocks && 1 === count( $blocks ) && $blocks[0] && 'wp-tasty/tasty-recipe' === $blocks[0]['blockName'] ) {
					$id = $blocks[0]['attrs']['id'];

					if ( $id && WPRM_POST_TYPE === get_post_type( $id ) ) {
						$wprm_block = self::get_block_replacement( $id );
						$content = str_replace( $match, $wprm_block, $content );
					}
				}
			}			
		}

		return $content;
	}

	/**
	 * Try to get a WPRM recipe ID from a piece of content that only contains a Tasty shortcode.
	 *
	 * @since    10.0.0
	 * @param    string $content Content to inspect.
	 */
	private static function get_wprm_recipe_id_from_single_tasty_shortcode( $content ) {
		$shortcode_id = self::get_single_tasty_shortcode_id_from_content( $content );

		if ( ! $shortcode_id ) {
			return false;
		}

		return self::get_wprm_recipe_id_from_tasty_reference( $shortcode_id );
	}

	/**
	 * Extract a Tasty shortcode ID when the content only contains that shortcode.
	 *
	 * @since    10.0.0
	 * @param    string $content Content to inspect.
	 */
	private static function get_single_tasty_shortcode_id_from_content( $content ) {
		if ( ! is_string( $content ) ) {
			return false;
		}

		$sanitized = html_entity_decode( trim( $content ) );
		$sanitized = trim( strip_tags( $sanitized ) );

		if ( '' === $sanitized ) {
			return false;
		}

		if ( preg_match( '/^\[tasty-recipe\s+.*?id\s*=\s*[\'"]?(\d+).*?\]\s*$/i', $sanitized, $match ) ) {
			return intval( $match[1] );
		}

		return false;
	}

	/**
	 * Determine the WPRM recipe ID that should replace a Tasty reference.
	 *
	 * @since    10.0.0
	 * @param    int $id Tasty recipe ID.
	 */
	private static function get_wprm_recipe_id_from_tasty_reference( $id ) {
		if ( ! $id ) {
			return false;
		}

		if ( WPRM_POST_TYPE === get_post_type( $id ) ) {
			return $id;
		}

		$wprm_imported_to = get_post_meta( $id, 'wprm_imported_to', true );

		if ( $wprm_imported_to && WPRM_POST_TYPE === get_post_type( $wprm_imported_to ) ) {
			return $wprm_imported_to;
		}

		return false;
	}

	/**
	 * Convert an Elementor widget array to the WPRM widget with the provided recipe ID.
	 *
	 * @since    10.0.0
	 * @param    array $element Elementor widget data.
	 * @param    int   $recipe_id WPRM recipe ID.
	 */
	private static function convert_elementor_element_to_wprm_widget( $element, $recipe_id ) {
		$element['widgetType'] = 'wprm-recipe';

		if ( ! isset( $element['settings'] ) || ! is_array( $element['settings'] ) ) {
			$element['settings'] = array();
		}

		$element['settings']['wprm_recipe_id'] = $recipe_id;

		unset( $element['settings']['editor'] );
		unset( $element['settings']['shortcode'] );

		return $element;
	}

	/**
	 * Replace ZipList shortcode with ours.
	 *
	 * @since    1.23.0
	 * @param	 mixed $content Content we want to filter before it gets passed along.
	 */
	public static function replace_ziplist_shortcode( $content ) {
		global $wpdb;
		$zl_table = $wpdb->prefix . 'amd_zlrecipe_recipes';

		preg_match_all( "/\[zrdn-recipe\s.*?id='?\"?(\d+).*?]/im", $content, $matches );
		foreach ( $matches[0] as $key => $match ) {
			$zl_id = intval( $matches[1][ $key ] );

			if ( $zl_id ) {
				$zl_recipe = $wpdb->get_row( $wpdb->prepare(
					"SELECT post_id FROM `%1s`
					WHERE recipe_id = %d",
					array(
						$zl_table,
						$zl_id,
					)
				) );
				$post_id = $zl_recipe ? $zl_recipe->post_id : false;

				if ( $post_id && WPRM_POST_TYPE === get_post_type( $post_id ) ) {
					$content = str_replace( $match, '[wprm-recipe id="' . $post_id . '"]', $content );
				}
			}
		}

		if ( function_exists( 'parse_blocks' ) ) {
			preg_match_all( '/<!--(.*?)-->/im', $content, $matches);

			foreach ( $matches[0] as $key => $match ) {
				$blocks = parse_blocks( $match );

				if ( $blocks && 1 === count( $blocks ) && $blocks[0] && 'zip-recipes/recipe-block' === $blocks[0]['blockName'] ) {
					$zl_id = intval( $blocks[0]['attrs']['id'] );

					if ( $zl_id ) {
						global $wpdb;
						$zl_recipe = $wpdb->get_row( $wpdb->prepare(
							"SELECT post_id FROM `%1s`
							WHERE recipe_id = %d",
							array(
								$zl_table,
								$zl_id,
							)
						) );
						
						$post_id = $zl_recipe ? $zl_recipe->post_id : false;
		
						if ( $post_id && WPRM_POST_TYPE === get_post_type( $post_id ) ) {
							$wprm_block = self::get_block_replacement( $post_id );
							$content = str_replace( $match, $wprm_block, $content );
						}
					}
				}
			}			
		}

		return $content;
	}

	/**
	 * Replace BigOven shortcode with ours.
	 *
	 * @since    1.7.0
	 * @param	 mixed $content Content we want to filter before it gets passed along.
	 */
	public static function replace_bigoven_shortcode( $content ) {
		preg_match_all( "/\[seo_recipe\s.*?id='?\"?(\d+).*?]/im", $content, $matches );
		foreach ( $matches[0] as $key => $match ) {
			if ( WPRM_POST_TYPE === get_post_type( $matches[1][ $key ] ) ) {
				$content = str_replace( $match, '[wprm-recipe id="' . $matches[1][ $key ] . '"]', $content );
			}
		}

		return $content;
	}

	/**
	 * Replace WP Zoom shortcode with ours.
	 *
	 * @since    8.9.0
	 * @param	 mixed $content Content we want to filter before it gets passed along.
	 */
	public static function replace_wpzoom_shortcode( $content ) {
		preg_match_all( "/\[wpzoom_rcb_post\s.*?id='?\"?(\d+).*?]/im", $content, $matches );
		foreach ( $matches[0] as $key => $match ) {
			$id = $matches[1][ $key ];

			$wprm_imported_to = get_post_meta( $id, 'wprm_imported_to', true );
			if ( $wprm_imported_to ) {
				$content = str_replace( $match, '[wprm-recipe id="' . $wprm_imported_to . '"]', $content );
			}
		}

		if ( function_exists( 'parse_blocks' ) ) {
			preg_match_all( '/<!--(.*?)-->/im', $content, $matches);

			foreach ( $matches[0] as $key => $match ) {
				$blocks = parse_blocks( $match );

				if ( $blocks && 1 === count( $blocks ) && $blocks[0] && 'wpzoom-recipe-card/recipe-block-from-posts' === $blocks[0]['blockName'] ) {
					$id = $blocks[0]['attrs']['postId'];

					if ( $id ) {
						$wprm_imported_to = get_post_meta( $id, 'wprm_imported_to', true );

						if ( $wprm_imported_to ) {
							$wprm_block = self::get_block_replacement( $wprm_imported_to );
							$content = str_replace( $match, $wprm_block, $content );
						}
					}
				}
			}			
		}

		return $content;
	}

	/**
	 * Get block replacement to use for imported recipes.
	 *
	 * @since    9.4.0
	 * @param	 int $recipe_id Recipe ID to get the block for.
	 */
	public static function get_block_replacement( $recipe_id ) {
		$block = '';

		$recipe_id = intval( $recipe_id ); // Make sure it's an integer.
		$updated = time() * 1000; // Match JavaScript time in milliseconds.

		$block .= '<!-- wp:wp-recipe-maker/recipe {"id":' . $recipe_id . ',"updated":' . $updated .'} -->';
		$block .= '[wprm-recipe id="' . $recipe_id . '"]';
		$block .= '<!-- /wp:wp-recipe-maker/recipe -->';

		return $block;
	}

	/**
	 * Replace blocks by other recipe plugins with ours, if they have been imported.
	 *
	 * @since    8.0.0
	 * @param	 mixed $content Content we want to filter before it gets passed along.
	 * @param	 mixed $block 	Block we're currently filtering.
	 */
	public static function replace_blocks( $content, $block ) {
		// Tasty Recipes.
		if ( 'wp-tasty/tasty-recipe' === $block['blockName'] ) {
			$id = isset( $block['attrs']['id'] ) ? intval( $block['attrs']['id'] ) : false;

			if ( $id && WPRM_POST_TYPE === get_post_type( $id ) ) {
				$content = do_shortcode( '[wprm-recipe id="' . $id . '"]' );
			}
		}
		
		// Zip Recipes.
		if ( 'zip-recipes/recipe-block' === $block['blockName'] ) {
			$zl_id = isset( $block['attrs']['id'] ) ? intval( $block['attrs']['id'] ) : false;

			if ( $zl_id ) {
				global $wpdb;

				$zl_table = $wpdb->prefix . 'amd_zlrecipe_recipes';
				$zl_recipe = $wpdb->get_row( $wpdb->prepare(
					"SELECT post_id FROM `%1s`
					WHERE recipe_id = %d",
					array(
						$zl_table,
						$zl_id,
					)
				) );

				$post_id = $zl_recipe ? $zl_recipe->post_id : false;

				if ( $post_id && WPRM_POST_TYPE === get_post_type( $post_id ) ) {
					$content = do_shortcode( '[wprm-recipe id="' . $post_id . '"]' );
				}
			}
		}

		// WP Zoom.
		if ( 'wpzoom-recipe-card/recipe-block-from-posts' === $block['blockName'] ) {
			$wpzoom_id = isset( $block['attrs']['postId'] ) ? intval( $block['attrs']['postId'] ) : false;

			if ( $wpzoom_id ) {
				$wprm_imported_to = get_post_meta( $wpzoom_id, 'wprm_imported_to', true );

				if ( $wprm_imported_to ) {
					$content = do_shortcode( '[wprm-recipe id="' . $wprm_imported_to . '"]' );
				}
			}
		}
		
		return $content;
	}

	/**
	 * To be used for shortcodes we want to (temporarily) remove from the content.
	 *
	 * @since    1.3.0
	 */
	public static function remove_shortcode() {
		return '';
	}

	/**
	 * Output for a fallback shortcode from another plugin.
	 *
	 * @since    4.2.1
	 * @param    array $atts Options passed along with the shortcode.
	 */
	public static function recipe_shortcode_fallback( $atts ) {
		$atts = shortcode_atts( array(
			'id' => false,
			'template' => '',
		), $atts );

		// Prevent outputting random recipe.
		if ( false === $atts['id'] ) {
			return '';
		} else {
			if ( WPRM_POST_TYPE !== get_post_type( $atts['id'] ) ) {
				// Find recipe in content.
				$recipe_ids = WPRM_Recipe_Manager::get_recipe_ids_from_post( $atts['id'] );

				if ( $recipe_ids && isset( $recipe_ids[0] ) ) {
					$atts['id'] = $recipe_ids[0];
				} else {
					// WP Ultimate Recipe shortcode migrated?
					$migrated_id = get_post_meta( $atts['id'], '_wpurp_wprm_migrated', true );
					if ( $migrated_id ) {
						$atts['id'] = intval( $migrated_id );
					}
				}
			}

			return self::recipe_shortcode( $atts );
		}
	}

	/**
	 * Output for the recipe shortcode.
	 *
	 * @since    1.0.0
	 * @param		 array $atts Options passed along with the shortcode.
	 */
	public static function recipe_shortcode( $atts ) {
		$atts = shortcode_atts( array(
			'id' => 'random',
			'preview' => '',
			'align' => '',
			'template' => '',
		), $atts, 'wprm_recipe' );

		$recipe_template = trim( $atts['template'] );
		$recipe = false;

		// Check if we're previewing a recipe.
		if ( $atts['preview'] ) {
			$recipe_id = $atts['preview'];
			$recipe = WPRM_Preview::get_preview_recipe( $atts['preview'] );
		} else {
			// Not previewing, get recipe from ID.
			if ( 'random' === $atts['id'] ) {
				$posts = get_posts( array(
					'post_type' => WPRM_POST_TYPE,
					'posts_per_page' => 1,
					'orderby' => 'rand',
				) );

				$recipe_id = isset( $posts[0] ) ? $posts[0]->ID : 0;
			} elseif ( 'latest' === $atts['id'] ) {
				$posts = get_posts(array(
					'post_type' => WPRM_POST_TYPE,
					'posts_per_page' => 1,
				));

				$recipe_id = isset( $posts[0] ) ? $posts[0]->ID : 0;
			} elseif ( 'demo' === $atts['id'] ) {
				$recipe_id = 'demo';
			} else {
				$recipe_id = intval( $atts['id'] );
			}

			$recipe = WPRM_Recipe_Manager::get_recipe( $recipe_id );
		}

		if ( $recipe ) {			
			WPRM_Assets::load();

			// Output recipe data on page.
			add_filter( 'wprm_recipes_on_page', function( $recipes ) use ( $recipe_id ) {
				$recipes[] = $recipe_id;
				return $recipes;
			} );

			// Check type of recipe we need to output.
			if ( 0 < count( self::$shortcode_type ) ) {
				$type = end( self::$shortcode_type );
			} else {
				$type = 'single';

				if ( is_feed() ) {
					$type = 'feed';
				} elseif ( function_exists( 'is_amp_endpoint' ) && is_amp_endpoint() ) {
					$type = 'amp';
					$recipe_template = ''; // Force default AMP template.
				} elseif ( isset( $GLOBALS['wp']->query_vars['rest_route'] ) && '/wp/v2/posts' === substr( $GLOBALS['wp']->query_vars['rest_route'], 0, 12 ) ) {
					$type = 'single'; // Use single template when accessing post through REST API.
				} elseif ( is_admin() ) {
					$type = 'single';
				} elseif ( is_front_page() || ! is_singular() || ! is_main_query() ) {
					$type = 'archive';
				}
			}

			// Early return recipe summary if this is for an excerpt.
			if ( 'excerpt' === $type ) {
				return $recipe->summary();
			}

			// Output full snippet or recipe template.
			$align_class = '';
			if ( isset( $atts['align'] ) && $atts['align'] ) {
				$align_class = ' align' . esc_attr( $atts['align'] );
			}

			if ( $recipe_template && 'snippet-' === substr( $recipe_template, 0, 8 ) ) {
				$output = '<div id="wprm-recipe-snippet-container-' . esc_attr( $recipe->id() ) . '" class="wprm-recipe-snippet-container' . esc_attr( $align_class ) . '" data-recipe-id="' . esc_attr( $recipe->id() ) . '">';
			} else {
				$output = '<div id="wprm-recipe-container-' . esc_attr( $recipe->id() ) . '" class="wprm-recipe-container' . esc_attr( $align_class ) . '" data-recipe-id="' . esc_attr( $recipe->id() ) . '" data-servings="' . esc_attr( $recipe->servings() ) . '">';
			}

			if ( 'amp' === $type || 'single' === $type ) {
				if ( 'recipe' === WPRM_Settings::get( 'metadata_location' ) && ! WPRM_Metadata::use_yoast_seo_integration() && WPRM_Metadata::should_output_metadata_for( $recipe->id() ) ) {
					$metadata_output = WPRM_Metadata::get_metadata_output( $recipe );

					if ( $metadata_output ) {
						$output .= $metadata_output;
						WPRM_Metadata::outputted_metadata_for( $recipe->id() );
					}
				}
			}

			$output .= WPRM_Template_Manager::get_template( $recipe, $type, $recipe_template );
			$output .= '</div>';
			
			return apply_filters( 'wprm_recipe_shortcode_output', $output, $recipe, $type, $recipe_template );
		} else {
			return '';
		}
	}
}

WPRM_Shortcode::init();