Your IP : 216.73.216.162


Current Path : /home/x/b/o/xbodynamge/namtation/wp-content/
Upload File :
Current File : /home/x/b/o/xbodynamge/namtation/wp-content/Schema.tar

Schema.php000066600000021213151146256020006462 0ustar00<?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'
		];
	}
}Context.php000066600000014327151146256020006716 0ustar00<?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
			] )
		];
	}
}Breadcrumb.php000066600000023613151146256020007336 0ustar00<?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';
	}
}Graphs/WebPage/RealEstateListing.php000066600000001244151146256020013405 0ustar00<?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;
	}
}Graphs/WebPage/FAQPage.php000066600000000476151146256020011234 0ustar00<?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';
}Graphs/WebPage/MedicalWebPage.php000066600000000650151146256020012613 0ustar00<?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';
}Graphs/WebPage/AboutPage.php000066600000000504151146256020011667 0ustar00<?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';
}Graphs/WebPage/CollectionPage.php000066600000000523151146256020012711 0ustar00<?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';
}Graphs/WebPage/WebPage.php000066600000005577151146256020011351 0ustar00<?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' ]
		];

		$breadcrumbs = aioseo()->breadcrumbs->frontend->getBreadcrumbs() ?? '';
		if ( ! empty( $breadcrumbs ) ) {
			$data['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'] ) {
				return $this->getAddonData( $data, 'webPage' );
			}

			$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 );

			return $this->getAddonData( $data, 'webPage' );
		}

		if ( is_front_page() ) {
			$data['about'] = [ '@id' => trailingslashit( home_url() ) . '#' . aioseo()->options->searchAppearance->global->schema->siteRepresents ];
		}

		return $this->getAddonData( $data, 'webPage' );
	}
}Graphs/WebPage/CheckoutPage.php000066600000000642151146256020012365 0ustar00<?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';
}Graphs/WebPage/SearchResultsPage.php000066600000000534151146256020013407 0ustar00<?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';
}Graphs/WebPage/ContactPage.php000066600000000512151146256020012207 0ustar00<?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';
}Graphs/WebPage/ProfilePage.php000066600000004274151146256020012225 0ustar00<?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;
	}
}Graphs/WebPage/ItemPage.php000066600000000626151146256020011520 0ustar00<?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';
}Graphs/WebPage/PersonAuthor.php000066600000004016151146256020012453 0ustar00<?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;
	}
}Graphs/Traits/Image.php000066600000005462151146256020011006 0ustar00<?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() ) : '';
	}
}Graphs/KnowledgeGraph/KgOrganization.php000066600000005336151146256020014345 0ustar00<?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 [];
	}
}Graphs/KnowledgeGraph/KgPerson.php000066600000003457151146256020013151 0ustar00<?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;
	}
}Graphs/WebSite.php000066600000001747151146256020010062 0ustar00<?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;
	}
}Graphs/BreadcrumbList.php000066600000004205151146256020011412 0ustar00<?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()->breadcrumbs->frontend->getBreadcrumbs() ?? '';
		if ( ! $breadcrumbs ) {
			return [];
		}

		// Set the position for each breadcrumb.
		foreach ( $breadcrumbs as $k => $breadcrumb ) {
			if ( ! isset( $breadcrumb['position'] ) ) {
				$breadcrumbs[ $k ]['position'] = $k + 1;
			}
		}

		$trailLength = count( $breadcrumbs );
		if ( ! $trailLength ) {
			return [];
		}

		$listItems = [];
		foreach ( $breadcrumbs as $breadcrumb ) {
			if ( empty( $breadcrumb['link'] ) ) {
				continue;
			}

			$listItem = [
				'@type'    => 'ListItem',
				'@id'      => $breadcrumb['link'] . '#listItem',
				'position' => $breadcrumb['position'],
				'name'     => $breadcrumb['label'] ?? ''
			];

			// Don't add "item" prop for last crumb.
			if ( $trailLength !== $breadcrumb['position'] ) {
				$listItem['item'] = $breadcrumb['link'];
			}

			if ( 1 === $trailLength ) {
				$listItems[] = $listItem;
				continue;
			}

			if ( $trailLength > $breadcrumb['position'] && ! empty( $breadcrumbs[ $breadcrumb['position'] ]['label'] ) ) {
				$listItem['nextItem'] = [
					'@type' => 'ListItem',
					'@id'   => $breadcrumbs[ $breadcrumb['position'] ]['link'] . '#listItem',
					'name'  => $breadcrumbs[ $breadcrumb['position'] ]['label'],
				];
			}

			if ( 1 < $breadcrumb['position'] && ! empty( $breadcrumbs[ $breadcrumb['position'] - 2 ]['label'] ) ) {
				$listItem['previousItem'] = [
					'@type' => 'ListItem',
					'@id'   => $breadcrumbs[ $breadcrumb['position'] - 2 ]['link'] . '#listItem',
					'name'  => $breadcrumbs[ $breadcrumb['position'] - 2 ]['label'],
				];
			}

			$listItems[] = $listItem;
		}

		$data = [
			'@type'           => 'BreadcrumbList',
			'@id'             => aioseo()->schema->context['url'] . '#breadcrumblist',
			'itemListElement' => $listItems
		];

		return $data;
	}
}Graphs/Graph.php000066600000004542151146256020007555 0ustar00<?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;
	}
}Graphs/Article/NewsArticle.php000066600000002315151146256020012313 0ustar00<?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;
	}
}Graphs/Article/BlogPosting.php000066600000001307151146256020012322 0ustar00<?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;
	}
}Graphs/Article/Article.php000066600000011770151146256020011463 0ustar00<?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 [];
	}
}Graphs/AmpStory.php000066600000002471151146256020010271 0ustar00<?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;
	}
}Helpers.php000066600000006776151146256020006705 0ustar00<?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 ) {
			$typeA = $a['@type'] ?? null;
			$typeB = $b['@type'] ?? null;

			if ( is_null( $typeA ) || is_array( $typeA ) ) {
				return 1;
			}

			if ( is_null( $typeB ) || is_array( $typeB ) ) {
				return -1;
			}

			return strcmp( $typeA, $typeB );
		} );

		// 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;
	}
}