Initial commit: Atomaste website
This commit is contained in:
@@ -0,0 +1,354 @@
|
||||
<?php
|
||||
namespace AIOSEO\Plugin\Common\Schema;
|
||||
|
||||
// Exit if accessed directly.
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the breadcrumb trail.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
class Breadcrumb {
|
||||
/**
|
||||
* Returns the breadcrumb trail for the homepage.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @return array The breadcrumb trail.
|
||||
*/
|
||||
public function home() {
|
||||
// Since we just need the root breadcrumb (homepage), we can call this immediately without passing any breadcrumbs.
|
||||
return $this->setPositions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the breadcrumb trail for the requested post.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @param \WP_Post $post The post object.
|
||||
* @return array The breadcrumb trail.
|
||||
*/
|
||||
public function post( $post ) {
|
||||
// Check if page is the static homepage.
|
||||
if ( aioseo()->helpers->isStaticHomePage() ) {
|
||||
return $this->home();
|
||||
}
|
||||
|
||||
if ( is_post_type_hierarchical( $post->post_type ) ) {
|
||||
return $this->setPositions( $this->postHierarchical( $post ) );
|
||||
}
|
||||
|
||||
return $this->setPositions( $this->postNonHierarchical( $post ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the breadcrumb trail for a hierarchical post.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @param \WP_Post $post The post object.
|
||||
* @return array The breadcrumb trail.
|
||||
*/
|
||||
private function postHierarchical( $post ) {
|
||||
$breadcrumbs = [];
|
||||
do {
|
||||
array_unshift(
|
||||
$breadcrumbs,
|
||||
[
|
||||
'name' => $post->post_title,
|
||||
'description' => aioseo()->meta->description->getDescription( $post ),
|
||||
'url' => get_permalink( $post ),
|
||||
'type' => aioseo()->helpers->isWooCommerceShopPage( $post->ID ) || is_home() ? 'CollectionPage' : $this->getPostWebPageGraph()
|
||||
]
|
||||
);
|
||||
|
||||
if ( $post->post_parent ) {
|
||||
$post = get_post( $post->post_parent );
|
||||
} else {
|
||||
$post = false;
|
||||
}
|
||||
} while ( $post );
|
||||
|
||||
return $breadcrumbs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the breadcrumb trail for a non-hierarchical post.
|
||||
*
|
||||
* In this case we need to compare the permalink structure with the permalink of the requested post and loop through all objects we're able to find.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @param \WP_Post $post The post object.
|
||||
* @return array The breadcrumb trail.
|
||||
*/
|
||||
private function postNonHierarchical( $post ) {
|
||||
global $wp_query; // phpcs:ignore Squiz.NamingConventions.ValidVariableName
|
||||
$homeUrl = aioseo()->helpers->escapeRegex( home_url() );
|
||||
$permalink = get_permalink();
|
||||
$slug = preg_replace( "/$homeUrl/", '', (string) $permalink );
|
||||
$tags = array_filter( explode( '/', get_option( 'permalink_structure' ) ) ); // Permalink structure exploded into separate tag strings.
|
||||
$objects = array_filter( explode( '/', $slug ) ); // Permalink slug exploded into separate object slugs.
|
||||
$postGraph = $this->getPostWebPageGraph();
|
||||
|
||||
if ( count( $tags ) !== count( $objects ) ) {
|
||||
return [
|
||||
'name' => $post->post_title,
|
||||
'description' => aioseo()->meta->description->getDescription( $post ),
|
||||
'url' => $permalink,
|
||||
'type' => $postGraph
|
||||
];
|
||||
}
|
||||
|
||||
$pairs = array_reverse( array_combine( $tags, $objects ) );
|
||||
|
||||
$breadcrumbs = [];
|
||||
$dateName = null;
|
||||
$timestamp = strtotime( $post->post_date );
|
||||
foreach ( $pairs as $tag => $object ) {
|
||||
// Escape the delimiter.
|
||||
$escObject = aioseo()->helpers->escapeRegex( $object );
|
||||
// Determine the slug for the object.
|
||||
preg_match( "/.*{$escObject}[\/]/", (string) $permalink, $url );
|
||||
if ( empty( $url[0] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$breadcrumb = [];
|
||||
switch ( $tag ) {
|
||||
case '%category%':
|
||||
$term = aioseo()->standalone->primaryTerm->getPrimaryTerm( $post->ID, 'category' );
|
||||
if ( ! $term ) {
|
||||
$term = get_category_by_slug( $object );
|
||||
}
|
||||
|
||||
if ( ! $term ) {
|
||||
break;
|
||||
}
|
||||
// phpcs:disable Squiz.NamingConventions.ValidVariableName
|
||||
$oldQueriedObject = $wp_query->queried_object;
|
||||
$wp_query->queried_object = $term;
|
||||
$wp_query->is_category = true;
|
||||
|
||||
$breadcrumb = [
|
||||
'name' => $term->name,
|
||||
'description' => aioseo()->meta->description->getDescription(),
|
||||
'url' => get_term_link( $term ),
|
||||
'type' => 'CollectionPage'
|
||||
];
|
||||
|
||||
$wp_query->queried_object = $oldQueriedObject;
|
||||
$wp_query->is_category = false;
|
||||
// phpcs:enable Squiz.NamingConventions.ValidVariableName
|
||||
break;
|
||||
case '%author%':
|
||||
$breadcrumb = [
|
||||
'name' => get_the_author_meta( 'display_name', $post->post_author ),
|
||||
'description' => aioseo()->meta->description->helpers->prepare( aioseo()->options->searchAppearance->archives->author->metaDescription ),
|
||||
'url' => $url[0],
|
||||
'type' => 'ProfilePage'
|
||||
];
|
||||
break;
|
||||
case '%postid%':
|
||||
case '%postname%':
|
||||
$breadcrumb = [
|
||||
'name' => $post->post_title,
|
||||
'description' => aioseo()->meta->description->getDescription( $post ),
|
||||
'url' => $url[0],
|
||||
'type' => $postGraph
|
||||
];
|
||||
break;
|
||||
case '%year%':
|
||||
$dateName = gmdate( 'Y', $timestamp );
|
||||
case '%monthnum%':
|
||||
if ( ! $dateName ) {
|
||||
$dateName = gmdate( 'F', $timestamp );
|
||||
}
|
||||
case '%day%':
|
||||
if ( ! $dateName ) {
|
||||
$dateName = gmdate( 'j', $timestamp );
|
||||
}
|
||||
$breadcrumb = [
|
||||
'name' => $dateName,
|
||||
'description' => aioseo()->meta->description->helpers->prepare( aioseo()->options->searchAppearance->archives->date->metaDescription ),
|
||||
'url' => $url[0],
|
||||
'type' => 'CollectionPage'
|
||||
];
|
||||
$dateName = null;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if ( $breadcrumb ) {
|
||||
array_unshift( $breadcrumbs, $breadcrumb );
|
||||
}
|
||||
}
|
||||
|
||||
return $breadcrumbs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the breadcrumb trail for the requested term.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @param \WP_Term $term The term object.
|
||||
* @return array The breadcrumb trail.
|
||||
*/
|
||||
public function term( $term ) {
|
||||
if ( 'product_attributes' === $term->taxonomy ) {
|
||||
$term = get_term( $term->term_id );
|
||||
}
|
||||
|
||||
$breadcrumbs = [];
|
||||
do {
|
||||
array_unshift(
|
||||
$breadcrumbs,
|
||||
[
|
||||
'name' => $term->name,
|
||||
'description' => aioseo()->meta->description->getDescription(),
|
||||
'url' => get_term_link( $term, $term->taxonomy ),
|
||||
'type' => 'CollectionPage'
|
||||
]
|
||||
);
|
||||
|
||||
if ( $term->parent ) {
|
||||
$term = aioseo()->helpers->getTerm( $term->parent, $term->taxonomy );
|
||||
} else {
|
||||
$term = false;
|
||||
}
|
||||
} while ( $term );
|
||||
|
||||
return $this->setPositions( $breadcrumbs );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the breadcrumb trail for the requested date archive.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @return array The breadcrumb trail.
|
||||
*/
|
||||
public function date() {
|
||||
// phpcs:disable Squiz.NamingConventions.ValidVariableName
|
||||
global $wp_query;
|
||||
|
||||
$oldYear = $wp_query->is_year;
|
||||
$oldMonth = $wp_query->is_month;
|
||||
$oldDay = $wp_query->is_day;
|
||||
$wp_query->is_year = true;
|
||||
$wp_query->is_month = false;
|
||||
$wp_query->is_day = false;
|
||||
|
||||
$breadcrumbs = [
|
||||
[
|
||||
'name' => get_the_date( 'Y' ),
|
||||
'description' => aioseo()->meta->description->getDescription(),
|
||||
'url' => trailingslashit( get_year_link( $wp_query->query_vars['year'] ) ),
|
||||
'type' => 'CollectionPage'
|
||||
]
|
||||
];
|
||||
|
||||
$wp_query->is_year = $oldYear;
|
||||
|
||||
// Fall through if data archive is more specific than the year.
|
||||
if ( is_year() ) {
|
||||
return $this->setPositions( $breadcrumbs );
|
||||
}
|
||||
|
||||
$wp_query->is_month = true;
|
||||
|
||||
$breadcrumbs[] = [
|
||||
'name' => get_the_date( 'F, Y' ),
|
||||
'description' => aioseo()->meta->description->getDescription(),
|
||||
'url' => trailingslashit( get_month_link(
|
||||
$wp_query->query_vars['year'],
|
||||
$wp_query->query_vars['monthnum']
|
||||
) ),
|
||||
'type' => 'CollectionPage'
|
||||
];
|
||||
|
||||
$wp_query->is_month = $oldMonth;
|
||||
|
||||
// Fall through if data archive is more specific than the year & month.
|
||||
if ( is_month() ) {
|
||||
return $this->setPositions( $breadcrumbs );
|
||||
}
|
||||
|
||||
$wp_query->is_day = $oldDay;
|
||||
|
||||
$breadcrumbs[] = [
|
||||
'name' => get_the_date(),
|
||||
'description' => aioseo()->meta->description->getDescription(),
|
||||
'url' => trailingslashit( get_day_link(
|
||||
$wp_query->query_vars['year'],
|
||||
$wp_query->query_vars['monthnum'],
|
||||
$wp_query->query_vars['day']
|
||||
) ),
|
||||
'type' => 'CollectionPage'
|
||||
];
|
||||
// phpcs:enable Squiz.NamingConventions.ValidVariableName
|
||||
|
||||
return $this->setPositions( $breadcrumbs );
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the position for each breadcrumb after adding the root breadcrumb first.
|
||||
*
|
||||
* If no breadcrumbs are passed, then we assume we're on the homepage and just need the root breadcrumb.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @param array $breadcrumbs The breadcrumb trail.
|
||||
* @return array The modified breadcrumb trail.
|
||||
*/
|
||||
public function setPositions( $breadcrumbs = [] ) {
|
||||
// If the array isn't two-dimensional, then we need to wrap it in another array before continuing.
|
||||
if (
|
||||
count( $breadcrumbs ) &&
|
||||
count( $breadcrumbs ) === count( $breadcrumbs, COUNT_RECURSIVE )
|
||||
) {
|
||||
$breadcrumbs = [ $breadcrumbs ];
|
||||
}
|
||||
|
||||
// The homepage needs to be root item of all trails.
|
||||
$homepage = [
|
||||
// Translators: This refers to the homepage of the site.
|
||||
'name' => apply_filters( 'aioseo_schema_breadcrumbs_home', __( 'Home', 'all-in-one-seo-pack' ) ),
|
||||
'description' => aioseo()->meta->description->getHomePageDescription(),
|
||||
'url' => trailingslashit( home_url() ),
|
||||
'type' => 'posts' === get_option( 'show_on_front' ) ? 'CollectionPage' : 'WebPage'
|
||||
];
|
||||
array_unshift( $breadcrumbs, $homepage );
|
||||
|
||||
$breadcrumbs = array_filter( $breadcrumbs );
|
||||
foreach ( $breadcrumbs as $index => &$breadcrumb ) {
|
||||
$breadcrumb['position'] = $index + 1;
|
||||
}
|
||||
|
||||
return $breadcrumbs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the most relevant WebPage graph for the post.
|
||||
*
|
||||
* @since 4.2.5
|
||||
*
|
||||
* @return string The graph name.
|
||||
*/
|
||||
private function getPostWebPageGraph() {
|
||||
foreach ( aioseo()->schema->graphs as $graphName ) {
|
||||
if ( in_array( $graphName, aioseo()->schema->webPageGraphs, true ) ) {
|
||||
return $graphName;
|
||||
}
|
||||
}
|
||||
|
||||
// Return the default if no WebPage graph was found.
|
||||
return 'WebPage';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,280 @@
|
||||
<?php
|
||||
namespace AIOSEO\Plugin\Common\Schema;
|
||||
|
||||
// Exit if accessed directly.
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the context.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
class Context {
|
||||
/**
|
||||
* Breadcrumb class instance.
|
||||
*
|
||||
* @since 4.2.7
|
||||
*
|
||||
* @var Breadcrumb
|
||||
*/
|
||||
public $breadcrumb = null;
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->breadcrumb = new Breadcrumb();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the default context data.
|
||||
*
|
||||
* @since 4.3.0
|
||||
*
|
||||
* @return array The context data.
|
||||
*/
|
||||
public function defaults() {
|
||||
return [
|
||||
'name' => aioseo()->meta->title->getTitle(),
|
||||
'description' => aioseo()->meta->description->getDescription(),
|
||||
'url' => aioseo()->helpers->getUrl(),
|
||||
'breadcrumb' => []
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the context data for the homepage.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @return array $context The context data.
|
||||
*/
|
||||
public function home() {
|
||||
$context = [
|
||||
'url' => aioseo()->helpers->getUrl(),
|
||||
'breadcrumb' => $this->breadcrumb->home(),
|
||||
'name' => aioseo()->meta->title->getTitle(),
|
||||
'description' => aioseo()->meta->description->getDescription()
|
||||
];
|
||||
|
||||
// Homepage set to show latest posts.
|
||||
if ( 'posts' === get_option( 'show_on_front' ) && is_home() ) {
|
||||
return $context;
|
||||
}
|
||||
|
||||
// Homepage set to static page.
|
||||
$post = aioseo()->helpers->getPost();
|
||||
if ( ! $post ) {
|
||||
return [
|
||||
'name' => '',
|
||||
'description' => '',
|
||||
'url' => aioseo()->helpers->getUrl(),
|
||||
'breadcrumb' => [],
|
||||
];
|
||||
}
|
||||
|
||||
$context['object'] = $post;
|
||||
|
||||
return $context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the context data for the requested post.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @return array The context data.
|
||||
*/
|
||||
public function post() {
|
||||
$post = aioseo()->helpers->getPost();
|
||||
if ( ! $post ) {
|
||||
return [
|
||||
'name' => '',
|
||||
'description' => '',
|
||||
'url' => aioseo()->helpers->getUrl(),
|
||||
'breadcrumb' => [],
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'name' => aioseo()->meta->title->getTitle( $post ),
|
||||
'description' => aioseo()->meta->description->getDescription( $post ),
|
||||
'url' => aioseo()->helpers->getUrl(),
|
||||
'breadcrumb' => $this->breadcrumb->post( $post ),
|
||||
'object' => $post,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the context data for the requested term archive.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @return array The context data.
|
||||
*/
|
||||
public function term() {
|
||||
$term = aioseo()->helpers->getTerm();
|
||||
if ( ! $term ) {
|
||||
return [
|
||||
'name' => '',
|
||||
'description' => '',
|
||||
'url' => aioseo()->helpers->getUrl(),
|
||||
'breadcrumb' => [],
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'name' => aioseo()->meta->title->getTitle(),
|
||||
'description' => aioseo()->meta->description->getDescription(),
|
||||
'url' => aioseo()->helpers->getUrl(),
|
||||
'breadcrumb' => $this->breadcrumb->term( $term )
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the context data for the requested author archive.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @return array The context data.
|
||||
*/
|
||||
public function author() {
|
||||
$author = get_queried_object();
|
||||
if ( ! $author ) {
|
||||
return [
|
||||
'name' => '',
|
||||
'description' => '',
|
||||
'url' => aioseo()->helpers->getUrl(),
|
||||
'breadcrumb' => [],
|
||||
];
|
||||
}
|
||||
|
||||
$title = aioseo()->meta->title->getTitle();
|
||||
$description = aioseo()->meta->description->getDescription();
|
||||
$url = aioseo()->helpers->getUrl();
|
||||
|
||||
if ( ! $description ) {
|
||||
$description = get_the_author_meta( 'description', $author->ID );
|
||||
}
|
||||
|
||||
return [
|
||||
'name' => $title,
|
||||
'description' => $description,
|
||||
'url' => $url,
|
||||
'breadcrumb' => $this->breadcrumb->setPositions( [
|
||||
'name' => get_the_author_meta( 'display_name', $author->ID ),
|
||||
'description' => $description,
|
||||
'url' => $url,
|
||||
'type' => 'CollectionPage'
|
||||
] )
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the context data for the requested post archive.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @return array The context data.
|
||||
*/
|
||||
public function postArchive() {
|
||||
$postType = get_queried_object();
|
||||
if ( ! $postType ) {
|
||||
return [
|
||||
'name' => '',
|
||||
'description' => '',
|
||||
'url' => aioseo()->helpers->getUrl(),
|
||||
'breadcrumb' => [],
|
||||
];
|
||||
}
|
||||
|
||||
$title = aioseo()->meta->title->getTitle();
|
||||
$description = aioseo()->meta->description->getDescription();
|
||||
$url = aioseo()->helpers->getUrl();
|
||||
|
||||
return [
|
||||
'name' => $title,
|
||||
'description' => $description,
|
||||
'url' => $url,
|
||||
'breadcrumb' => $this->breadcrumb->setPositions( [
|
||||
'name' => $postType->label,
|
||||
'description' => $description,
|
||||
'url' => $url,
|
||||
'type' => 'CollectionPage'
|
||||
] )
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the context data for the requested data archive.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @return array $context The context data.
|
||||
*/
|
||||
public function date() {
|
||||
$context = [
|
||||
'name' => aioseo()->meta->title->getTitle(),
|
||||
'description' => aioseo()->meta->description->getDescription(),
|
||||
'url' => aioseo()->helpers->getUrl()
|
||||
];
|
||||
|
||||
$context['breadcrumb'] = $this->breadcrumb->date();
|
||||
|
||||
return $context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the context data for the search page.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @return array The context data.
|
||||
*/
|
||||
public function search() {
|
||||
global $s;
|
||||
$title = aioseo()->meta->title->getTitle();
|
||||
$description = aioseo()->meta->description->getDescription();
|
||||
$url = aioseo()->helpers->getUrl();
|
||||
|
||||
return [
|
||||
'name' => $title,
|
||||
'description' => $description,
|
||||
'url' => $url,
|
||||
'breadcrumb' => $this->breadcrumb->setPositions( [
|
||||
'name' => $s ? $s : $title,
|
||||
'description' => $description,
|
||||
'url' => $url,
|
||||
'type' => 'SearchResultsPage'
|
||||
] )
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the context data for the 404 Not Found page.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @return array The context data.
|
||||
*/
|
||||
public function notFound() {
|
||||
$title = aioseo()->meta->title->getTitle();
|
||||
$description = aioseo()->meta->description->getDescription();
|
||||
$url = aioseo()->helpers->getUrl();
|
||||
|
||||
return [
|
||||
'name' => $title,
|
||||
'description' => $description,
|
||||
'url' => $url,
|
||||
'breadcrumb' => $this->breadcrumb->setPositions( [
|
||||
'name' => __( 'Not Found', 'all-in-one-seo-pack' ),
|
||||
'description' => $description,
|
||||
'url' => $url
|
||||
] )
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
namespace AIOSEO\Plugin\Common\Schema\Graphs;
|
||||
|
||||
// Exit if accessed directly.
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* AmpStory graph class.
|
||||
*
|
||||
* @since 4.7.6
|
||||
*/
|
||||
class AmpStory extends Graph {
|
||||
/**
|
||||
* Returns the graph data.
|
||||
*
|
||||
* @since 4.7.6
|
||||
*
|
||||
* @return array The parsed graph data.
|
||||
*/
|
||||
public function get() {
|
||||
$post = aioseo()->helpers->getPost();
|
||||
if ( ! is_a( $post, 'WP_Post' ) || 'web-story' !== $post->post_type ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$data = [
|
||||
'@type' => 'AmpStory',
|
||||
'@id' => aioseo()->schema->context['url'] . '#amp-story',
|
||||
'name' => aioseo()->schema->context['name'],
|
||||
'headline' => get_the_title(),
|
||||
'author' => [
|
||||
'@id' => get_author_posts_url( $post->post_author ) . '#author'
|
||||
],
|
||||
'publisher' => [ '@id' => trailingslashit( home_url() ) . '#' . aioseo()->options->searchAppearance->global->schema->siteRepresents ],
|
||||
'image' => $this->getFeaturedImage(),
|
||||
'datePublished' => mysql2date( DATE_W3C, $post->post_date, false ),
|
||||
'dateModified' => mysql2date( DATE_W3C, $post->post_modified, false ),
|
||||
'inLanguage' => aioseo()->helpers->currentLanguageCodeBCP47()
|
||||
];
|
||||
|
||||
if ( ! in_array( 'PersonAuthor', aioseo()->schema->graphs, true ) ) {
|
||||
aioseo()->schema->graphs[] = 'PersonAuthor';
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
<?php
|
||||
namespace AIOSEO\Plugin\Common\Schema\Graphs\Article;
|
||||
|
||||
// Exit if accessed directly.
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
use AIOSEO\Plugin\Common\Schema\Graphs;
|
||||
|
||||
/**
|
||||
* Article graph class.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
class Article extends Graphs\Graph {
|
||||
/**
|
||||
* Returns the graph data.
|
||||
*
|
||||
* @since 4.2.5
|
||||
*
|
||||
* @param Object $graphData The graph data.
|
||||
* @return array The parsed graph data.
|
||||
*/
|
||||
public function get( $graphData = null ) {
|
||||
$post = aioseo()->helpers->getPost();
|
||||
if ( ! is_a( $post, 'WP_Post' ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$data = [
|
||||
'@type' => 'Article',
|
||||
'@id' => ! empty( $graphData->id ) ? aioseo()->schema->context['url'] . $graphData->id : aioseo()->schema->context['url'] . '#article',
|
||||
'name' => ! empty( $graphData->properties->name ) ? $graphData->properties->name : aioseo()->schema->context['name'],
|
||||
'headline' => ! empty( $graphData->properties->headline ) ? $graphData->properties->headline : get_the_title(),
|
||||
'description' => ! empty( $graphData->properties->description ) ? $graphData->properties->description : '',
|
||||
'author' => [
|
||||
'@type' => 'Person',
|
||||
'name' => ! empty( $graphData->properties->author->name ) ? $graphData->properties->author->name : get_the_author_meta( 'display_name' ),
|
||||
'url' => ! empty( $graphData->properties->author->url ) ? $graphData->properties->author->url : '',
|
||||
],
|
||||
'publisher' => [ '@id' => trailingslashit( home_url() ) . '#' . aioseo()->options->searchAppearance->global->schema->siteRepresents ],
|
||||
'image' => ! empty( $graphData->properties->image ) ? $this->image( $graphData->properties->image ) : $this->postImage( $post ),
|
||||
'datePublished' => ! empty( $graphData->properties->dates->datePublished )
|
||||
? mysql2date( DATE_W3C, $graphData->properties->dates->datePublished, false )
|
||||
: mysql2date( DATE_W3C, $post->post_date, false ),
|
||||
'dateModified' => ! empty( $graphData->properties->dates->dateModified )
|
||||
? mysql2date( DATE_W3C, $graphData->properties->dates->dateModified, false )
|
||||
: mysql2date( DATE_W3C, $post->post_modified, false ),
|
||||
'inLanguage' => aioseo()->helpers->currentLanguageCodeBCP47(),
|
||||
'commentCount' => get_comment_count( $post->ID )['approved'],
|
||||
'mainEntityOfPage' => empty( $graphData ) ? [ '@id' => aioseo()->schema->context['url'] . '#webpage' ] : '',
|
||||
'isPartOf' => empty( $graphData ) ? [ '@id' => aioseo()->schema->context['url'] . '#webpage' ] : ''
|
||||
];
|
||||
|
||||
if ( empty( $graphData->properties->author->name ) ) {
|
||||
if ( ! in_array( 'PersonAuthor', aioseo()->schema->graphs, true ) ) {
|
||||
aioseo()->schema->graphs[] = 'PersonAuthor';
|
||||
}
|
||||
|
||||
$data['author'] = [
|
||||
'@id' => get_author_posts_url( $post->post_author ) . '#author'
|
||||
];
|
||||
}
|
||||
|
||||
if ( ! empty( $graphData->properties->keywords ) ) {
|
||||
$keywords = json_decode( $graphData->properties->keywords, true );
|
||||
$keywords = array_map( function ( $keywordObject ) {
|
||||
return $keywordObject['value'];
|
||||
}, $keywords );
|
||||
$data['keywords'] = implode( ', ', $keywords );
|
||||
}
|
||||
|
||||
if ( isset( $graphData->properties->dates->include ) && ! $graphData->properties->dates->include ) {
|
||||
unset( $data['datePublished'] );
|
||||
unset( $data['dateModified'] );
|
||||
}
|
||||
|
||||
$postTaxonomies = get_post_taxonomies( $post );
|
||||
$postTerms = [];
|
||||
foreach ( $postTaxonomies as $taxonomy ) {
|
||||
$terms = get_the_terms( $post, $taxonomy );
|
||||
if ( $terms ) {
|
||||
$postTerms = array_merge( $postTerms, wp_list_pluck( $terms, 'name' ) );
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! empty( $postTerms ) ) {
|
||||
$data['articleSection'] = implode( ', ', $postTerms );
|
||||
}
|
||||
|
||||
$pageNumber = aioseo()->helpers->getPageNumber();
|
||||
if ( 1 < $pageNumber ) {
|
||||
$data['pagination'] = $pageNumber;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the graph data for the post image.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @param \WP_Post $post The post object.
|
||||
* @return array The image graph data.
|
||||
*/
|
||||
private function postImage( $post ) {
|
||||
$featuredImage = $this->getFeaturedImage();
|
||||
if ( $featuredImage ) {
|
||||
return $featuredImage;
|
||||
}
|
||||
|
||||
preg_match_all( '#<img[^>]+src="([^">]+)"#', (string) $post->post_content, $matches );
|
||||
if ( isset( $matches[1] ) && isset( $matches[1][0] ) ) {
|
||||
$url = aioseo()->helpers->removeImageDimensions( $matches[1][0] );
|
||||
$imageId = aioseo()->helpers->attachmentUrlToPostId( $url );
|
||||
if ( $imageId ) {
|
||||
return $this->image( $imageId, 'articleImage' );
|
||||
} else {
|
||||
return $this->image( $url, 'articleImage' );
|
||||
}
|
||||
}
|
||||
|
||||
if ( 'organization' === aioseo()->options->searchAppearance->global->schema->siteRepresents ) {
|
||||
$logo = ( new Graphs\KnowledgeGraph\KgOrganization() )->logo();
|
||||
if ( ! empty( $logo ) ) {
|
||||
$logo['@id'] = trailingslashit( home_url() ) . '#articleImage';
|
||||
|
||||
return $logo;
|
||||
}
|
||||
} else {
|
||||
$avatar = $this->avatar( $post->post_author, 'articleImage' );
|
||||
if ( $avatar ) {
|
||||
return $avatar;
|
||||
}
|
||||
}
|
||||
|
||||
$imageId = aioseo()->helpers->getSiteLogoId();
|
||||
if ( $imageId ) {
|
||||
return $this->image( $imageId, 'articleImage' );
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
namespace AIOSEO\Plugin\Common\Schema\Graphs\Article;
|
||||
|
||||
// Exit if accessed directly.
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Blog Posting graph class.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
class BlogPosting extends Article {
|
||||
/**
|
||||
* Returns the graph data.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @return object $graphData The graph data.
|
||||
* @return array The parsed graph data.
|
||||
*/
|
||||
public function get( $graphData = null ) {
|
||||
$data = parent::get( $graphData );
|
||||
if ( ! $data ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$data['@type'] = 'BlogPosting';
|
||||
$data['@id'] = ! empty( $graphData->id ) ? aioseo()->schema->context['url'] . $graphData->id : aioseo()->schema->context['url'] . '#blogposting';
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
namespace AIOSEO\Plugin\Common\Schema\Graphs\Article;
|
||||
|
||||
// Exit if accessed directly.
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* News Article graph class.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
class NewsArticle extends Article {
|
||||
/**
|
||||
* Returns the graph data.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @param object $graphData The graph data.
|
||||
* @return array The parsed graph data.
|
||||
*/
|
||||
public function get( $graphData = null ) {
|
||||
if ( ! empty( self::$overwriteGraphData[ __CLASS__ ] ) ) {
|
||||
$graphData = json_decode( wp_json_encode( wp_parse_args( self::$overwriteGraphData[ __CLASS__ ], $graphData ) ) );
|
||||
}
|
||||
|
||||
$data = parent::get( $graphData );
|
||||
if ( ! $data ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$data['@type'] = 'NewsArticle';
|
||||
$data['@id'] = ! empty( $graphData->id ) ? aioseo()->schema->context['url'] . $graphData->id : aioseo()->schema->context['url'] . '#newsarticle';
|
||||
|
||||
$date = ! empty( $graphData->properties->datePublished )
|
||||
? mysql2date( 'F j, Y', $graphData->properties->datePublished, false )
|
||||
: get_the_date( 'F j, Y' );
|
||||
if ( $date ) {
|
||||
// Translators: 1 - A date (e.g. September 2, 2022).
|
||||
$data['dateline'] = sprintf( __( 'Published on %1$s.', 'all-in-one-seo-pack' ), $date );
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
namespace AIOSEO\Plugin\Common\Schema\Graphs;
|
||||
|
||||
// Exit if accessed directly.
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* BreadcrumbList graph class.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
class BreadcrumbList extends Graph {
|
||||
/**
|
||||
* Returns the graph data.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @return array The graph data.
|
||||
*/
|
||||
public function get() {
|
||||
$breadcrumbs = aioseo()->schema->context['breadcrumb'] ?? '';
|
||||
if ( ! $breadcrumbs ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$trailLength = count( $breadcrumbs );
|
||||
if ( ! $trailLength ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$listItems = [];
|
||||
foreach ( $breadcrumbs as $breadcrumb ) {
|
||||
$listItem = [
|
||||
'@type' => 'ListItem',
|
||||
'@id' => $breadcrumb['url'] . '#listItem',
|
||||
'position' => $breadcrumb['position'],
|
||||
'name' => $breadcrumb['name'] ?? ''
|
||||
];
|
||||
|
||||
// Don't add "item" prop for last crumb.
|
||||
if ( $trailLength !== $breadcrumb['position'] ) {
|
||||
$listItem['item'] = $breadcrumb['url'];
|
||||
}
|
||||
|
||||
if ( 1 === $trailLength ) {
|
||||
$listItems[] = $listItem;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( $trailLength > $breadcrumb['position'] ) {
|
||||
$listItem['nextItem'] = [
|
||||
'@type' => 'ListItem',
|
||||
'@id' => $breadcrumbs[ $breadcrumb['position'] ]['url'] . '#listItem',
|
||||
'name' => $breadcrumbs[ $breadcrumb['position'] ]['name'],
|
||||
];
|
||||
}
|
||||
|
||||
if ( 1 < $breadcrumb['position'] ) {
|
||||
$listItem['previousItem'] = [
|
||||
'@type' => 'ListItem',
|
||||
'@id' => $breadcrumbs[ $breadcrumb['position'] - 2 ]['url'] . '#listItem',
|
||||
'name' => $breadcrumbs[ $breadcrumb['position'] - 2 ]['name'],
|
||||
];
|
||||
}
|
||||
|
||||
$listItems[] = $listItem;
|
||||
}
|
||||
|
||||
$data = [
|
||||
'@type' => 'BreadcrumbList',
|
||||
'@id' => aioseo()->schema->context['url'] . '#breadcrumblist',
|
||||
'itemListElement' => $listItems
|
||||
];
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
<?php
|
||||
namespace AIOSEO\Plugin\Common\Schema\Graphs;
|
||||
|
||||
// Exit if accessed directly.
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
use AIOSEO\Plugin\Common\Traits as CommonTraits;
|
||||
|
||||
/**
|
||||
* The base graph class.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
abstract class Graph {
|
||||
use Traits\Image;
|
||||
use CommonTraits\SocialProfiles;
|
||||
|
||||
/**
|
||||
* The graph data to overwrite.
|
||||
*
|
||||
* @since 4.7.6
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $overwriteGraphData = [];
|
||||
|
||||
/**
|
||||
* Returns the graph data.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
abstract public function get();
|
||||
|
||||
/**
|
||||
* Iterates over a list of functions and sets the results as graph data.
|
||||
*
|
||||
* @since 4.0.13
|
||||
*
|
||||
* @param array $data The graph data to add to.
|
||||
* @param array $dataFunctions List of functions to loop over, associated with a graph property.
|
||||
* @return array $data The graph data with the results added.
|
||||
*/
|
||||
protected function getData( $data, $dataFunctions ) {
|
||||
foreach ( $dataFunctions as $k => $f ) {
|
||||
if ( ! method_exists( $this, $f ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$value = $this->$f();
|
||||
if ( $value || in_array( $k, aioseo()->schema->nullableFields, true ) ) {
|
||||
$data[ $k ] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes a multiselect field and returns the values.
|
||||
*
|
||||
* @since 4.6.4
|
||||
*
|
||||
* @param string $json The JSON encoded multiselect field.
|
||||
* @return array The decoded values.
|
||||
*/
|
||||
protected function extractMultiselectTags( $json ) {
|
||||
$tags = is_string( $json ) ? json_decode( $json ) : [];
|
||||
if ( ! $tags ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return wp_list_pluck( $tags, 'value' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges in data from our addon plugins.
|
||||
*
|
||||
* @since 4.5.6
|
||||
* @version 4.6.4 Moved to main graph class.
|
||||
*
|
||||
* @param array $data The graph data.
|
||||
* @return array The graph data.
|
||||
*/
|
||||
protected function getAddonData( $data, $className, $methodName = 'getAdditionalGraphData' ) {
|
||||
$addonData = array_filter( aioseo()->addons->doAddonFunction( $className, $methodName, [
|
||||
'postId' => get_the_ID(),
|
||||
'data' => $data
|
||||
] ) );
|
||||
|
||||
foreach ( $addonData as $addonGraphData ) {
|
||||
$data = array_merge( $data, $addonGraphData );
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* A way to overwrite the graph data.
|
||||
*
|
||||
* @since 4.7.6
|
||||
*
|
||||
* @param array $data The data to overwrite.
|
||||
* @return void
|
||||
*/
|
||||
public static function setOverwriteGraphData( $data ) {
|
||||
self::$overwriteGraphData[ static::class ] = $data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
namespace AIOSEO\Plugin\Common\Schema\Graphs\KnowledgeGraph;
|
||||
|
||||
// Exit if accessed directly.
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
use \AIOSEO\Plugin\Common\Schema\Graphs;
|
||||
|
||||
/**
|
||||
* Knowledge Graph Organization graph class.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
class KgOrganization extends Graphs\Graph {
|
||||
/**
|
||||
* Returns the graph data.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @return array $data The graph data.
|
||||
*/
|
||||
public function get() {
|
||||
$homeUrl = trailingslashit( home_url() );
|
||||
$organizationName = aioseo()->tags->replaceTags( aioseo()->options->searchAppearance->global->schema->organizationName );
|
||||
$organizationDescription = aioseo()->tags->replaceTags( aioseo()->options->searchAppearance->global->schema->organizationDescription );
|
||||
|
||||
$data = [
|
||||
'@type' => 'Organization',
|
||||
'@id' => $homeUrl . '#organization',
|
||||
'name' => $organizationName ? $organizationName : aioseo()->helpers->decodeHtmlEntities( get_bloginfo( 'name' ) ),
|
||||
'description' => $organizationDescription,
|
||||
'url' => $homeUrl,
|
||||
'email' => aioseo()->options->searchAppearance->global->schema->email,
|
||||
'telephone' => aioseo()->options->searchAppearance->global->schema->phone,
|
||||
'foundingDate' => aioseo()->options->searchAppearance->global->schema->foundingDate
|
||||
];
|
||||
|
||||
$numberOfEmployeesData = aioseo()->options->searchAppearance->global->schema->numberOfEmployees->all();
|
||||
|
||||
if (
|
||||
$numberOfEmployeesData['isRange'] &&
|
||||
isset( $numberOfEmployeesData['from'] ) &&
|
||||
isset( $numberOfEmployeesData['to'] ) &&
|
||||
0 < $numberOfEmployeesData['to']
|
||||
) {
|
||||
$data['numberOfEmployees'] = [
|
||||
'@type' => 'QuantitativeValue',
|
||||
'minValue' => $numberOfEmployeesData['from'],
|
||||
'maxValue' => $numberOfEmployeesData['to']
|
||||
];
|
||||
}
|
||||
|
||||
if (
|
||||
! $numberOfEmployeesData['isRange'] &&
|
||||
! empty( $numberOfEmployeesData['number'] )
|
||||
) {
|
||||
$data['numberOfEmployees'] = [
|
||||
'@type' => 'QuantitativeValue',
|
||||
'value' => $numberOfEmployeesData['number']
|
||||
];
|
||||
}
|
||||
|
||||
$logo = $this->logo();
|
||||
if ( ! empty( $logo ) ) {
|
||||
$data['logo'] = $logo;
|
||||
$data['image'] = [ '@id' => $data['logo']['@id'] ];
|
||||
}
|
||||
|
||||
$socialUrls = array_values( $this->getOrganizationProfiles() );
|
||||
if ( $socialUrls ) {
|
||||
$data['sameAs'] = $socialUrls;
|
||||
}
|
||||
|
||||
$data = $this->getAddonData( $data, 'kgOrganization' );
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the logo data.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @return array The logo data.
|
||||
*/
|
||||
public function logo() {
|
||||
$logo = aioseo()->options->searchAppearance->global->schema->organizationLogo;
|
||||
if ( $logo ) {
|
||||
return $this->image( $logo, 'organizationLogo' );
|
||||
}
|
||||
|
||||
$imageId = aioseo()->helpers->getSiteLogoId();
|
||||
if ( $imageId ) {
|
||||
return $this->image( $imageId, 'organizationLogo' );
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
namespace AIOSEO\Plugin\Common\Schema\Graphs\KnowledgeGraph;
|
||||
|
||||
// Exit if accessed directly.
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
use \AIOSEO\Plugin\Common\Schema\Graphs;
|
||||
|
||||
/**
|
||||
* Knowledge Graph Person graph class.
|
||||
* This is the main Person graph that can be set to represent the site.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
class KgPerson extends Graphs\Graph {
|
||||
/**
|
||||
* Returns the graph data.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @return array $data The graph data.
|
||||
*/
|
||||
public function get() {
|
||||
if ( 'person' !== aioseo()->options->searchAppearance->global->schema->siteRepresents ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$person = aioseo()->options->searchAppearance->global->schema->person;
|
||||
if ( 'manual' === $person ) {
|
||||
return $this->manual();
|
||||
}
|
||||
|
||||
$person = intval( $person );
|
||||
if ( empty( $person ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$data = [
|
||||
'@type' => 'Person',
|
||||
'@id' => trailingslashit( home_url() ) . '#person',
|
||||
'name' => get_the_author_meta( 'display_name', $person )
|
||||
];
|
||||
|
||||
$avatar = $this->avatar( $person, 'personImage' );
|
||||
if ( $avatar ) {
|
||||
$data['image'] = $avatar;
|
||||
}
|
||||
|
||||
$socialUrls = array_values( $this->getUserProfiles( $person ) );
|
||||
if ( $socialUrls ) {
|
||||
$data['sameAs'] = $socialUrls;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the data for the person if it is set manually.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @return array $data The graph data.
|
||||
*/
|
||||
private function manual() {
|
||||
$data = [
|
||||
'@type' => 'Person',
|
||||
'@id' => trailingslashit( home_url() ) . '#person',
|
||||
'name' => aioseo()->options->searchAppearance->global->schema->personName
|
||||
];
|
||||
|
||||
$logo = aioseo()->options->searchAppearance->global->schema->personLogo;
|
||||
if ( $logo ) {
|
||||
$data['image'] = $logo;
|
||||
}
|
||||
|
||||
$socialUrls = array_values( $this->getOrganizationProfiles() );
|
||||
if ( $socialUrls ) {
|
||||
$data['sameAs'] = $socialUrls;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
<?php
|
||||
namespace AIOSEO\Plugin\Common\Schema\Graphs\Traits;
|
||||
|
||||
// Exit if accessed directly.
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Trait that handles images for the graphs.
|
||||
*
|
||||
* @since 4.2.5
|
||||
*/
|
||||
trait Image {
|
||||
/**
|
||||
* Builds the graph data for a given image with a given schema ID.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @param int $imageId The image ID.
|
||||
* @param string $graphId The graph ID (optional).
|
||||
* @return array $data The image graph data.
|
||||
*/
|
||||
protected function image( $imageId, $graphId = '' ) {
|
||||
$attachmentId = is_string( $imageId ) && ! is_numeric( $imageId ) ? aioseo()->helpers->attachmentUrlToPostId( $imageId ) : $imageId;
|
||||
$imageUrl = wp_get_attachment_image_url( $attachmentId, 'full' );
|
||||
|
||||
$data = [
|
||||
'@type' => 'ImageObject',
|
||||
'url' => $imageUrl ? $imageUrl : $imageId,
|
||||
];
|
||||
|
||||
if ( $graphId ) {
|
||||
$baseUrl = aioseo()->schema->context['url'] ?? aioseo()->helpers->getUrl();
|
||||
$data['@id'] = trailingslashit( $baseUrl ) . '#' . $graphId;
|
||||
}
|
||||
|
||||
if ( ! $attachmentId ) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$metaData = wp_get_attachment_metadata( $attachmentId );
|
||||
if ( $metaData && ! empty( $metaData['width'] ) && ! empty( $metaData['height'] ) ) {
|
||||
$data['width'] = (int) $metaData['width'];
|
||||
$data['height'] = (int) $metaData['height'];
|
||||
}
|
||||
|
||||
$caption = $this->getImageCaption( $attachmentId );
|
||||
if ( ! empty( $caption ) ) {
|
||||
$data['caption'] = $caption;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the image caption.
|
||||
*
|
||||
* @since 4.1.4
|
||||
*
|
||||
* @param int $attachmentId The attachment ID.
|
||||
* @return string The caption.
|
||||
*/
|
||||
private function getImageCaption( $attachmentId ) {
|
||||
$caption = wp_get_attachment_caption( $attachmentId );
|
||||
if ( ! empty( $caption ) ) {
|
||||
return $caption;
|
||||
}
|
||||
|
||||
return get_post_meta( $attachmentId, '_wp_attachment_image_alt', true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the graph data for the avatar of a given user.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @param int $userId The user ID.
|
||||
* @param string $graphId The graph ID.
|
||||
* @return array The graph data.
|
||||
*/
|
||||
protected function avatar( $userId, $graphId ) {
|
||||
if ( ! get_option( 'show_avatars' ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$avatar = get_avatar_data( $userId );
|
||||
if ( ! $avatar['found_avatar'] ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return array_filter( [
|
||||
'@type' => 'ImageObject',
|
||||
'@id' => aioseo()->schema->context['url'] . "#$graphId",
|
||||
'url' => $avatar['url'],
|
||||
'width' => $avatar['width'],
|
||||
'height' => $avatar['height'],
|
||||
'caption' => get_the_author_meta( 'display_name', $userId )
|
||||
] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the graph data for the post's featured image.
|
||||
*
|
||||
* @since 4.2.5
|
||||
*
|
||||
* @return string The featured image URL.
|
||||
*/
|
||||
protected function getFeaturedImage() {
|
||||
$post = aioseo()->helpers->getPost();
|
||||
|
||||
return has_post_thumbnail( $post ) ? $this->image( get_post_thumbnail_id() ) : '';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
namespace AIOSEO\Plugin\Common\Schema\Graphs\WebPage;
|
||||
|
||||
// Exit if accessed directly.
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* AboutPage graph class.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
class AboutPage extends WebPage {
|
||||
/**
|
||||
* The graph type.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $type = 'AboutPage';
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
namespace AIOSEO\Plugin\Common\Schema\Graphs\WebPage;
|
||||
|
||||
// Exit if accessed directly.
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* CheckoutPage graph class.
|
||||
*
|
||||
* @since 4.6.4
|
||||
*/
|
||||
class CheckoutPage extends WebPage {
|
||||
/**
|
||||
* The graph type.
|
||||
*
|
||||
* This value can be overridden by WebPage child graphs that are more specific.
|
||||
*
|
||||
* @since 4.6.4
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $type = 'CheckoutPage';
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
namespace AIOSEO\Plugin\Common\Schema\Graphs\WebPage;
|
||||
|
||||
// Exit if accessed directly.
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* CollectionPage graph class.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
class CollectionPage extends WebPage {
|
||||
/**
|
||||
* The graph type.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $type = 'CollectionPage';
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
namespace AIOSEO\Plugin\Common\Schema\Graphs\WebPage;
|
||||
|
||||
// Exit if accessed directly.
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* ContactPage graph class.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
class ContactPage extends WebPage {
|
||||
/**
|
||||
* The graph type.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $type = 'ContactPage';
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
namespace AIOSEO\Plugin\Common\Schema\Graphs\WebPage;
|
||||
|
||||
// Exit if accessed directly.
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* FAQPage graph class.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
class FAQPage extends WebPage {
|
||||
/**
|
||||
* The graph type.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $type = 'FAQPage';
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
namespace AIOSEO\Plugin\Common\Schema\Graphs\WebPage;
|
||||
|
||||
// Exit if accessed directly.
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* ItemPage graph class.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
class ItemPage extends WebPage {
|
||||
/**
|
||||
* The graph type.
|
||||
*
|
||||
* This value can be overridden by WebPage child graphs that are more specific.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $type = 'ItemPage';
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
namespace AIOSEO\Plugin\Common\Schema\Graphs\WebPage;
|
||||
|
||||
// Exit if accessed directly.
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* MedicalWebPage graph class.
|
||||
*
|
||||
* @since 4.6.4
|
||||
*/
|
||||
class MedicalWebPage extends WebPage {
|
||||
/**
|
||||
* The graph type.
|
||||
*
|
||||
* This value can be overridden by WebPage child graphs that are more specific.
|
||||
*
|
||||
* @since 4.6.4
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $type = 'MedicalWebPage';
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
namespace AIOSEO\Plugin\Common\Schema\Graphs\WebPage;
|
||||
|
||||
// Exit if accessed directly.
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
use AIOSEO\Plugin\Common\Schema\Graphs;
|
||||
|
||||
/**
|
||||
* Person Author graph class.
|
||||
* This a secondary Person graph for post authors and BuddyPress profile pages.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
class PersonAuthor extends Graphs\Graph {
|
||||
/**
|
||||
* Returns the graph data.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @param int $userId The user ID.
|
||||
* @return array $data The graph data.
|
||||
*/
|
||||
public function get( $userId = null ) {
|
||||
$post = aioseo()->helpers->getPost();
|
||||
$user = get_queried_object();
|
||||
$isAuthorPage = is_author() && is_a( $user, 'WP_User' );
|
||||
if (
|
||||
(
|
||||
( ! is_singular() && ! $isAuthorPage ) ||
|
||||
( is_singular() && ! is_a( $post, 'WP_Post' ) )
|
||||
) &&
|
||||
! $userId
|
||||
) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Dynamically determine the User ID.
|
||||
if ( ! $userId ) {
|
||||
$userId = $isAuthorPage ? $user->ID : $post->post_author;
|
||||
if ( function_exists( 'bp_is_user' ) && bp_is_user() ) {
|
||||
$userId = intval( wp_get_current_user()->ID );
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! $userId ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$authorUrl = get_author_posts_url( $userId );
|
||||
|
||||
$data = [
|
||||
'@type' => 'Person',
|
||||
'@id' => $authorUrl . '#author',
|
||||
'url' => $authorUrl,
|
||||
'name' => get_the_author_meta( 'display_name', $userId )
|
||||
];
|
||||
|
||||
$avatar = $this->avatar( $userId, 'authorImage' );
|
||||
if ( $avatar ) {
|
||||
$data['image'] = $avatar;
|
||||
}
|
||||
|
||||
$socialUrls = array_values( $this->getUserProfiles( $userId ) );
|
||||
if ( $socialUrls ) {
|
||||
$data['sameAs'] = $socialUrls;
|
||||
}
|
||||
|
||||
if ( is_author() ) {
|
||||
$data['mainEntityOfPage'] = [
|
||||
'@id' => aioseo()->schema->context['url'] . '#profilepage'
|
||||
];
|
||||
}
|
||||
|
||||
// Check if our addons need to modify this graph.
|
||||
$addonsPersonAuthorData = array_filter( aioseo()->addons->doAddonFunction( 'personAuthor', 'get', [
|
||||
'userId' => $userId,
|
||||
'data' => $data
|
||||
] ) );
|
||||
|
||||
foreach ( $addonsPersonAuthorData as $addonPersonAuthorData ) {
|
||||
$data = array_merge( $data, $addonPersonAuthorData );
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
namespace AIOSEO\Plugin\Common\Schema\Graphs\WebPage;
|
||||
|
||||
// Exit if accessed directly.
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
use AIOSEO\Plugin\Common\Integrations\BuddyPress as BuddyPressIntegration;
|
||||
|
||||
/**
|
||||
* ProfilePage graph class.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
class ProfilePage extends WebPage {
|
||||
/**
|
||||
* The graph type.
|
||||
*
|
||||
* @since 4.5.6
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $type = 'ProfilePage';
|
||||
|
||||
/**
|
||||
* Returns the graph data.
|
||||
*
|
||||
* @since 4.5.4
|
||||
*
|
||||
* @return array The graph data.
|
||||
*/
|
||||
public function get() {
|
||||
$data = parent::get();
|
||||
|
||||
$post = aioseo()->helpers->getPost();
|
||||
$author = get_queried_object();
|
||||
if (
|
||||
! is_a( $author, 'WP_User' ) &&
|
||||
( is_singular() && ! is_a( $post, 'WP_Post' ) )
|
||||
) {
|
||||
return [];
|
||||
}
|
||||
|
||||
global $wp_query; // phpcs:ignore Squiz.NamingConventions.ValidVariableName
|
||||
$articles = [];
|
||||
$authorId = $author->ID ?? $post->post_author ?? 0;
|
||||
foreach ( $wp_query->posts as $post ) { // phpcs:ignore Squiz.NamingConventions.ValidVariableName
|
||||
if ( $post->post_author !== $authorId ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$articles[] = [
|
||||
'@type' => 'Article',
|
||||
'url' => get_permalink( $post->ID ),
|
||||
'headline' => $post->post_title,
|
||||
'datePublished' => mysql2date( DATE_W3C, $post->post_date, false ),
|
||||
'dateModified' => mysql2date( DATE_W3C, $post->post_modified, false ),
|
||||
'author' => [
|
||||
'@id' => get_author_posts_url( $authorId ) . '#author'
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
$data = array_merge( $data, [
|
||||
'dateCreated' => mysql2date( DATE_W3C, $author->user_registered, false ),
|
||||
'mainEntity' => [
|
||||
'@id' => get_author_posts_url( $authorId ) . '#author'
|
||||
],
|
||||
'hasPart' => $articles
|
||||
|
||||
] );
|
||||
|
||||
if (
|
||||
BuddyPressIntegration::isComponentPage() &&
|
||||
'bp-member_single' === aioseo()->standalone->buddyPress->component->templateType
|
||||
) {
|
||||
if ( ! isset( $data['mainEntity'] ) ) {
|
||||
$data['mainEntity'] = [];
|
||||
}
|
||||
|
||||
$data['mainEntity']['@type'] = 'Person';
|
||||
$data['mainEntity']['name'] = aioseo()->standalone->buddyPress->component->author->display_name;
|
||||
$data['mainEntity']['url'] = BuddyPressIntegration::getComponentSingleUrl( 'member', aioseo()->standalone->buddyPress->component->author->ID );
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
namespace AIOSEO\Plugin\Common\Schema\Graphs\WebPage;
|
||||
|
||||
// Exit if accessed directly.
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* RealEstateListing graph class.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
class RealEstateListing extends WebPage {
|
||||
/**
|
||||
* The graph type.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $type = 'RealEstateListing';
|
||||
|
||||
/**
|
||||
* Returns the graph data.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @return array $data The graph data.
|
||||
*/
|
||||
public function get() {
|
||||
$data = parent::get();
|
||||
$post = aioseo()->helpers->getPost();
|
||||
if ( ! $post ) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$data['datePosted'] = mysql2date( DATE_W3C, $post->post_date, false );
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
namespace AIOSEO\Plugin\Common\Schema\Graphs\WebPage;
|
||||
|
||||
// Exit if accessed directly.
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* SearchResultsPage graph class.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
class SearchResultsPage extends WebPage {
|
||||
/**
|
||||
* The graph type.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $type = 'SearchResultsPage';
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
namespace AIOSEO\Plugin\Common\Schema\Graphs\WebPage;
|
||||
|
||||
// Exit if accessed directly.
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
use AIOSEO\Plugin\Common\Schema\Graphs;
|
||||
|
||||
/**
|
||||
* WebPage graph class.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
class WebPage extends Graphs\Graph {
|
||||
/**
|
||||
* The graph type.
|
||||
*
|
||||
* This value can be overridden by WebPage child graphs that are more specific.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $type = 'WebPage';
|
||||
|
||||
/**
|
||||
* Returns the graph data.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @return array $data The graph data.
|
||||
*/
|
||||
public function get() {
|
||||
$homeUrl = trailingslashit( home_url() );
|
||||
$data = [
|
||||
'@type' => $this->type,
|
||||
'@id' => aioseo()->schema->context['url'] . '#' . strtolower( $this->type ),
|
||||
'url' => aioseo()->schema->context['url'],
|
||||
'name' => aioseo()->meta->title->getTitle(),
|
||||
'description' => aioseo()->schema->context['description'],
|
||||
'inLanguage' => aioseo()->helpers->currentLanguageCodeBCP47(),
|
||||
'isPartOf' => [ '@id' => $homeUrl . '#website' ],
|
||||
'breadcrumb' => [ '@id' => aioseo()->schema->context['url'] . '#breadcrumblist' ]
|
||||
];
|
||||
|
||||
if ( is_singular() && 'page' !== get_post_type() ) {
|
||||
$post = aioseo()->helpers->getPost();
|
||||
if ( is_a( $post, 'WP_Post' ) && post_type_supports( $post->post_type, 'author' ) ) {
|
||||
$author = get_author_posts_url( $post->post_author );
|
||||
if ( ! empty( $author ) ) {
|
||||
if ( ! in_array( 'PersonAuthor', aioseo()->schema->graphs, true ) ) {
|
||||
aioseo()->schema->graphs[] = 'PersonAuthor';
|
||||
}
|
||||
|
||||
$data['author'] = [ '@id' => $author . '#author' ];
|
||||
$data['creator'] = [ '@id' => $author . '#author' ];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( isset( aioseo()->schema->context['description'] ) && aioseo()->schema->context['description'] ) {
|
||||
$data['description'] = aioseo()->schema->context['description'];
|
||||
}
|
||||
|
||||
if ( is_singular() ) {
|
||||
if ( ! isset( aioseo()->schema->context['object'] ) || ! aioseo()->schema->context['object'] ) {
|
||||
$data = $this->getAddonData( $data, 'webPage' );
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
$post = aioseo()->schema->context['object'];
|
||||
if ( has_post_thumbnail( $post ) ) {
|
||||
$image = $this->image( get_post_thumbnail_id(), 'mainImage' );
|
||||
if ( $image ) {
|
||||
$data['image'] = $image;
|
||||
$data['primaryImageOfPage'] = [
|
||||
'@id' => aioseo()->schema->context['url'] . '#mainImage'
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$data['datePublished'] = mysql2date( DATE_W3C, $post->post_date, false );
|
||||
$data['dateModified'] = mysql2date( DATE_W3C, $post->post_modified, false );
|
||||
|
||||
$data = $this->getAddonData( $data, 'webPage' );
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
if ( is_front_page() ) {
|
||||
$data['about'] = [ '@id' => trailingslashit( home_url() ) . '#' . aioseo()->options->searchAppearance->global->schema->siteRepresents ];
|
||||
}
|
||||
|
||||
$data = $this->getAddonData( $data, 'webPage' );
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
namespace AIOSEO\Plugin\Common\Schema\Graphs;
|
||||
|
||||
// Exit if accessed directly.
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* WebSite graph class.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
class WebSite extends Graph {
|
||||
/**
|
||||
* Returns the graph data.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @return array $data The graph data.
|
||||
*/
|
||||
public function get() {
|
||||
$homeUrl = trailingslashit( home_url() );
|
||||
$data = [
|
||||
'@type' => 'WebSite',
|
||||
'@id' => $homeUrl . '#website',
|
||||
'url' => $homeUrl,
|
||||
'name' => aioseo()->helpers->getWebsiteName(),
|
||||
'alternateName' => aioseo()->tags->replaceTags( aioseo()->options->searchAppearance->global->schema->websiteAlternateName ),
|
||||
'description' => aioseo()->helpers->decodeHtmlEntities( get_bloginfo( 'description' ) ),
|
||||
'inLanguage' => aioseo()->helpers->currentLanguageCodeBCP47(),
|
||||
'publisher' => [ '@id' => $homeUrl . '#' . aioseo()->options->searchAppearance->global->schema->siteRepresents ]
|
||||
];
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
namespace AIOSEO\Plugin\Common\Schema;
|
||||
|
||||
// Exit if accessed directly.
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Contains helper methods for our schema classes.
|
||||
*
|
||||
* @since 4.2.5
|
||||
*/
|
||||
class Helpers {
|
||||
/**
|
||||
* Checks whether the schema markup feature is enabled.
|
||||
*
|
||||
* @since 4.2.5
|
||||
*
|
||||
* @return bool Whether the schema markup feature is enabled or not.
|
||||
*/
|
||||
public function isEnabled() {
|
||||
$isEnabled = ! in_array( 'enableSchemaMarkup', aioseo()->internalOptions->deprecatedOptions, true ) || aioseo()->options->deprecated->searchAppearance->global->schema->enableSchemaMarkup;
|
||||
|
||||
return ! apply_filters( 'aioseo_schema_disable', ! $isEnabled );
|
||||
}
|
||||
|
||||
/**
|
||||
* Strips HTML and removes all blank properties in each of our graphs.
|
||||
* Also parses properties that might contain smart tags.
|
||||
*
|
||||
* @since 4.0.13
|
||||
* @version 4.2.5
|
||||
*
|
||||
* @param array $data The graph data.
|
||||
* @param string $parentKey The key of the group parent (optional).
|
||||
* @param bool $replaceTags Whether the smart tags should be replaced.
|
||||
* @return array The cleaned graph data.
|
||||
*/
|
||||
public function cleanAndParseData( $data, $parentKey = '', $replaceTags = true ) {
|
||||
foreach ( $data as $k => &$v ) {
|
||||
if ( is_numeric( $v ) || is_bool( $v ) || is_null( $v ) ) {
|
||||
// Do nothing.
|
||||
} elseif ( is_array( $v ) ) {
|
||||
$v = $this->cleanAndParseData( $v, $k, $replaceTags );
|
||||
} else {
|
||||
// Check if the prop can contain some HTML tags.
|
||||
if (
|
||||
isset( aioseo()->schema->htmlAllowedFields[ $parentKey ] ) &&
|
||||
in_array( $k, aioseo()->schema->htmlAllowedFields[ $parentKey ], true )
|
||||
) {
|
||||
$v = trim( wp_kses_post( $v ) );
|
||||
} else {
|
||||
$v = trim( wp_strip_all_tags( $v ) );
|
||||
}
|
||||
|
||||
$v = $replaceTags ? aioseo()->tags->replaceTags( $v, get_the_ID() ) : $v;
|
||||
}
|
||||
|
||||
if ( empty( $v ) && ! in_array( $k, aioseo()->schema->nullableFields, true ) ) {
|
||||
unset( $data[ $k ] );
|
||||
} else {
|
||||
$data[ $k ] = $v;
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts the schema data and then returns it as JSON.
|
||||
* We temporarily change the floating point precision in order to prevent rounding errors.
|
||||
* Otherwise e.g. 4.9 could be output as 4.90000004.
|
||||
*
|
||||
* @since 4.2.7
|
||||
*
|
||||
* @param array $schema The schema data.
|
||||
* @param bool $replaceTags Whether the smart tags should be replaced.
|
||||
* @return string The schema as JSON.
|
||||
*/
|
||||
public function getOutput( $schema, $replaceTags = true ) {
|
||||
$schema['@graph'] = apply_filters( 'aioseo_schema_output', $schema['@graph'] );
|
||||
$schema['@graph'] = $this->cleanAndParseData( $schema['@graph'], '', $replaceTags );
|
||||
|
||||
// Sort the graphs alphabetically.
|
||||
usort( $schema['@graph'], function ( $a, $b ) {
|
||||
if ( is_array( $a['@type'] ) ) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if ( is_array( $b['@type'] ) ) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return strcmp( $a['@type'], $b['@type'] );
|
||||
} );
|
||||
|
||||
// Allow users to control the default json_encode flags.
|
||||
// Some users report better SEO performance when non-Latin unicode characters are not escaped.
|
||||
$jsonFlags = apply_filters( 'aioseo_schema_json_flags', 0 );
|
||||
|
||||
$json = isset( $_GET['aioseo-dev'] ) || aioseo()->schema->generatingValidatorOutput // phpcs:ignore HM.Security.NonceVerification.Recommended, WordPress.Security.NonceVerification.Recommended
|
||||
? aioseo()->helpers->wpJsonEncode( $schema, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE )
|
||||
: aioseo()->helpers->wpJsonEncode( $schema, $jsonFlags );
|
||||
|
||||
return $json;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,385 @@
|
||||
<?php
|
||||
namespace AIOSEO\Plugin\Common\Schema;
|
||||
|
||||
// Exit if accessed directly.
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
use AIOSEO\Plugin\Common\Integrations\BuddyPress as BuddyPressIntegration;
|
||||
use AIOSEO\Plugin\Common\Integrations\BbPress as BbPressIntegration;
|
||||
|
||||
/**
|
||||
* Builds our schema.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
class Schema {
|
||||
/**
|
||||
* The graphs that need to be generated.
|
||||
*
|
||||
* @since 4.2.5
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $graphs = [];
|
||||
|
||||
/**
|
||||
* The context data.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $context = [];
|
||||
|
||||
/**
|
||||
* Helpers class instance.
|
||||
*
|
||||
* @since 4.2.7
|
||||
*
|
||||
* @var Helpers
|
||||
*/
|
||||
public $helpers = null;
|
||||
|
||||
/**
|
||||
* The subdirectories that contain graph classes.
|
||||
*
|
||||
* @since 4.2.5
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $graphSubDirectories = [
|
||||
'Article',
|
||||
'KnowledgeGraph',
|
||||
'WebPage'
|
||||
];
|
||||
|
||||
/**
|
||||
* All existing WebPage graphs.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $webPageGraphs = [
|
||||
'WebPage',
|
||||
'AboutPage',
|
||||
'CheckoutPage',
|
||||
'CollectionPage',
|
||||
'ContactPage',
|
||||
'FAQPage',
|
||||
'ItemPage',
|
||||
'MedicalWebPage',
|
||||
'ProfilePage',
|
||||
'RealEstateListing',
|
||||
'SearchResultsPage'
|
||||
];
|
||||
|
||||
/**
|
||||
* Fields that can be 0 or null, which shouldn't be stripped when cleaning the data.
|
||||
*
|
||||
* @since 4.1.2
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $nullableFields = [
|
||||
'price', // Needs to be 0 if free for Software Application.
|
||||
'ratingValue', // Needs to be 0 for 0 star ratings.
|
||||
'value', // Needs to be 0 if free for product shipping details.
|
||||
'minValue', // Needs to be 0 for product delivery time.
|
||||
'maxValue', // Needs to be 0 for product delivery time.
|
||||
'suggestedMinAge' // Needs to be 0 for PeopleAudience minimum age.
|
||||
];
|
||||
|
||||
/**
|
||||
* List of mapped parents with properties that are allowed to contain a restricted set of HTML tags.
|
||||
*
|
||||
* @since 4.2.3
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $htmlAllowedFields = [
|
||||
// FAQPage
|
||||
'acceptedAnswer' => [
|
||||
'text'
|
||||
]
|
||||
];
|
||||
|
||||
/**
|
||||
* Whether we are generating the validator output.
|
||||
*
|
||||
* @since 4.6.3
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $generatingValidatorOutput = false;
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
// No AJAX check since we need to be able to grab the schema output via the REST API.
|
||||
if ( wp_doing_cron() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->helpers = new Helpers();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the JSON schema output.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*
|
||||
* @return string The JSON schema output.
|
||||
*/
|
||||
public function get() {
|
||||
// First, check if the schema is disabled.
|
||||
if ( ! $this->helpers->isEnabled() ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$this->determineSmartGraphsAndContext();
|
||||
|
||||
return $this->generateSchema();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the JSON schema after the graphs/context have been determined.
|
||||
*
|
||||
* @since 4.2.5
|
||||
*
|
||||
* @return string The JSON schema output.
|
||||
*/
|
||||
protected function generateSchema() {
|
||||
// Now, filter the graphs.
|
||||
$this->graphs = apply_filters(
|
||||
'aioseo_schema_graphs',
|
||||
array_unique( array_filter( array_values( $this->graphs ) ) )
|
||||
);
|
||||
|
||||
if ( ! $this->graphs ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Check if a WebPage graph is included. Otherwise add the default one.
|
||||
$webPageGraphFound = false;
|
||||
foreach ( $this->graphs as $graphName ) {
|
||||
if ( in_array( $graphName, $this->webPageGraphs, true ) ) {
|
||||
$webPageGraphFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! $webPageGraphFound ) {
|
||||
$this->graphs[] = 'WebPage';
|
||||
}
|
||||
|
||||
// Now that we've determined the graphs, start generating their data.
|
||||
$schema = [
|
||||
'@context' => 'https://schema.org',
|
||||
'@graph' => []
|
||||
];
|
||||
|
||||
// By determining the length of the array after every iteration, we are able to add additional graphs during runtime.
|
||||
// e.g. The Article graph may require a Person graph to be output for the author.
|
||||
$this->graphs = array_values( $this->graphs );
|
||||
for ( $i = 0; $i < count( $this->graphs ); $i++ ) {
|
||||
$namespace = $this->getGraphNamespace( $this->graphs[ $i ] );
|
||||
if ( $namespace ) {
|
||||
$schema['@graph'][] = ( new $namespace() )->get();
|
||||
}
|
||||
}
|
||||
|
||||
return aioseo()->schema->helpers->getOutput( $schema );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the relevant namespace for the given graph.
|
||||
*
|
||||
* @since 4.2.5
|
||||
*
|
||||
* @param string $graphName The graph name.
|
||||
* @return string The namespace.
|
||||
*/
|
||||
protected function getGraphNamespace( $graphName ) {
|
||||
$namespace = "\AIOSEO\Plugin\Common\Schema\Graphs\\{$graphName}";
|
||||
if ( class_exists( $namespace ) ) {
|
||||
return $namespace;
|
||||
}
|
||||
|
||||
// If we can't find it in the root dir, check if we can find it in a sub dir.
|
||||
foreach ( $this->graphSubDirectories as $dirName ) {
|
||||
$namespace = "\AIOSEO\Plugin\Common\Schema\Graphs\\{$dirName}\\{$graphName}";
|
||||
if ( class_exists( $namespace ) ) {
|
||||
return $namespace;
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the smart graphs that need to be output by default, as well as the current context for the breadcrumbs.
|
||||
*
|
||||
* @since 4.2.5
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function determineSmartGraphsAndContext() {
|
||||
$this->graphs = array_merge( $this->graphs, $this->getDefaultGraphs() );
|
||||
|
||||
$contextInstance = new Context();
|
||||
$this->context = $contextInstance->defaults();
|
||||
|
||||
if ( BuddyPressIntegration::isComponentPage() ) {
|
||||
aioseo()->standalone->buddyPress->component->determineSchemaGraphsAndContext( $contextInstance );
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ( BbPressIntegration::isComponentPage() ) {
|
||||
aioseo()->standalone->bbPress->component->determineSchemaGraphsAndContext();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ( aioseo()->helpers->isDynamicHomePage() ) {
|
||||
$this->graphs[] = 'CollectionPage';
|
||||
$this->context = $contextInstance->home();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ( is_home() || aioseo()->helpers->isWooCommerceShopPage() ) {
|
||||
$this->graphs[] = 'CollectionPage';
|
||||
$this->context = $contextInstance->post();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ( is_singular() ) {
|
||||
$this->determineContextSingular( $contextInstance );
|
||||
|
||||
if ( is_singular( 'web-story' ) ) {
|
||||
$this->graphs[] = 'AmpStory';
|
||||
}
|
||||
}
|
||||
|
||||
if ( is_category() || is_tag() || is_tax() ) {
|
||||
$this->graphs[] = 'CollectionPage';
|
||||
$this->context = $contextInstance->term();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ( is_author() ) {
|
||||
$this->graphs[] = 'ProfilePage';
|
||||
$this->graphs[] = 'PersonAuthor';
|
||||
$this->context = $contextInstance->author();
|
||||
}
|
||||
|
||||
if ( is_post_type_archive() ) {
|
||||
$this->graphs[] = 'CollectionPage';
|
||||
$this->context = $contextInstance->postArchive();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ( is_date() ) {
|
||||
$this->graphs[] = 'CollectionPage';
|
||||
$this->context = $contextInstance->date();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ( is_search() ) {
|
||||
$this->graphs[] = 'SearchResultsPage';
|
||||
$this->context = $contextInstance->search();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ( is_404() ) {
|
||||
$this->context = $contextInstance->notFound();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the smart graphs and context for singular pages.
|
||||
*
|
||||
* @since 4.2.6
|
||||
*
|
||||
* @param Context $contextInstance The Context class instance.
|
||||
* @return void
|
||||
*/
|
||||
protected function determineContextSingular( $contextInstance ) {
|
||||
// If the current request is for the validator, we can't include the default graph here.
|
||||
// We need to include the default graph that the validator sent.
|
||||
// Don't do this if we're in Pro since we then need to get it from the post meta.
|
||||
if ( ! $this->generatingValidatorOutput ) {
|
||||
$this->graphs[] = $this->getDefaultPostGraph();
|
||||
}
|
||||
|
||||
$this->context = $contextInstance->post();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the default graph for the post type.
|
||||
*
|
||||
* @since 4.2.6
|
||||
*
|
||||
* @return string The default graph.
|
||||
*/
|
||||
public function getDefaultPostGraph() {
|
||||
return $this->getDefaultPostTypeGraph();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the default graph for the current post type.
|
||||
*
|
||||
* @since 4.2.5
|
||||
*
|
||||
* @param \WP_Post $post The post object.
|
||||
* @return string The default graph.
|
||||
*/
|
||||
public function getDefaultPostTypeGraph( $post = null ) {
|
||||
$post = $post ? $post : aioseo()->helpers->getPost();
|
||||
if ( ! is_a( $post, 'WP_Post' ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$dynamicOptions = aioseo()->dynamicOptions->noConflict();
|
||||
if ( ! $dynamicOptions->searchAppearance->postTypes->has( $post->post_type ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$defaultType = $dynamicOptions->searchAppearance->postTypes->{$post->post_type}->schemaType;
|
||||
switch ( $defaultType ) {
|
||||
case 'Article':
|
||||
return $dynamicOptions->searchAppearance->postTypes->{$post->post_type}->articleType;
|
||||
case 'WebPage':
|
||||
return $dynamicOptions->searchAppearance->postTypes->{$post->post_type}->webPageType;
|
||||
default:
|
||||
return $defaultType;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the default graphs that should be output on every page, regardless of its type.
|
||||
*
|
||||
* @since 4.2.5
|
||||
*
|
||||
* @return array The default graphs.
|
||||
*/
|
||||
protected function getDefaultGraphs() {
|
||||
$siteRepresents = ucfirst( aioseo()->options->searchAppearance->global->schema->siteRepresents );
|
||||
|
||||
return [
|
||||
'BreadcrumbList',
|
||||
'Kg' . $siteRepresents,
|
||||
'WebSite'
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user