*/ namespace RankMath\Schema; use RankMath\Helper; use RankMath\Traits\Hooker; use RankMath\Traits\Shortcode; use RankMath\Helpers\Str; use RankMath\Helpers\Param; defined( 'ABSPATH' ) || exit; /** * Snippet_Shortcode class. */ class Snippet_Shortcode { use Hooker, Shortcode; /** * Post object. * * @var object */ private $post; /** * Schema data. * * @var array */ private $schema; /** * The Constructor. */ public function __construct() { $this->add_shortcode( 'rank_math_rich_snippet', 'rich_snippet' ); $this->add_shortcode( 'rank_math_review_snippet', 'rich_snippet' ); if ( ! is_admin() ) { $this->filter( 'the_content', 'output_schema_in_content', 11 ); } if ( ! function_exists( 'register_block_type' ) ) { return; } register_block_type( 'rank-math/rich-snippet', [ 'render_callback' => [ $this, 'rich_snippet' ], 'attributes' => [ 'id' => [ 'default' => '', 'type' => 'string', ], 'post_id' => [ 'default' => '', 'type' => 'integer', ], ], ] ); } /** * Schema shortcode. * * @param array $atts Optional. Shortcode arguments - currently only 'show' * parameter, which is a comma-separated list of elements to show. * * @return string Shortcode output. */ public function rich_snippet( $atts ) { $atts = shortcode_atts( [ 'id' => false, 'post_id' => Param::get( 'post_id' ) ? Param::get( 'post_id' ) : get_the_ID(), 'className' => '', ], $atts, 'rank_math_rich_snippet' ); if ( 'edit' === Param::get( 'context' ) ) { rank_math()->variables->setup(); } $data = $this->get_schema_data( $atts['id'], $atts['post_id'] ); if ( empty( $data ) || empty( $data['schema'] ) ) { return esc_html__( 'No schema found.', 'rank-math' ); } $post = get_post( $data['post_id'] ); $schemas = ! empty( $atts['id'] ) ? [ $data['schema'] ] : $data['schema']; $html = ''; foreach ( $schemas as $schema ) { $schema = $this->replace_variables( $schema, $post ); $schema = $this->do_filter( 'schema/shortcode/filter_attributes', $schema, $atts ); /** * Change the Schema HTML output. * * @param string $unsigned HTML output. * @param array $schema Schema data. * @param WP_Post $post The post instance. * @param Snippet_Shortcode $this Snippet_Shortcode instance. */ $html .= $this->do_filter( 'snippet/html', $this->get_snippet_content( $schema, $post, $atts ), $schema, $post, $this ); } return $html; } /** * Get Snippet content. * * @param array $schema Schema to replace. * @param WP_Post $post Post schema attached to. * @param array $atts Optional. Shortcode arguments - currently only 'show' * parameter, which is a comma-separated list of elements to show. * * @return string Shortcode output. */ public function get_snippet_content( $schema, $post, $atts ) { wp_enqueue_style( 'rank-math-review-snippet', rank_math()->assets() . 'css/rank-math-snippet.css', null, rank_math()->version ); $type = \strtolower( $schema['@type'] ); $type = preg_replace( '/[^a-z0-9_-]+/i', '', $type ); $this->post = $post; $this->schema = $schema; if ( in_array( $type, [ 'article', 'blogposting', 'newsarticle' ], true ) ) { return; } if ( Str::ends_with( 'event', $type ) ) { $type = 'event'; } if ( 'resturant' === $type ) { $type = 'restaurant'; } $class = ! empty( $atts['className'] ) ? $atts['className'] : ''; ob_start(); ?>
plugin_dir() . "includes/modules/schema/shortcode/$type.php"; if ( file_exists( $file ) ) { include $file; } $this->do_action( 'snippet/after_schema_content', $this ); ?>
schema; if ( isset( $array[ $field_id ] ) ) { if ( isset( $array[ $field_id ]['@type'] ) ) { unset( $array[ $field_id ]['@type'] ); } return $array[ $field_id ]; } foreach ( explode( '.', $field_id ) as $segment ) { if ( ! is_array( $array ) || ! array_key_exists( $segment, $array ) ) { return $default; } $array = $array[ $segment ]; } return $array; } /** * Get field. * * @param string $title Field title. * @param string $field_id Field id to get value. * @param string $convert_date Convert date value to proper format. * @param mixed $default Default value. */ public function get_field( $title, $field_id, $convert_date = false, $default = null ) { $value = $this->get_field_value( $field_id, $default ); if ( empty( $value ) ) { return; } if ( $convert_date ) { $value = Helper::convert_date( $value ); } $this->output_field( $title, $value ); } /** * Get Opening hours data. * * @param string $field_id Field id to get value. */ public function get_opening_hours( $field_id ) { $opening_hours = $this->get_field_value( $field_id ); if ( empty( $opening_hours ) ) { return; } if ( count( array_filter( array_keys( $opening_hours ), 'is_string' ) ) > 0 ) { $this->get_opening_hour( $opening_hours ); return; } echo '
'; echo '' . esc_html__( 'Opening Hours', 'rank-math' ) . '
'; foreach ( $opening_hours as $opening_hour ) { $this->get_opening_hour( $opening_hour ); } echo '
'; } /** * Get Opening hours. * * @param array $opening_hour Opening hours data. */ public function get_opening_hour( $opening_hour ) { $labels = [ 'dayOfWeek' => esc_html__( 'Days', 'rank-math' ), 'opens' => esc_html__( 'Opening Time', 'rank-math' ), 'closes' => esc_html__( 'Closing Time', 'rank-math' ), ]; foreach ( $labels as $key => $label ) { if ( empty( $opening_hour[ $key ] ) ) { continue; } $this->output_field( $label, $opening_hour[ $key ] ); } } /** * Get field. * * @param string $title Field title. * @param mixed $value Field value. */ public function output_field( $title, $value ) { ?>

:

schema['name'] ) && ! isset( $this->schema['title'] ) ) { return; } $title = isset( $this->schema['title'] ) ? $this->schema['title'] : $this->schema['name']; $title = $title && '' !== $title ? $title : Helper::replace_vars( '%title%', $this->post ); ?>
post ); if ( $description && '' !== $description ) { $description = $this->get_field_value( $description ); } $description = $description && '' !== $description ? $description : ( $excerpt ? $excerpt : Helper::get_post_meta( 'description', $this->post->ID ) ); ?>

