Source: includes/renderers/class-sensei-renderer-single-post.php

<?php

if ( ! defined( 'ABSPATH' ) ) {
	exit;
} // Exit if accessed directly

/**
 * Renders a single Sensei post of any type based on the given ID. The rendered
 * result is meant to be displayed on the frontend, and may be used by
 * shortcodes or other rendering code.
 *
 * @author Automattic
 *
 * @since 1.12.0
 */
class Sensei_Renderer_Single_Post implements Sensei_Renderer_Interface {

	/**
	 * @var int $post_id The ID of the post to render.
	 */
	private $post_id;

	/**
	 * @var string $template The filename of the template to render.
	 */
	private $template;

	/**
	 * @var bool $show_pagination Whether or not to render pagination links.
	 */
	private $show_pagination;

	/**
	 * @var WP_Query $post_query The query for the post.
	 */
	protected $post_query;

	/**
	 * @var WP_Post $global_post_ref Backup of the global $post variable.
	 */
	protected $global_post_ref;

	/**
	 * @var WP_Query $global_wp_query_ref Backup of the global $wp_query variable.
	 */
	protected $global_wp_query_ref;

	/**
	 * @var WP_Query $global_wp_the_query_ref Backup of the global $wp_the_query variable.
	 */
	protected $global_wp_the_query_ref;

	/**
	 * @var array $global_pages_ref Backup of the global $pages variable.
	 */
	protected $global_pages_ref;

	/**
	 * Setup the renderer object
	 *
	 * @since 1.12.0
	 *
	 * @param int    $post_id  The post ID.
	 * @param string $template The template to use for rendering the post.
	 * @param array  $options  {
	 *   @type bool show_pagination Whether to show Sensei's pagination in the rendered output.
	 * }
	 */
	public function __construct( $post_id, $template, $options = array() ) {
		$this->post_id         = $post_id;
		$this->template        = $template;
		$this->show_pagination = isset( $options['show_pagination'] ) ? $options['show_pagination'] : false;
		$this->setup_post_query();
	}

	/**
	 * Render and return the content. This will use the given template, and
	 * will use an overridden version if it exists.
	 *
	 * @return string The rendered output.
	 */
	public function render() {
		$this->backup_global_vars();
		$this->set_global_vars();

		// Remove the header and footer.
		add_filter( 'sensei_show_main_footer', '__return_false' );
		add_filter( 'sensei_show_main_header', '__return_false' );

		// We'll make the assumption that the theme will display the title.
		add_filter( 'the_title', array( $this, 'hide_the_title' ), 100, 2 );

		// Capture output.
		ob_start();

		// Even though the header is not being displayed, the action hooked to it still needs to fire. Remove default wrapper.
		remove_action( 'sensei_before_main_content', array( Sensei()->frontend, 'sensei_output_content_wrapper' ) );

		/**
		 * Fires before the main content is output.
		 *
		 * @hook sensei_before_main_content
		 */
		do_action( 'sensei_before_main_content' );

		// Render the template.
		Sensei_Templates::get_template( $this->template );

		// Reset all filters.
		remove_filter( 'sensei_show_main_footer', '__return_false' );
		remove_filter( 'sensei_show_main_header', '__return_false' );
		remove_filter( 'the_title', array( $this, 'hide_the_title' ), 100 );

		// Render pagination if needed.
		if ( $this->show_pagination ) {
			/**
			 * Fires when the pagination is displayed.
			 *
			 * @hook sensei_pagination
			 */
			do_action( 'sensei_pagination' );
		}
		$output = ob_get_clean();

		$this->reset_global_vars();

		return $output;
	}

	/**
	 * Create the posts query.
	 */
	private function setup_post_query() {
		global $page, $cpage;

		if ( empty( $this->post_id ) ) {
			return;
		}

		$args = array(
			'p'         => $this->post_id,
			'post_type' => get_post_type( $this->post_id ),
			'page'      => $page,
			'cpage'     => $cpage,
		);

		$this->post_query = new WP_Query( $args );
	}

	/**
	 * Backup the globals that we will be modifying. Set them back with
	 * `reset_global_vars`.
	 */
	private function backup_global_vars() {
		global $wp_query, $wp_the_query, $post, $pages;

		$this->global_post_ref         = $post;
		$this->global_wp_query_ref     = $wp_query;
		$this->global_wp_the_query_ref = $wp_the_query;
		$this->global_pages_ref        = $pages;
	}

	/**
	 * Set global variables to the currently requested post. This is used
	 * internally and should not be called from external code.
	 *
	 * @access private
	 */
	public function set_global_vars() {
		global $wp_query, $wp_the_query, $post, $pages;

		// phpcs:disable WordPress.WP.GlobalVariablesOverride.Prohibited -- Used to render single post.
		$post         = get_post( $this->post_id );
		$pages        = array( $post->post_content );
		$wp_query     = $this->post_query;
		$wp_the_query = $wp_query;
		// phpcs:enable WordPress.WP.GlobalVariablesOverride.Prohibited
	}

	/**
	 * Reset global variables to what they were before calling
	 * `backup_global_vars`.
	 */
	private function reset_global_vars() {
		global $wp_query, $wp_the_query, $post, $pages;

		// phpcs:disable WordPress.WP.GlobalVariablesOverride.Prohibited -- Restoring preexisting state.
		$wp_query     = $this->global_wp_query_ref;
		$wp_the_query = $this->global_wp_the_query_ref;
		$post         = $this->global_post_ref;
		$pages        = $this->global_pages_ref;
		// phpcs:enable WordPress.WP.GlobalVariablesOverride.Prohibited
	}

	/**
	 * Hide the title of this post on the page.
	 *
	 * @access private
	 *
	 * @param string $title   The incoming title.
	 * @param int    $post_id The incoming post ID.
	 *
	 * @return string Empty string if $post_id matches our post, $title otherwise.
	 */
	public function hide_the_title( $title, $post_id ) {
		if ( $this->post_id === $post_id ) {
			return '';
		}
		return $title;
	}
}