*/ namespace RankMath\Schema; use RankMath\Helper; use RankMath\Traits\Hooker; use RankMath\Helpers\Str; defined( 'ABSPATH' ) || exit; /** * Frontend class. */ class Frontend { use Hooker; /** * Hold post object. * * @var WP_Post */ public $post = null; /** * Hold post ID. * * @var ID */ public $post_id = 0; /** * The Constructor. */ public function __construct() { if ( Helper::is_divi_frontend_editor() ) { return; } $this->action( 'rank_math/json_ld', 'add_schema', 10, 2 ); $this->action( 'rank_math/json_ld', 'connect_schema_entities', 99, 2 ); $this->filter( 'rank_math/snippet/rich_snippet_event_entity', 'validate_event_schema', 11, 2 ); $this->filter( 'rank_math/snippet/rich_snippet_article_entity', 'add_name_property', 11, 2 ); $this->action( 'rank_math/schema/validated_data', 'remove_person_entity', 999 ); new Opengraph(); } /** * Output schema data for a post. * * @param array $data Array of json-ld data. * @param JsonLD $jsonld Instance of jsonld. * * @return array */ public function add_schema( $data, $jsonld ) { if ( ! is_singular() || post_password_required() ) { return $data; } global $post; $schemas = array_filter( DB::get_schemas( $post->ID ), function( $schema ) { return ! in_array( $schema['@type'], [ 'WooCommerceProduct', 'EDDProduct' ], true ); } ); // Check & Unpublish the JobPosting post. DB::unpublish_jobposting_post( $jsonld, $schemas ); $schemas = $jsonld->replace_variables( $schemas, [], $data ); $schemas = $jsonld->filter( $schemas, $jsonld, $data ); return array_merge( $data, $schemas ); } /** * Connect different schema entities using isPartOf & publisher properties. * * @param array $schemas Array of json-ld data. * @param JsonLD $jsonld Instance of jsonld. * * @return array */ public function connect_schema_entities( $schemas, $jsonld ) { if ( empty( $schemas ) ) { return $schemas; } $jsonld->parts['canonical'] = ! empty( $jsonld->parts['canonical'] ) ? $jsonld->parts['canonical'] : \RankMath\Paper\Paper::get()->get_canonical(); $schema_types = []; foreach ( $schemas as $id => $schema ) { if ( ! Str::starts_with( 'schema-', $id ) && 'richSnippet' !== $id ) { continue; } $schema_types[] = $schema['@type']; $this->connect_properties( $schema, $id, $jsonld, $schemas ); $this->add_main_entity_of_page( $schema, $jsonld ); $schemas[ $id ] = $schema; } return $this->change_webpage_entity( $schemas, $schema_types ); } /** * Add name property to the Article schema. * * @since 1.0.61 * * @param array $schema Snippet Data. * @return array */ public function add_name_property( $schema ) { if ( empty( $schema['headline'] ) ) { return $schema; } $schema['name'] = $schema['headline']; if ( ! isset( $schema['articleSection'] ) ) { global $post; $schema['articleSection'] = Helper::replace_vars( '%primary_taxonomy_terms%', $post ); } return $schema; } /** * Add timezone to startDate field. * * @param array $schema Event schema Data. * @return array */ public function validate_event_schema( $schema ) { if ( ! empty( $schema['startDate'] ) ) { $start_date = date_i18n( 'Y-m-d H:i:sP', strtotime( $schema['startDate'] ) ); $schema['startDate'] = str_replace( ' ', 'T', $start_date ); } if ( ! empty( $schema['endDate'] ) ) { $end_date = date_i18n( 'Y-m-d H:i:sP', strtotime( $schema['endDate'] ) ); $schema['endDate'] = str_replace( ' ', 'T', $end_date ); } return $schema; } /** * Connect schema properties. * * @param array $schema Schema Entity. * @param string $id Schema Entity ID. * @param JsonLD $jsonld JsonLD Instance. * @param array $schemas Array of json-ld data. */ private function connect_properties( &$schema, $id, $jsonld, $schemas ) { if ( isset( $schema['isCustom'] ) ) { unset( $schema['isCustom'] ); return; } // Remove empty ImageObject. if ( isset( $schema['image'] ) && empty( $schema['image']['url'] ) && ! is_array( $schema['image'] ) ) { unset( $schema['image'] ); } $jsonld->parts['canonical'] = ! empty( $jsonld->parts['canonical'] ) ? $jsonld->parts['canonical'] : \RankMath\Paper\Paper::get()->get_canonical(); $schema['@id'] = $jsonld->parts['canonical'] . '#' . $id; $types = array_map( 'strtolower', (array) $schema['@type'] ); foreach ( $types as $type ) { $is_event = Str::contains( 'event', $type ); // Add publisher entity @id in the organizer property of Event schema. if ( $is_event ) { $jsonld->add_prop( 'publisher', $schema, 'organizer', $schemas ); } $props = [ 'is_part_of' => [ 'key' => 'webpage', 'value' => ! in_array( $type, [ 'jobposting', 'musicgroup', 'person', 'product', 'productgroup', 'restaurant', 'service' ], true ) && ! $is_event, ], 'publisher' => [ 'key' => 'publisher', 'value' => ! in_array( $type, [ 'jobposting', 'musicgroup', 'person', 'product', 'productgroup', 'restaurant', 'service' ], true ) && ! $is_event, ], 'thumbnail' => [ 'key' => 'image', 'value' => ! in_array( $type, [ 'videoobject' ], true ) || isset( $schema['image'] ), ], 'language' => [ 'key' => 'inLanguage', 'value' => ! in_array( $type, [ 'person', 'service', 'restaurant', 'product', 'productgroup', 'musicgroup', 'musicalbum', 'jobposting' ], true ), ], ]; if ( isset( $schema['image'] ) && 'product' === $type && is_array( $schema['image'] ) ) { $props['thumbnail']['value'] = false; } foreach ( $props as $prop => $data ) { if ( ! $data['value'] ) { continue; } $jsonld->add_prop( $prop, $schema, $data['key'], $schemas ); } } } /** * Add mainEntityOfPage property to Primary schema entity. * * @param array $schema Schema Entity. * @param JsonLD $jsonld JsonLD Instance. */ private function add_main_entity_of_page( &$schema, $jsonld ) { if ( ! isset( $schema['isPrimary'] ) ) { return; } if ( ! empty( $schema['isPrimary'] ) ) { $schema['mainEntityOfPage'] = [ '@id' => $jsonld->parts['canonical'] . '#webpage' ]; } unset( $schema['isPrimary'] ); } /** * Remove Person entity if it is not referenced in any other entities. * * @param array $data Array of json-ld data. * * @return array */ public function remove_person_entity( $data ) { if ( empty( $data['ProfilePage'] ) || empty( $data['ProfilePage']['@id'] ) || ! is_singular() ) { return $data; } if ( function_exists( 'bp_is_user' ) && bp_is_user() ) { return $data; } $temp_data = $data; $id = $temp_data['ProfilePage']['@id']; $ids = []; unset( $temp_data['ProfilePage'] ); array_walk_recursive( $temp_data, function( $value, $key ) use ( &$ids, $id ) { if ( '@id' === $key && $value === $id ) { $ids[] = $value; } } ); if ( empty( $ids ) ) { unset( $data['ProfilePage'] ); } return $data; } /** * Change WebPage entity type depending on the schemas on the page. * * @param array $schemas Schema data. * @param array $types Schema types. * * @return array */ private function change_webpage_entity( $schemas, $types ) { if ( in_array( 'Product', $types, true ) ) { $schemas['WebPage']['@type'] = 'ItemPage'; } if ( isset( $schemas['howto'] ) && ! empty( $schemas['WebPage'] ) ) { $schemas['howto']['mainEntityOfPage'] = [ '@id' => $schemas['WebPage']['@id'] ]; } $faq_data = array_map( function( $schema ) { return isset( $schema['@type'] ) && 'FAQPage' === $schema['@type']; }, $schemas ); $faq_key = is_array( $faq_data ) && ! empty( $faq_data ) ? key( array_filter( $faq_data ) ) : ''; if ( ! $faq_key ) { return $schemas; } if ( in_array( $faq_key, array_keys( $schemas ), true ) ) { $schemas['WebPage']['@type'] = ! empty( $types ) ? array_merge( (array) $schemas['WebPage']['@type'], [ 'FAQPage' ] ) : 'FAQPage'; $schemas['WebPage']['mainEntity'] = $schemas[ $faq_key ]['mainEntity']; unset( $schemas[ $faq_key ] ); } return $schemas; } }