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

/**
 * Handle the recipe roundup feature.
 *
 * @since      4.3.0
 * @package    WP_Recipe_Maker
 * @subpackage WP_Recipe_Maker/includes/public
 * @author     Brecht Vandersmissen <brecht@bootstrapped.ventures>
 */
class WPRM_Recipe_Roundup {

	/**
	 * Roundup overrides.
	 *
	 * @since	8.0.0
	 * @access	private
	 * @var		array $roundup_overrides Overrides to use for recipe values in the roundup.
	 */
	private static $roundup_overrides = array();

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

		add_action( 'init', array( __CLASS__, 'meta_fields_in_rest' ) );
		add_action( 'wp_head', array( __CLASS__, 'metadata_in_head' ), 2 );
	}

	/**
	 * Output itemlist metadata in the HTML head.
	 *
	 * @since    4.3.0
	 */
	public static function metadata_in_head() {
		if ( is_singular() && ( ! WPRM_Metadata::has_outputted_metadata() || false === WPRM_Settings::get( 'recipe_roundup_no_metadata_when_recipe' ) ) ) {
			$post = get_post();
			$recipe_ids = self::get_items_from_content( $post->post_content );

			// Need at least 2 items before outputting a list.
			if ( 1 < count( $recipe_ids ) ) {
				$name = get_post_meta( get_the_ID(), 'wprm-recipe-roundup-name', true );
				$description = get_post_meta( get_the_ID(), 'wprm-recipe-roundup-description', true );

				self::output_itemlist_metadata( get_permalink( $post ), $name, $description, $recipe_ids );
			}
		}

		// Archive pages.
		if ( is_archive() && WPRM_Settings::get( 'itemlist_metadata_archive_pages' ) ) {
			self::list_metadata_for_archive_pages();	
		}
	}

	/**
	 * Output list metadata for archive pages.
	 *
	 * @since	8.0.0
	 */
	public static function list_metadata_for_archive_pages() {
		global $wp_query;

		$recipe_ids = array();
		foreach ( $wp_query->posts as $post ) {
			if ( WPRM_POST_TYPE === $post->post_type ) {
				$recipe_ids[] = $post->ID;
			} else if ( 'all' === WPRM_Settings::get( 'itemlist_metadata_archive_pages_post_types' ) ) {
				$recipe_ids_in_post = WPRM_Recipe_Manager::get_recipe_ids_from_post( $post->ID );

				if ( $recipe_ids_in_post ) {
					if ( ! WPRM_Settings::get( 'metadata_only_show_for_first_recipe' ) ) {
						// Output metadata for all recipes.
						$recipe_ids = array_merge( $recipe_ids, $recipe_ids_in_post );
					} else {
						// Only add metadata for first food recipe on page.
						foreach ( $recipe_ids_in_post as $recipe_id_in_post ) {
							$recipe = WPRM_Recipe_Manager::get_recipe( $recipe_id_in_post );
	
							if ( $recipe && 'other' !== $recipe->type() ) {
								$recipe_ids[] = $recipe_id_in_post;
								break;
							}
						}
					}
				}
			}
		}

		if ( 1 < count( $recipe_ids ) ) {
			// TODO term name.
			$http_host = isset( $_SERVER['HTTP_HOST'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_HOST'] ) ) : '';
			$request_uri = isset( $_SERVER['REQUEST_URI'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : '';
			$url = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? "https" : "http") . '://' . $http_host . $request_uri;
			self::output_itemlist_metadata( $url, '', '', $recipe_ids );
		}
	}

	/**
	 * Output ItemList metadata for a set of recipe ids.
	 *
	 * @since	8.0.0
	 * @param	mixed $url			URL of the roundup page.
	 * @param	mixed $name			Name for the ItemList.
	 * @param	mixed $description	Description for the ItemList.
	 * @param	mixed $post_ids 	IDs of the posts to get the ItemList metadata for.
	 */
	public static function output_itemlist_metadata( $url, $name, $description, $post_ids ) {
		$metadata = array(
			'@context' => 'http://schema.org',
			'@type' => 'ItemList',
			'url' => $url,
			'itemListElement' => array(),
		);

		if ( $name ) {
			$metadata['name'] = wp_strip_all_tags( $name );
		}

		if ( $description ) {
			$metadata['description'] = wp_strip_all_tags( $description );
		}

		$item_list_counter = 0;
		foreach ( $post_ids as $post_id ) {
			$url = false;
			
			if ( WPRM_POST_TYPE === get_post_type( $post_id ) ) {
				$recipe = WPRM_Recipe_Manager::get_recipe( $post_id );

				if ( $recipe ) {
					$url = $recipe->permalink();
				}
			} else {
				$url = get_permalink( $post_id );
			}

			if ( $url ) {
				$item_list_counter++;
				$metadata['itemListElement'][] = array(
					'@type'    => 'ListItem',
					'position' => $item_list_counter,
					'url'      => $url,
				);
			}
		}

		$metadata['numberOfItems'] = $item_list_counter;

		if ( 1 < $item_list_counter ) {
			echo '<script type="application/ld+json">' . wp_json_encode( $metadata ) . '</script>';
		}
	}

	/**
	 * Get recipe roundup items from the content.
	 *
	 * @since    4.3.0
	 * @param    mixed $content Content to get the recipe roundup items from.
	 */
	public static function get_items_from_content( $content ) {
		$post_ids = array();

		$recipe_shortcodes = array();
		$pattern = get_shortcode_regex( array( 'wprm-recipe-roundup-item' ) );

		if ( preg_match_all( '/' . $pattern . '/s', $content, $matches ) && array_key_exists( 2, $matches ) ) {
			foreach ( $matches[2] as $key => $value ) {
				if ( 'wprm-recipe-roundup-item' === $value ) {
					$recipe_shortcodes[ $matches[0][ $key ] ] = shortcode_parse_atts( stripslashes( $matches[3][ $key ] ) );
				}
			}
		}

		foreach ( $recipe_shortcodes as $shortcode => $shortcode_options ) {
			$post_id = isset( $shortcode_options['id'] ) ? intval( $shortcode_options['id'] ) : 0;

			if ( $post_id ) {
				$post_ids[] = $post_id;
			}
		}

		return $post_ids;
	}

	/**
	 * Register the meta fields in the REST API.
	 *
	 * @since    4.3.0
	 */
	public static function meta_fields_in_rest() {
		register_meta( 'post', 'wprm-recipe-roundup-name', array( 'show_in_rest' => true, 'single' => true ) );
		register_meta( 'post', 'wprm-recipe-roundup-description', array( 'show_in_rest' => true, 'single' => true ) );
	}

	/**
	 * Output for the recipe roundup item shortcode.
	 *
	 * @since    4.3.0
	 * @param    array $atts Options passed along with the shortcode.
	 */
	public static function shortcode( $atts ) {
		$atts = shortcode_atts(
			array(
				'id' => false,
				'align' => '',
				'link' => '',
				'image' => '',
				'image_url' => '',
				'credit' => '',
				'summary' => '',
				'name' => '',
				'button' => '',
				'template' => '',
				'nofollow' => false,
				'newtab' => true,
			),
			$atts,
			'wprm_recipe_roundup_item'
		);

		$recipe = false;
		$recipe_template = trim( $atts['template'] );
		$recipe_id = intval( $atts['id'] );
		self::$roundup_overrides = array();

		if ( $recipe_id ) {
			$type = 'internal';
			$recipe = WPRM_Recipe_Manager::get_recipe( $recipe_id );

			if ( $atts['image'] && 0 < intval( $atts['image'] ) ) { self::$roundup_overrides['image_id'] = intval( $atts['image'] ); }
			if ( $atts['name'] ) 	{ self::$roundup_overrides['name'] = self::sanitize_roundup_text( $atts['name'] ); }
			if ( $atts['summary'] ) { self::$roundup_overrides['summary'] = self::sanitize_roundup_text( $atts['summary'], true ); }

			// If no recipe was found, maybe it was of a "post" type instead.
			if ( ! $recipe ) {
				$post = get_post( $recipe_id );

				if ( $post && WPRM_POST_TYPE !== $post->post_type ) {
					$type = 'post';

					$image = $atts['image'] && 0 < intval( $atts['image'] ) ? intval( $atts['image'] ) : get_post_thumbnail_id( $post );
					$name = self::sanitize_roundup_text( $atts['name'] );
					$summary = self::sanitize_roundup_text( $atts['summary'], true );

					$recipe_data = array(
						'type' => 'food',
						'parent_id' => true,
						'parent_url' => get_permalink( $post ),
						'permalink' => get_permalink( $post ),
						'post_status' => $post->post_status,
						'name' => $name ? $name : $post->post_title,
						'summary' => $summary ? $summary : '',
						'image_id' => $image,
						'parent_url_new_tab' => false,
						'parent_url_nofollow' => false,
						'parent_url_noopener' => false,
					);

					$recipe = new WPRM_Recipe_Shell( $recipe_data );
				}
			}

			// Only display published recipes/posts.
			if ( WPRM_Settings::get( 'recipe_roundup_published_only' ) ) {
				if ( $recipe && 'publish' !== $recipe->post_status() ) {
					// If not in Gutenberg preview, return empty shortcode.
					if ( ! WPRM_Context::is_gutenberg_preview() ) {
						return '';
					}
				}
			}
		} else {
			$type = 'external';
			$link = esc_url_raw( rawurldecode( $atts['link'] ) );
			$recipe_data = array(
				'type' => 'food',
				'parent_id' => true,
				'parent_url' => $link,
				'parent_external' => true,
				'permalink' => $link,
				'name' => self::sanitize_roundup_text( $atts['name'] ),
				'summary' => self::sanitize_roundup_text( $atts['summary'], true ),
				'parent_url_new_tab' => $atts['newtab'] ? true : false,
				'parent_url_nofollow' => $atts['nofollow'] ? true : false,
				'parent_url_noopener' => WPRM_Settings::get( 'recipe_roundup_external_noopener' ),
				'credit' => self::sanitize_roundup_text( $atts['credit'] ),
			);

			$image_id = intval( $atts['image'] );
			if ( -1 === $image_id ) {
				$recipe_data['image_id'] = 'url';
				$recipe_data['image_url'] = esc_url_raw( $atts['image_url'] );
			} else {
				$recipe_data['image_id'] = $image_id;
			}

			$recipe = new WPRM_Recipe_Shell( $recipe_data );
		}

		// Both internal and external.
		if ( $atts['button'] ) { self::$roundup_overrides['roundup_link_button_text'] = self::sanitize_roundup_text( $atts['button'] ); }

		if ( $recipe ) {
			$template = false;
			$template_slug = trim( $atts['template'] );

			if ( $template_slug ) {
				$template = WPRM_Template_Manager::get_template_by_slug( $template_slug );
			}

			if ( ! $template ) {
				$template = WPRM_Template_Manager::get_template_by_type( 'roundup', $recipe->type() );
			}

			if ( $template ) {
				// Add to used templates.
				WPRM_Template_Manager::add_used_template( $template );

				$align_class = '';
				if ( isset( $atts['align'] ) && $atts['align'] ) {
					$align_class = ' align' . esc_attr( $atts['align'] );
				}

				$output = '<div class="wprm-recipe wprm-recipe-roundup-item wprm-recipe-roundup-item-' . esc_attr( $recipe->id() ) . ' wprm-recipe-template-' . esc_attr( $template['slug'] ) . esc_attr( $align_class ) . '" data-servings="' . esc_attr( $recipe->servings() ). '">';

				// Add filters for overrides and immediately remove after doing shortcode.
				add_filter( 'wprm_recipe_roundup_link_text', array( __CLASS__, 'roundup_link_text_override' ) );
				if ( 'internal' === $type ) {
					add_filter( 'wprm_recipe_field', array( __CLASS__, 'recipe_field_overrides' ), 10, 2 );
					WPRM_Template_Shortcodes::set_current_recipe_id( $recipe->id() );
					$output .= do_shortcode( $template['html'] );
					WPRM_Template_Shortcodes::set_current_recipe_id( false );
					remove_filter( 'wprm_recipe_field', array( __CLASS__, 'recipe_field_overrides' ), 10, 2 );
				} else {
					WPRM_Template_Shortcodes::set_current_recipe_shell( $recipe );
					$output .= do_shortcode( $template['html'] );
					WPRM_Template_Shortcodes::set_current_recipe_shell( false );
				}
				remove_filter( 'wprm_recipe_roundup_link_text', array( __CLASS__, 'roundup_link_text_override' ) );

				$output .= '</div>';

				return $output;
			}
		}

		return '';
	}

	/**
	 * Maybe apply overrides to recipe fields.
	 *
	 * @since    8.0.0
	 * @param    mixed $output	Current recipe field output.
	 * @param    mixed $field	Current recipe field getting output.
	 */
	public static function recipe_field_overrides( $output, $field ) {
		foreach ( self::$roundup_overrides as $key => $value ) {
			if ( $value && $field === $key ) {
				return $value;
			}
		}

		return $output;
	}

	/**
	 * Maybe apply override to the roundup link text..
	 *
	 * @since    8.0.0
	 * @param    mixed $output	Current roundup link text.
	 */
	public static function roundup_link_text_override( $output ) {
		if ( isset( self::$roundup_overrides['roundup_link_button_text'] ) && self::$roundup_overrides['roundup_link_button_text'] ) {
			return self::$roundup_overrides['roundup_link_button_text'];
		}

		return $output;
	}

	/**
	 * Sanitize user-supplied text for roundup fields.
	 *
	 * @since	10.2.3
	 * @param	string $text Text to sanitize.
	 */
	private static function sanitize_roundup_text( $text, $multiline = false ) {
		if ( is_array( $text ) || is_object( $text ) ) {
			return '';
		}

		if ( ! is_string( $text ) ) {
			$text = (string) $text;
		}

		// Decode shortcode-provided values before sanitizing.
		$text = rawurldecode( $text );

		if ( $multiline ) {
			$text = str_replace( '%0A', '<br/>', $text );
		}

		return WPRM_Shortcode_Helper::sanitize_html( $text );
	}
}

WPRM_Recipe_Roundup::init();