You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
319 lines
8.2 KiB
PHTML
319 lines
8.2 KiB
PHTML
8 months ago
|
<?php
|
||
|
/**
|
||
|
* The frontend code of the Schema module.
|
||
|
*
|
||
|
* @since 1.4.3
|
||
|
* @package RankMath
|
||
|
* @subpackage RankMath\Schema
|
||
|
* @author Rank Math <support@rankmath.com>
|
||
|
*/
|
||
|
|
||
|
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;
|
||
|
}
|
||
|
}
|