schema['image'] ) ) { return; } $image = Helper::get_thumbnail_with_fallback( $this->post->ID, 'medium' ); if ( empty( $image ) ) { return; } ?>
get_field_value( $field_id ); if ( empty( $rating ) ) { return; } $best_rating = (int) $this->get_field_value( 'review.reviewRating.bestRating', 5 ); ?>
do_filter( 'review/text', esc_html__( 'Editor\'s Rating:', 'rank-math' ) ); // phpcs:ignore ?>
', $best_rating ); // phpcs:ignore ?>
', $best_rating ); // phpcs:ignore ?>
get_schemas(); if ( empty( $schemas ) ) { return $content; } foreach ( $schemas as $schema ) { $location = $this->get_content_location( $schema ); if ( false === $location || 'custom' === $location ) { continue; } $review = do_shortcode( '[rank_math_rich_snippet id="' . $schema['metadata']['shortcode'] . '"]' ); if ( in_array( $location, [ 'top', 'both' ], true ) ) { $content = $review . $content; } if ( in_array( $location, [ 'bottom', 'both' ], true ) && $this->can_add_multi_page() ) { $content .= $review; } } return $content; } /** * Get schema data by shortcode/post ID. * * @param string $shortcode_id Schema shortcode ID. * @param string $post_id Post ID. * @return array */ private function get_schema_data( $shortcode_id, $post_id = false ) { if ( ! empty( $shortcode_id ) && is_string( $shortcode_id ) ) { return DB::get_schema_by_shortcode_id( $shortcode_id ); } if ( ! $post_id ) { $post_id = Param::get( 'post_id' ) ? Param::get( 'post_id' ) : get_the_ID(); } $data = DB::get_schemas( $post_id ); return empty( $data ) ? false : [ 'post_id' => $post_id, 'schema' => $data, ]; } /** * Function to replace variables used in Schema fields. * * @param array $schemas Schema to replace. * @param WP_Post $post Post schema attached to. * @return array */ private function replace_variables( $schemas, $post ) { if ( ! is_array( $schemas ) && ! is_object( $schemas ) ) { return []; } $new_schemas = []; foreach ( $schemas as $key => $schema ) { if ( 'metadata' === $key ) { continue; } if ( is_array( $schema ) ) { $new_schemas[ $key ] = $this->replace_variables( $schema, $post ); continue; } $new_schemas[ $key ] = Str::contains( '%', $schema ) ? Helper::replace_seo_fields( $schema, $post ) : $schema; } return $new_schemas; } /** * Check if we can inject the review in the content. * * @param array $schema Schema Data. * * @return boolean|string */ private function get_content_location( $schema ) { $location = ! empty( $schema['metadata']['shortcode'] ) && isset( $schema['metadata']['reviewLocation'] ) ? $schema['metadata']['reviewLocation'] : false; return $this->do_filter( 'snippet/review/location', $location ); } /** * Get schema data to show in the content. * * @return boolean|array * * @since 1.0.59 */ private function get_schemas() { /** * Filter: Allow disabling the review display. * * @param bool $return True to disable. */ if ( ! is_main_query() || ! in_the_loop() || $this->do_filter( 'snippet/review/hide_data', false ) ) { return false; } $schemas = $this->get_schema_data( false ); if ( empty( $schemas ) ) { return false; } return array_filter( $schemas['schema'], function( $schema ) { return ! empty( $schema['metadata']['reviewLocation'] ); } ); } /** * Check if we can add content if multipage. * * @return bool */ private function can_add_multi_page() { global $multipage, $numpages, $page; return ( ! $multipage || $page === $numpages ); } }