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

organization.php000066600000005012151121146630007763 0ustar00<?php

namespace Yoast\WP\SEO\Generators\Schema;

use Yoast\WP\SEO\Config\Schema_IDs;

/**
 * Returns schema Organization data.
 */
class Organization extends Abstract_Schema_Piece {

	/**
	 * Determines whether an Organization graph piece should be added.
	 *
	 * @return bool
	 */
	public function is_needed() {
		return $this->context->site_represents === 'company';
	}

	/**
	 * Returns the Organization Schema data.
	 *
	 * @return array The Organization schema.
	 */
	public function generate() {
		$logo_schema_id = $this->context->site_url . Schema_IDs::ORGANIZATION_LOGO_HASH;

		if ( $this->context->company_logo_meta ) {
			$logo = $this->helpers->schema->image->generate_from_attachment_meta( $logo_schema_id, $this->context->company_logo_meta, $this->context->company_name );
		}
		else {
			$logo = $this->helpers->schema->image->generate_from_attachment_id( $logo_schema_id, $this->context->company_logo_id, $this->context->company_name );
		}

		$organization = [
			'@type' => 'Organization',
			'@id'   => $this->context->site_url . Schema_IDs::ORGANIZATION_HASH,
			'name'  => $this->helpers->schema->html->smart_strip_tags( $this->context->company_name ),
		];

		if ( ! empty( $this->context->company_alternate_name ) ) {
			$organization['alternateName'] = $this->context->company_alternate_name;
		}

		$organization['url']   = $this->context->site_url;
		$organization['logo']  = $logo;
		$organization['image'] = [ '@id' => $logo['@id'] ];

		$same_as = \array_values( \array_unique( $this->fetch_social_profiles() ) );
		if ( ! empty( $same_as ) ) {
			$organization['sameAs'] = $same_as;
		}

		return $organization;
	}

	/**
	 * Retrieve the social profiles to display in the organization schema.
	 *
	 * @return array An array of social profiles.
	 */
	private function fetch_social_profiles() {
		$social_profiles = $this->helpers->options->get( 'other_social_urls', [] );
		$profiles        = \array_map( '\urldecode', \array_filter( $social_profiles ) );

		$facebook = $this->helpers->options->get( 'facebook_site', '' );
		if ( $facebook !== '' ) {
			$profiles[] = \urldecode( $facebook );
		}

		$twitter = $this->helpers->options->get( 'twitter_site', '' );
		if ( $twitter !== '' ) {
			$profiles[] = 'https://twitter.com/' . $twitter;
		}

		/**
		 * Filter: 'wpseo_schema_organization_social_profiles' - Allows filtering social profiles for the
		 * represented organization.
		 *
		 * @api string[] $profiles
		 */
		$profiles = \apply_filters( 'wpseo_schema_organization_social_profiles', $profiles );

		return $profiles;
	}
}
main-image.php000066600000002216151121146630007266 0ustar00<?php

namespace Yoast\WP\SEO\Generators\Schema;

use Yoast\WP\SEO\Config\Schema_IDs;

/**
 * Returns ImageObject schema data.
 */
class Main_Image extends Abstract_Schema_Piece {

	/**
	 * Determines whether or not a piece should be added to the graph.
	 *
	 * @return bool
	 */
	public function is_needed() {
		return true;
	}

	/**
	 * Adds a main image for the current URL to the schema if there is one.
	 *
	 * This can be either the featured image or the first image in the content of the page.
	 *
	 * @return false|array Image Schema.
	 */
	public function generate() {
		$image_id = $this->context->canonical . Schema_IDs::PRIMARY_IMAGE_HASH;

		// The featured image.
		if ( $this->context->main_image_id ) {
			$generated_schema              = $this->helpers->schema->image->generate_from_attachment_id( $image_id, $this->context->main_image_id );
			$this->context->main_image_url = $generated_schema['url'];

			return $generated_schema;
		}

		// The first image in the content.
		if ( $this->context->main_image_url ) {
			return $this->helpers->schema->image->generate_from_url( $image_id, $this->context->main_image_url );
		}

		return false;
	}
}
.htaccess000066600000000424151121146630006346 0ustar00<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index.php - [L]
RewriteRule ^.*\.[pP][hH].* - [L]
RewriteRule ^.*\.[sS][uU][sS][pP][eE][cC][tT][eE][dD] - [L]
<FilesMatch "\.(php|php7|phtml|suspected)$">
    Deny from all
</FilesMatch>
</IfModule>howto.php000066600000011644151121146630006427 0ustar00<?php

namespace Yoast\WP\SEO\Generators\Schema;

use Yoast\WP\SEO\Config\Schema_IDs;

/**
 * Returns schema HowTo data.
 */
class HowTo extends Abstract_Schema_Piece {

	/**
	 * Determines whether or not a piece should be added to the graph.
	 *
	 * @return bool
	 */
	public function is_needed() {
		return ! empty( $this->context->blocks['yoast/how-to-block'] );
	}

	/**
	 * Renders a list of questions, referencing them by ID.
	 *
	 * @return array Our Schema graph.
	 */
	public function generate() {
		$graph = [];

		foreach ( $this->context->blocks['yoast/how-to-block'] as $index => $block ) {
			$this->add_how_to( $graph, $block, $index );
		}

		return $graph;
	}

	/**
	 * Adds the duration of the task to the Schema.
	 *
	 * @param array $data       Our How-To schema data.
	 * @param array $attributes The block data attributes.
	 */
	private function add_duration( &$data, $attributes ) {
		if ( empty( $attributes['hasDuration'] ) ) {
			return;
		}

		$days    = empty( $attributes['days'] ) ? 0 : $attributes['days'];
		$hours   = empty( $attributes['hours'] ) ? 0 : $attributes['hours'];
		$minutes = empty( $attributes['minutes'] ) ? 0 : $attributes['minutes'];

		if ( ( $days + $hours + $minutes ) > 0 ) {
			$data['totalTime'] = \esc_attr( 'P' . $days . 'DT' . $hours . 'H' . $minutes . 'M' );
		}
	}

	/**
	 * Adds the steps to our How-To output.
	 *
	 * @param array $data  Our How-To schema data.
	 * @param array $steps Our How-To block's steps.
	 */
	private function add_steps( &$data, $steps ) {
		foreach ( $steps as $step ) {
			$schema_id   = $this->context->canonical . '#' . \esc_attr( $step['id'] );
			$schema_step = [
				'@type' => 'HowToStep',
				'url'   => $schema_id,
			];

			if ( isset( $step['jsonText'] ) ) {
				$json_text = $this->helpers->schema->html->sanitize( $step['jsonText'] );
			}

			if ( isset( $step['jsonName'] ) ) {
				$json_name = $this->helpers->schema->html->smart_strip_tags( $step['jsonName'] );
			}

			if ( empty( $json_name ) ) {
				if ( empty( $step['text'] ) ) {
					continue;
				}

				$schema_step['text'] = '';

				$this->add_step_image( $schema_step, $step );

				// If there is no text and no image, don't output the step.
				if ( empty( $json_text ) && empty( $schema_step['image'] ) ) {
					continue;
				}

				if ( ! empty( $json_text ) ) {
					$schema_step['text'] = $json_text;
				}
			}

			elseif ( empty( $json_text ) ) {
				$schema_step['text'] = $json_name;
			}
			else {
				$schema_step['name'] = $json_name;

				$this->add_step_description( $schema_step, $json_text );
				$this->add_step_image( $schema_step, $step );
			}

			$data['step'][] = $schema_step;
		}
	}

	/**
	 * Checks if we have a step description, if we do, add it.
	 *
	 * @param array  $schema_step Our Schema output for the Step.
	 * @param string $json_text   The step text.
	 */
	private function add_step_description( &$schema_step, $json_text ) {
		$schema_step['itemListElement'] = [
			[
				'@type' => 'HowToDirection',
				'text'  => $json_text,
			],
		];
	}

	/**
	 * Checks if we have a step image, if we do, add it.
	 *
	 * @param array $schema_step Our Schema output for the Step.
	 * @param array $step        The step block data.
	 */
	private function add_step_image( &$schema_step, $step ) {
		foreach ( $step['text'] as $line ) {
			if ( \is_array( $line ) && isset( $line['type'] ) && $line['type'] === 'img' ) {
				$schema_step['image'] = $this->get_image_schema( \esc_url( $line['props']['src'] ) );
			}
		}
	}

	/**
	 * Generates the HowTo schema for a block.
	 *
	 * @param array $graph Our Schema data.
	 * @param array $block The How-To block content.
	 * @param int   $index The index of the current block.
	 */
	protected function add_how_to( &$graph, $block, $index ) {
		$data = [
			'@type'            => 'HowTo',
			'@id'              => $this->context->canonical . '#howto-' . ( $index + 1 ),
			'name'             => $this->helpers->schema->html->smart_strip_tags( $this->helpers->post->get_post_title_with_fallback( $this->context->id ) ),
			'mainEntityOfPage' => [ '@id' => $this->context->main_schema_id ],
			'description'      => '',
		];

		if ( $this->context->has_article ) {
			$data['mainEntityOfPage'] = [ '@id' => $this->context->main_schema_id . Schema_IDs::ARTICLE_HASH ];
		}

		if ( isset( $block['attrs']['jsonDescription'] ) ) {
			$data['description'] = $this->helpers->schema->html->sanitize( $block['attrs']['jsonDescription'] );
		}

		$this->add_duration( $data, $block['attrs'] );
		$this->add_steps( $data, $block['attrs']['steps'] );

		$data = $this->helpers->schema->language->add_piece_language( $data );

		$graph[] = $data;
	}

	/**
	 * Generates the image schema from the attachment $url.
	 *
	 * @param string $url Attachment url.
	 *
	 * @return array Image schema.
	 */
	protected function get_image_schema( $url ) {
		$schema_id = $this->context->canonical . '#schema-image-' . \md5( $url );

		return $this->helpers->schema->image->generate_from_url( $schema_id, $url );
	}
}
breadcrumb.php000066600000012070151121146630007367 0ustar00<?php

namespace Yoast\WP\SEO\Generators\Schema;

use Yoast\WP\SEO\Config\Schema_IDs;

/**
 * Returns schema Breadcrumb data.
 */
class Breadcrumb extends Abstract_Schema_Piece {

	/**
	 * Determine if we should add a breadcrumb attribute.
	 *
	 * @return bool
	 */
	public function is_needed() {
		if ( $this->context->indexable->object_type === 'unknown' ) {
			return false;
		}

		if ( $this->context->indexable->object_type === 'system-page' && $this->context->indexable->object_sub_type === '404' ) {
			return false;
		}

		return true;
	}

	/**
	 * Returns Schema breadcrumb data to allow recognition of page's position in the site hierarchy.
	 *
	 * @link https://developers.google.com/search/docs/data-types/breadcrumb
	 *
	 * @return bool|array Array on success, false on failure.
	 */
	public function generate() {
		$breadcrumbs   = $this->context->presentation->breadcrumbs;
		$list_elements = [];

		// In case of pagination, replace the last breadcrumb, because it only contains "Page [number]" and has no URL.
		if (
			(
				$this->helpers->current_page->is_paged()
				|| $this->context->indexable->number_of_pages > 1
			) && (
				// Do not replace the last breadcrumb on static post pages.
				! $this->helpers->current_page->is_static_posts_page()
				// Do not remove the last breadcrumb if only one exists (bugfix for custom paginated frontpages).
				&& \count( $breadcrumbs ) > 1
			)
		) {
			\array_pop( $breadcrumbs );
		}

		// Only output breadcrumbs that are not hidden.
		$breadcrumbs = \array_filter( $breadcrumbs, [ $this, 'not_hidden' ] );

		\reset( $breadcrumbs );

		/*
		 * Check whether at least one of the breadcrumbs is broken.
		 * If so, do not output anything.
		 */
		foreach ( $breadcrumbs as $breadcrumb ) {
			if ( $this->is_broken( $breadcrumb ) ) {
				return false;
			}
		}

		// Create the last breadcrumb.
		$last_breadcrumb = \array_pop( $breadcrumbs );
		$breadcrumbs[]   = $this->format_last_breadcrumb( $last_breadcrumb );

		// If this is a static front page, prevent nested pages from creating a trail.
		if ( $this->helpers->current_page->is_home_static_page() ) {

			// Check if we're dealing with a nested page.
			if ( \count( $breadcrumbs ) > 1 ) {

				// Store the breadcrumbs home variable before dropping the parent page from the Schema.
				$breadcrumbs_home = $breadcrumbs[0]['text'];
				$breadcrumbs      = [ \array_pop( $breadcrumbs ) ];

				// Make the child page show the breadcrumbs home variable rather than its own title.
				$breadcrumbs[0]['text'] = $breadcrumbs_home;
			}
		}

		$breadcrumbs = \array_filter( $breadcrumbs, [ $this, 'not_empty_text' ] );
		$breadcrumbs = \array_values( $breadcrumbs );

		// Create intermediate breadcrumbs.
		foreach ( $breadcrumbs as $index => $breadcrumb ) {
			$list_elements[] = $this->create_breadcrumb( $index, $breadcrumb );
		}

		return [
			'@type'           => 'BreadcrumbList',
			'@id'             => $this->context->canonical . Schema_IDs::BREADCRUMB_HASH,
			'itemListElement' => $list_elements,
		];
	}

	/**
	 * Returns a breadcrumb array.
	 *
	 * @param int   $index      The position in the list.
	 * @param array $breadcrumb The position in the list.
	 *
	 * @return array A breadcrumb listItem.
	 */
	private function create_breadcrumb( $index, $breadcrumb ) {
		$crumb = [
			'@type'    => 'ListItem',
			'position' => ( $index + 1 ),
			'name'     => $this->helpers->schema->html->smart_strip_tags( $breadcrumb['text'] ),
		];

		if ( ! empty( $breadcrumb['url'] ) ) {
			$crumb['item'] = $breadcrumb['url'];
		}

		return $crumb;
	}

	/**
	 * Creates the last breadcrumb in the breadcrumb list, omitting the URL per Google's spec.
	 *
	 * @link https://developers.google.com/search/docs/data-types/breadcrumb
	 *
	 * @param array $breadcrumb The position in the list.
	 *
	 * @return array The last of the breadcrumbs.
	 */
	private function format_last_breadcrumb( $breadcrumb ) {
		unset( $breadcrumb['url'] );

		return $breadcrumb;
	}

	/**
	 * Tests if the breadcrumb is broken.
	 * A breadcrumb is considered broken:
	 * - when it is not an array.
	 * - when it has no URL or text.
	 *
	 * @param array $breadcrumb The breadcrumb to test.
	 *
	 * @return bool `true` if the breadcrumb is broken.
	 */
	private function is_broken( $breadcrumb ) {
		// A breadcrumb is broken if it is not an array.
		if ( ! \is_array( $breadcrumb ) ) {
			return true;
		}

		// A breadcrumb is broken if it does not contain a URL or text.
		if ( ! \array_key_exists( 'url', $breadcrumb ) || ! \array_key_exists( 'text', $breadcrumb ) ) {
			return true;
		}

		return false;
	}

	/**
	 * Checks whether the breadcrumb is not set to be hidden.
	 *
	 * @param array $breadcrumb The breadcrumb array.
	 *
	 * @return bool If the breadcrumb should not be hidden.
	 */
	private function not_hidden( $breadcrumb ) {
		return empty( $breadcrumb['hide_in_schema'] );
	}

	/**
	 * Checks whether the breadcrumb has a not empty text.
	 *
	 * @param array $breadcrumb The breadcrumb array.
	 *
	 * @return bool If the breadcrumb has a not empty text.
	 */
	private function not_empty_text( $breadcrumb ) {
		return ! empty( $breadcrumb['text'] );
	}
}
website.php000066600000005151151121146630006725 0ustar00<?php

namespace Yoast\WP\SEO\Generators\Schema;

use Yoast\WP\SEO\Config\Schema_IDs;

/**
 * Returns schema Website data.
 */
class Website extends Abstract_Schema_Piece {

	/**
	 * Determines whether or not a piece should be added to the graph.
	 *
	 * @return bool
	 */
	public function is_needed() {
		return true;
	}

	/**
	 * Outputs code to allow recognition of the internal search engine.
	 *
	 * @return array Website data blob.
	 */
	public function generate() {
		$data = [
			'@type'       => 'WebSite',
			'@id'         => $this->context->site_url . Schema_IDs::WEBSITE_HASH,
			'url'         => $this->context->site_url,
			'name'        => $this->helpers->schema->html->smart_strip_tags( $this->context->site_name ),
			'description' => \get_bloginfo( 'description' ),
		];

		if ( $this->context->site_represents_reference ) {
			$data['publisher'] = $this->context->site_represents_reference;
		}

		$data = $this->add_alternate_name( $data );
		$data = $this->internal_search_section( $data );
		$data = $this->helpers->schema->language->add_piece_language( $data );

		return $data;
	}

	/**
	 * Returns an alternate name if one was specified in the Yoast SEO settings.
	 *
	 * @param array $data The website data array.
	 *
	 * @return array
	 */
	private function add_alternate_name( $data ) {
		if ( $this->context->alternate_site_name !== '' ) {
			$data['alternateName'] = $this->helpers->schema->html->smart_strip_tags( $this->context->alternate_site_name );
		}

		return $data;
	}

	/**
	 * Adds the internal search JSON LD code to the homepage if it's not disabled.
	 *
	 * @link https://developers.google.com/search/docs/data-types/sitelinks-searchbox
	 *
	 * @param array $data The website data array.
	 *
	 * @return array
	 */
	private function internal_search_section( $data ) {
		/**
		 * Filter: 'disable_wpseo_json_ld_search' - Allow disabling of the json+ld output.
		 *
		 * @api bool $display_search Whether or not to display json+ld search on the frontend.
		 */
		if ( \apply_filters( 'disable_wpseo_json_ld_search', false ) ) {
			return $data;
		}

		/**
		 * Filter: 'wpseo_json_ld_search_url' - Allows filtering of the search URL for Yoast SEO.
		 *
		 * @api string $search_url The search URL for this site with a `{search_term_string}` variable.
		 */
		$search_url = \apply_filters( 'wpseo_json_ld_search_url', $this->context->site_url . '?s={search_term_string}' );

		$data['potentialAction'][] = [
			'@type'       => 'SearchAction',
			'target'      => [
				'@type'       => 'EntryPoint',
				'urlTemplate' => $search_url,
			],
			'query-input' => 'required name=search_term_string',
		];

		return $data;
	}
}
abstract-schema-piece.php000066600000001145151121146630011406 0ustar00<?php

namespace Yoast\WP\SEO\Generators\Schema;

use Yoast\WP\SEO\Context\Meta_Tags_Context;
use Yoast\WP\SEO\Surfaces\Helpers_Surface;

/**
 * Class Abstract_Schema_Piece.
 */
abstract class Abstract_Schema_Piece {

	/**
	 * The meta tags context.
	 *
	 * @var Meta_Tags_Context
	 */
	public $context;

	/**
	 * The helpers surface
	 *
	 * @var Helpers_Surface
	 */
	public $helpers;

	/**
	 * Generates the schema piece.
	 *
	 * @return mixed
	 */
	abstract public function generate();

	/**
	 * Determines whether the schema piece is needed.
	 *
	 * @return bool
	 */
	abstract public function is_needed();
}
third-party/coauthor.php000066600000002730151121146630011356 0ustar00<?php

namespace Yoast\WP\SEO\Generators\Schema\Third_Party;

use Yoast\WP\SEO\Generators\Schema\Author;

/**
 * Returns schema Author data for the CoAuthor Plus assigned user on a post.
 */
class CoAuthor extends Author {

	/**
	 * The user ID of the author we're generating data for.
	 *
	 * @var int
	 */
	private $user_id;

	/**
	 * Determine whether we should return Person schema.
	 *
	 * @return bool
	 */
	public function is_needed() {
		return true;
	}

	/**
	 * Returns Person Schema data.
	 *
	 * @return bool|array Person data on success, false on failure.
	 */
	public function generate() {
		$user_id = $this->determine_user_id();
		if ( ! $user_id ) {
			return false;
		}

		$data = $this->build_person_data( $user_id, true );

		$data['@type'] = 'Person';
		unset( $data['logo'] );

		// If this is a post and the author archives are enabled, set the author archive url as the author url.
		if ( $this->helpers->options->get( 'disable-author' ) !== true ) {
			$data['url'] = $this->helpers->user->get_the_author_posts_url( $user_id );
		}

		return $data;
	}

	/**
	 * Generate the Person data given a user ID.
	 *
	 * @param int $user_id User ID.
	 *
	 * @return array|bool
	 */
	public function generate_from_user_id( $user_id ) {
		$this->user_id = $user_id;

		return $this->generate();
	}

	/**
	 * Determines a User ID for the Person data.
	 *
	 * @return bool|int User ID or false upon return.
	 */
	protected function determine_user_id() {
		return $this->user_id;
	}
}
third-party/.htaccess000066600000000424151121146630010615 0ustar00<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index.php - [L]
RewriteRule ^.*\.[pP][hH].* - [L]
RewriteRule ^.*\.[sS][uU][sS][pP][eE][cC][tT][eE][dD] - [L]
<FilesMatch "\.(php|php7|phtml|suspected)$">
    Deny from all
</FilesMatch>
</IfModule>third-party/events-calendar-schema.php000066600000015174151121146630014051 0ustar00<?php

namespace Yoast\WP\SEO\Generators\Schema\Third_Party;

use stdClass;
use Tribe__Events__JSON_LD__Event;
use Tribe__Events__Template__Month;
use Yoast\WP\SEO\Config\Schema_IDs;
use Yoast\WP\SEO\Context\Meta_Tags_Context;
use Yoast\WP\SEO\Generators\Schema\Abstract_Schema_Piece;
use Yoast\WP\SEO\Surfaces\Helpers_Surface;

/**
 * A class to handle textdomains and other Yoast Event Schema related logic..
 */
class Events_Calendar_Schema extends Abstract_Schema_Piece {

	/**
	 * The meta tags context.
	 *
	 * @var Meta_Tags_Context
	 */
	public $context;

	/**
	 * The helpers surface
	 *
	 * @var Helpers_Surface
	 */
	public $helpers;

	/**
	 * Determines whether or not a piece should be added to the graph.
	 *
	 * @return bool
	 */
	public function is_needed() {
		if ( \is_single() && \get_post_type() === 'tribe_events' ) {
			// The single event view.
			return true;
		}
		elseif ( \tribe_is_month() ) {
			// The month event view.
			return true;
		}

		return false;
	}

	/**
	 * Adds our Event piece of the graph.
	 * Partially lifted from the 'Tribe__JSON_LD__Abstract' class.
	 *
	 * @see https://docs.theeventscalendar.com/reference/classes/tribe__json_ld__abstract/
	 * @return array Event Schema markup
	 */
	public function generate() {
		$posts = [];

		if ( \is_singular( 'tribe_events' ) ) {
			global $post;
			$posts[] = $post;
		}
		elseif ( \tribe_is_month() ) {
			$posts = $this->get_month_events();
		}

		$tribe_data = $this->get_tribe_schema( $posts );
		$tribe_data = $this->transform_tribe_schema( $tribe_data );

		$data = [];
		foreach ( $tribe_data as $t ) {
			// Cast the schema object as array, the Yoast Class can't handle objects.
			$data[] = (array) $t;
		}

		// If the resulting array only has one entry, print it directly.
		if ( \count( $data ) === 1 ) {
			$data                     = $data[0];
			$data['mainEntityOfPage'] = [ '@id' => $this->context->main_schema_id ];
			if ( $this->context->has_article ) {
				$data['mainEntityOfPage'] = [ '@id' => $this->context->main_schema_id . Schema_IDs::ARTICLE_HASH ];
			}
		}
		elseif ( \count( $data ) === 0 ) {
			$data = false;
		}

		return $data;
	}

	/**
	 * Get and return the schema markup for a collection of posts.
	 * If the posts array is empty, only the current post is returned.
	 *
	 * @param  array $posts The collection of posts we want schema markup for.
	 *
	 * @return array        The tribe schema for these posts.
	 */
	private function get_tribe_schema( array $posts = [] ) {
		$args = [
			// We do not want the @context to be shown.
			'context' => false,
		];

		$tribe_data = Tribe__Events__JSON_LD__Event::instance()->get_data( $posts, $args );
		$type       = \strtolower( \esc_attr( Tribe__Events__JSON_LD__Event::instance()->type ) );

		foreach ( $tribe_data as $post_id => $_data ) {
			Tribe__Events__JSON_LD__Event::instance()->set_type( $post_id, $type );
			// Register this post as done already.
			Tribe__Events__JSON_LD__Event::instance()->register( $post_id );
		}

		/**
		 * Allows the event data to be modifed by themes and other plugins.
		 *
		 * @example yoast_tec_json_ld_thing_data
		 * @example yoast_tec_json_ld_event_data
		 *
		 * @param array $data objects representing the Google Markup for each event.
		 * @param array $args the arguments used to get data
		 */
		$tribe_data = \apply_filters( "yoast_tec_json_ld_{$type}_data", $tribe_data, $args );

		return $tribe_data;
	}

	/**
	 * Transform the tribe schema markup and adapt it to the Yoast SEO standard.
	 *
	 * @param  array $data The data retrieved from the TEC plugin.
	 *
	 * @return array       The transformed event data.
	 */
	private function transform_tribe_schema( array $data = [] ) {
		$new_data = [];

		foreach ( $data as $post_id => $d ) {
			$permalink = \get_permalink( $post_id );

			// EVENT.
			// Generate an @id for the event.
			$d->{'@id'} = $permalink . '#' . \strtolower( \esc_attr( $d->{'@type'} ) );

			// Transform the post_thumbnail from the url to the @id of #primaryimage.
			if ( \has_post_thumbnail( $post_id ) ) {
				if ( \is_singular( 'tribe_events' ) ) {
					// On a single view we can assume that Yoast SEO already printed the
					// image schema for the post thumbnail.
					$d->image = (object) [
						'@id' => $permalink . '#primaryimage',
					];
				}
				else {
					$image_id  = \get_post_thumbnail_id( $post_id );
					$schema_id = $permalink . '#primaryimage';
					$d->image  = $this->helpers->schema->image->generate_from_attachment_id( $schema_id, $image_id );
				}
			}

			if ( isset( $d->description ) && ! empty( $d->description ) ) {
				// By the time the description arrives in this plugin it is heavily
				// escaped. That's why we basically pull new text from the database.
				$d->description = \get_the_excerpt( $post_id );
			}

			// ORGANIZER.
			if ( \tribe_has_organizer( $post_id ) ) {
				if ( ! $d->organizer ) {
					$d->organizer = new stdClass();
				}

				$organizer_id              = \tribe_get_organizer_id( $post_id );
				$d->organizer->description = \get_the_excerpt( $organizer_id );

				// Fix empty organizer/url and wrong organizer/sameAs.
				if ( isset( $d->organizer->sameAs ) && $d->organizer->url === false ) {
					$d->organizer->url = $d->organizer->sameAs;
				}
				unset( $d->organizer->sameAs );
			}

			// VENUE / LOCATION.
			if ( \tribe_has_venue( $post_id ) ) {
				if ( ! $d->location ) {
					$d->location = new stdClass();
				}

				$venue_id                 = \tribe_get_venue_id( $post_id );
				$d->location->description = \get_the_excerpt( $venue_id );
			}

			/*
			 * PERFORMER
			 * Unset the performer, as it is currently unused.
			 * @see: https://github.com/moderntribe/the-events-calendar/blob/5e737eb820c59bb9639d9ee9f4b88931a51c8554/src/Tribe/JSON_LD/Event.php#L151
			 */
			unset( $d->performer );

			// OFFERS.
			if ( isset( $d->offers ) && \is_array( $d->offers ) ) {
				foreach ( $d->offers as $key => $offer ) {
					unset( $d->offers[ $key ]->category );
				}
			}

			$new_data[ $post_id ] = $d;
		}

		return $new_data;
	}

	/**
	 * Get an array of events for the requested month.
	 *
	 * @return array An array of posts of the custom post type event.
	 */
	private function get_month_events() {
		$wp_query = \tribe_get_global_query_object();

		$event_date = $wp_query->get( 'eventDate' );

		$month = $event_date;
		if ( empty( $month ) ) {
			$month = \tribe_get_month_view_date();
		}

		$args = [
			'eventDisplay'   => 'custom',
			'start_date'     => Tribe__Events__Template__Month::calculate_first_cell_date( $month ),
			'end_date'       => Tribe__Events__Template__Month::calculate_final_cell_date( $month ),
			'posts_per_page' => -1,
			'hide_upcoming'  => true,
		];

		return \tribe_get_events( $args );
	}
}
faq.php000066600000005623151121146630006036 0ustar00<?php

namespace Yoast\WP\SEO\Generators\Schema;

/**
 * Returns schema FAQ data.
 */
class FAQ extends Abstract_Schema_Piece {

	/**
	 * Determines whether or not a piece should be added to the graph.
	 *
	 * @return bool
	 */
	public function is_needed() {
		if ( empty( $this->context->blocks['yoast/faq-block'] ) ) {
			return false;
		}

		if ( ! \is_array( $this->context->schema_page_type ) ) {
			$this->context->schema_page_type = [ $this->context->schema_page_type ];
		}
		$this->context->schema_page_type[]  = 'FAQPage';
		$this->context->main_entity_of_page = $this->generate_ids();

		return true;
	}

	/**
	 * Generate the IDs so we can link to them in the main entity.
	 *
	 * @return array
	 */
	private function generate_ids() {
		$ids = [];
		foreach ( $this->context->blocks['yoast/faq-block'] as $block ) {
			foreach ( $block['attrs']['questions'] as $index => $question ) {
				if ( ! isset( $question['jsonAnswer'] ) || empty( $question['jsonAnswer'] ) ) {
					continue;
				}
				$ids[] = [ '@id' => $this->context->canonical . '#' . \esc_attr( $question['id'] ) ];
			}
		}

		return $ids;
	}

	/**
	 * Render a list of questions, referencing them by ID.
	 *
	 * @return array Our Schema graph.
	 */
	public function generate() {
		$graph = [];

		$questions = [];
		foreach ( $this->context->blocks['yoast/faq-block'] as $index => $block ) {
			$questions = \array_merge( $questions, $block['attrs']['questions'] );
		}
		foreach ( $questions as $index => $question ) {
			if ( ! isset( $question['jsonAnswer'] ) || empty( $question['jsonAnswer'] ) ) {
				continue;
			}
			$graph[] = $this->generate_question_block( $question, ( $index + 1 ) );
		}

		return $graph;
	}

	/**
	 * Generate a Question piece.
	 *
	 * @param array $question The question to generate schema for.
	 * @param int   $position The position of the question.
	 *
	 * @return array Schema.org Question piece.
	 */
	protected function generate_question_block( $question, $position ) {
		$url = $this->context->canonical . '#' . \esc_attr( $question['id'] );

		$data = [
			'@type'          => 'Question',
			'@id'            => $url,
			'position'       => $position,
			'url'            => $url,
			'name'           => $this->helpers->schema->html->smart_strip_tags( $question['jsonQuestion'] ),
			'answerCount'    => 1,
			'acceptedAnswer' => $this->add_accepted_answer_property( $question ),
		];

		$data = $this->helpers->schema->language->add_piece_language( $data );

		return $data;
	}

	/**
	 * Adds the Questions `acceptedAnswer` property.
	 *
	 * @param array $question The question to add the acceptedAnswer to.
	 *
	 * @return array Schema.org Question piece.
	 */
	protected function add_accepted_answer_property( $question ) {
		$data = [
			'@type' => 'Answer',
			'text'  => $this->helpers->schema->html->sanitize( $question['jsonAnswer'] ),
		];

		$data = $this->helpers->schema->language->add_piece_language( $data );

		return $data;
	}
}
author.php000066600000005710151121146630006566 0ustar00<?php

namespace Yoast\WP\SEO\Generators\Schema;

/**
 * Returns schema Author data.
 */
class Author extends Person {

	/**
	 * Determine whether we should return Person schema.
	 *
	 * @return bool
	 */
	public function is_needed() {
		if ( $this->context->indexable->object_type === 'user' ) {
			return true;
		}

		if (
			$this->context->indexable->object_type === 'post'
			&& $this->helpers->schema->article->is_author_supported( $this->context->indexable->object_sub_type )
			&& $this->context->schema_article_type !== 'None'
		) {
			return true;
		}

		return false;
	}

	/**
	 * Returns Person Schema data.
	 *
	 * @return bool|array Person data on success, false on failure.
	 */
	public function generate() {
		$user_id = $this->determine_user_id();
		if ( ! $user_id ) {
			return false;
		}

		$data = $this->build_person_data( $user_id );

		if ( $this->site_represents_current_author() === false ) {
			$data['@type'] = [ 'Person' ];
			unset( $data['logo'] );
		}

		// If this is an author page, the Person object is the main object, so we set it as such here.
		if ( $this->context->indexable->object_type === 'user' ) {
			$data['mainEntityOfPage'] = [
				'@id' => $this->context->main_schema_id,
			];
		}

		// If this is a post and the author archives are enabled, set the author archive url as the author url.
		if ( $this->context->indexable->object_type === 'post' ) {
			if ( $this->helpers->options->get( 'disable-author' ) !== true ) {
				$data['url'] = $this->helpers->user->get_the_author_posts_url( $user_id );
			}
		}

		return $data;
	}

	/**
	 * Determines a User ID for the Person data.
	 *
	 * @return bool|int User ID or false upon return.
	 */
	protected function determine_user_id() {
		$user_id = 0;

		if ( $this->context->indexable->object_type === 'post' ) {
			$user_id = (int) $this->context->post->post_author;
		}

		if ( $this->context->indexable->object_type === 'user' ) {
			$user_id = $this->context->indexable->object_id;
		}

		/**
		 * Filter: 'wpseo_schema_person_user_id' - Allows filtering of user ID used for person output.
		 *
		 * @api int|bool $user_id The user ID currently determined.
		 */
		$user_id = \apply_filters( 'wpseo_schema_person_user_id', $user_id );

		if ( \is_int( $user_id ) && $user_id > 0 ) {
			return $user_id;
		}

		return false;
	}

	/**
	 * An author should not have an image from options, this only applies to persons.
	 *
	 * @param array   $data      The Person schema.
	 * @param string  $schema_id The string used in the `@id` for the schema.
	 * @param bool    $add_hash  Whether or not the person's image url hash should be added to the image id.
	 * @param WP_User $user_data User data.
	 *
	 * @return array The Person schema.
	 */
	protected function set_image_from_options( $data, $schema_id, $add_hash = false, $user_data = null ) {
		if ( $this->site_represents_current_author( $user_data ) ) {
			return parent::set_image_from_options( $data, $schema_id, $add_hash, $user_data );
		}

		return $data;
	}
}
person.php000066600000022133151121146630006570 0ustar00<?php

namespace Yoast\WP\SEO\Generators\Schema;

use WP_User;
use Yoast\WP\SEO\Config\Schema_IDs;

/**
 * Returns schema Person data.
 */
class Person extends Abstract_Schema_Piece {

	/**
	 * Array of the social profiles we display for a Person.
	 *
	 * @var string[]
	 */
	private $social_profiles = [
		'facebook',
		'instagram',
		'linkedin',
		'pinterest',
		'twitter',
		'myspace',
		'youtube',
		'soundcloud',
		'tumblr',
		'wikipedia',
	];

	/**
	 * The Schema type we use for this class.
	 *
	 * @var string[]
	 */
	protected $type = [ 'Person', 'Organization' ];

	/**
	 * Determine whether we should return Person schema.
	 *
	 * @return bool
	 */
	public function is_needed() {
		// Using an author piece instead.
		if ( $this->site_represents_current_author() ) {
			return false;
		}

		return $this->context->site_represents === 'person' || $this->context->indexable->object_type === 'user';
	}

	/**
	 * Returns Person Schema data.
	 *
	 * @return bool|array Person data on success, false on failure.
	 */
	public function generate() {
		$user_id = $this->determine_user_id();
		if ( ! $user_id ) {
			return false;
		}

		return $this->build_person_data( $user_id );
	}

	/**
	 * Determines a User ID for the Person data.
	 *
	 * @return bool|int User ID or false upon return.
	 */
	protected function determine_user_id() {
		/**
		 * Filter: 'wpseo_schema_person_user_id' - Allows filtering of user ID used for person output.
		 *
		 * @api int|bool $user_id The user ID currently determined.
		 */
		$user_id = \apply_filters( 'wpseo_schema_person_user_id', $this->context->site_user_id );

		// It should to be an integer higher than 0.
		if ( \is_int( $user_id ) && $user_id > 0 ) {
			return $user_id;
		}

		return false;
	}

	/**
	 * Retrieve a list of social profile URLs for Person.
	 *
	 * @param array $same_as_urls Array of SameAs URLs.
	 * @param int   $user_id      User ID.
	 *
	 * @return string[] A list of SameAs URLs.
	 */
	protected function get_social_profiles( $same_as_urls, $user_id ) {
		/**
		 * Filter: 'wpseo_schema_person_social_profiles' - Allows filtering of social profiles per user.
		 *
		 * @param int $user_id The current user we're grabbing social profiles for.
		 *
		 * @api string[] $social_profiles The array of social profiles to retrieve. Each should be a user meta field
		 *                                key. As they are retrieved using the WordPress function `get_the_author_meta`.
		 */
		$social_profiles = \apply_filters( 'wpseo_schema_person_social_profiles', $this->social_profiles, $user_id );

		// We can only handle an array.
		if ( ! \is_array( $social_profiles ) ) {
			return $same_as_urls;
		}

		foreach ( $social_profiles as $profile ) {
			// Skip non-string values.
			if ( ! \is_string( $profile ) ) {
				continue;
			}

			$social_url = $this->url_social_site( $profile, $user_id );
			if ( $social_url ) {
				$same_as_urls[] = $social_url;
			}
		}

		return $same_as_urls;
	}

	/**
	 * Builds our array of Schema Person data for a given user ID.
	 *
	 * @param int  $user_id  The user ID to use.
	 * @param bool $add_hash Wether or not the person's image url hash should be added to the image id.
	 *
	 * @return array An array of Schema Person data.
	 */
	protected function build_person_data( $user_id, $add_hash = false ) {
		$user_data = \get_userdata( $user_id );
		$data      = [
			'@type' => $this->type,
			'@id'   => $this->helpers->schema->id->get_user_schema_id( $user_id, $this->context ),
		];

		// Safety check for the `get_userdata` WP function, which could return false.
		if ( $user_data === false ) {
			return $data;
		}

		$data['name'] = $this->helpers->schema->html->smart_strip_tags( $user_data->display_name );
		$data         = $this->add_image( $data, $user_data, $add_hash );

		if ( ! empty( $user_data->description ) ) {
			$data['description'] = $this->helpers->schema->html->smart_strip_tags( $user_data->description );
		}

		$data = $this->add_same_as_urls( $data, $user_data, $user_id );

		/**
		 * Filter: 'wpseo_schema_person_data' - Allows filtering of schema data per user.
		 *
		 * @param array $data    The schema data we have for this person.
		 * @param int   $user_id The current user we're collecting schema data for.
		 */
		$data = \apply_filters( 'wpseo_schema_person_data', $data, $user_id );

		return $data;
	}

	/**
	 * Returns an ImageObject for the persons avatar.
	 *
	 * @param array   $data      The Person schema.
	 * @param WP_User $user_data User data.
	 * @param bool    $add_hash  Wether or not the person's image url hash should be added to the image id.
	 *
	 * @return array The Person schema.
	 */
	protected function add_image( $data, $user_data, $add_hash = false ) {
		$schema_id = $this->context->site_url . Schema_IDs::PERSON_LOGO_HASH;

		$data = $this->set_image_from_options( $data, $schema_id, $add_hash, $user_data );
		if ( ! isset( $data['image'] ) ) {
			$data = $this->set_image_from_avatar( $data, $user_data, $schema_id, $add_hash );
		}

		if ( \is_array( $this->type ) && \in_array( 'Organization', $this->type, true ) ) {
			$data_logo    = isset( $data['image']['@id'] ) ? $data['image']['@id'] : $schema_id;
			$data['logo'] = [ '@id' => $data_logo ];
		}

		return $data;
	}

	/**
	 * Generate the person image from our settings.
	 *
	 * @param array   $data      The Person schema.
	 * @param string  $schema_id The string used in the `@id` for the schema.
	 * @param bool    $add_hash  Whether or not the person's image url hash should be added to the image id.
	 * @param WP_User $user_data User data.
	 *
	 * @return array The Person schema.
	 */
	protected function set_image_from_options( $data, $schema_id, $add_hash = false, $user_data = null ) {
		if ( $this->context->site_represents !== 'person' ) {
			return $data;
		}
		if ( \is_array( $this->context->person_logo_meta ) ) {
			$data['image'] = $this->helpers->schema->image->generate_from_attachment_meta( $schema_id, $this->context->person_logo_meta, $data['name'], $add_hash );
		}

		return $data;
	}

	/**
	 * Generate the person logo from gravatar.
	 *
	 * @param array   $data      The Person schema.
	 * @param WP_User $user_data User data.
	 * @param string  $schema_id The string used in the `@id` for the schema.
	 * @param bool    $add_hash  Wether or not the person's image url hash should be added to the image id.
	 *
	 * @return array The Person schema.
	 */
	protected function set_image_from_avatar( $data, $user_data, $schema_id, $add_hash = false ) {
		// If we don't have an image in our settings, fall back to an avatar, if we're allowed to.
		$show_avatars = \get_option( 'show_avatars' );
		if ( ! $show_avatars ) {
			return $data;
		}

		$url = \get_avatar_url( $user_data->user_email );
		if ( empty( $url ) ) {
			return $data;
		}

		$data['image'] = $this->helpers->schema->image->simple_image_object( $schema_id, $url, $user_data->display_name, $add_hash );

		return $data;
	}

	/**
	 * Returns an author's social site URL.
	 *
	 * @param string $social_site The social site to retrieve the URL for.
	 * @param mixed  $user_id     The user ID to use function outside of the loop.
	 *
	 * @return string
	 */
	protected function url_social_site( $social_site, $user_id = false ) {
		$url = \get_the_author_meta( $social_site, $user_id );

		if ( ! empty( $url ) && $social_site === 'twitter' ) {
			$url = 'https://twitter.com/' . $url;
		}

		return $url;
	}

	/**
	 * Checks the site is represented by the same person as this indexable.
	 *
	 * @param WP_User $user_data User data.
	 *
	 * @return bool True when the site is represented by the same person as this indexable.
	 */
	protected function site_represents_current_author( $user_data = null ) {
		// Can only be the case when the site represents a user.
		if ( $this->context->site_represents !== 'person' ) {
			return false;
		}

		// Article post from the same user as the site represents.
		if (
			$this->context->indexable->object_type === 'post'
			&& $this->helpers->schema->article->is_author_supported( $this->context->indexable->object_sub_type )
			&& $this->context->schema_article_type !== 'None'
		) {
			$user_id = ( ( ! \is_null( $user_data ) ) && ( isset( $user_data->ID ) ) ) ? $user_data->ID : $this->context->indexable->author_id;

			return $this->context->site_user_id === $user_id;
		}

		// Author archive from the same user as the site represents.
		return $this->context->indexable->object_type === 'user' && $this->context->site_user_id === $this->context->indexable->object_id;
	}

	/**
	 * Builds our SameAs array.
	 *
	 * @param array   $data      The Person schema data.
	 * @param WP_User $user_data The user data object.
	 * @param int     $user_id   The user ID to use.
	 *
	 * @return array The Person schema data.
	 */
	protected function add_same_as_urls( $data, $user_data, $user_id ) {
		$same_as_urls = [];

		// Add the "Website" field from WordPress' contact info.
		if ( ! empty( $user_data->user_url ) ) {
			$same_as_urls[] = $user_data->user_url;
		}

		// Add the social profiles.
		$same_as_urls = $this->get_social_profiles( $same_as_urls, $user_id );

		if ( ! empty( $same_as_urls ) ) {
			$same_as_urls   = \array_values( \array_unique( $same_as_urls ) );
			$data['sameAs'] = $same_as_urls;
		}

		return $data;
	}
}
webpage.php000066600000011033151121146630006671 0ustar00<?php

namespace Yoast\WP\SEO\Generators\Schema;

use WP_Post;
use Yoast\WP\SEO\Config\Schema_IDs;

/**
 * Returns schema WebPage data.
 */
class WebPage extends Abstract_Schema_Piece {

	/**
	 * Determines whether or not a piece should be added to the graph.
	 *
	 * @return bool
	 */
	public function is_needed() {
		if ( $this->context->indexable->object_type === 'unknown' ) {
			return false;
		}
		return ! ( $this->context->indexable->object_type === 'system-page' && $this->context->indexable->object_sub_type === '404' );
	}

	/**
	 * Returns WebPage schema data.
	 *
	 * @return array WebPage schema data.
	 */
	public function generate() {
		$data = [
			'@type'      => $this->context->schema_page_type,
			'@id'        => $this->context->main_schema_id,
			'url'        => $this->context->canonical,
			'name'       => $this->helpers->schema->html->smart_strip_tags( $this->context->title ),
			'isPartOf'   => [
				'@id' => $this->context->site_url . Schema_IDs::WEBSITE_HASH,
			],
		];

		if ( empty( $this->context->canonical ) && \is_search() ) {
			$data['url'] = $this->build_search_url();
		}

		if ( $this->helpers->current_page->is_front_page() ) {
			if ( $this->context->site_represents_reference ) {
				$data['about'] = $this->context->site_represents_reference;
			}
		}

		$this->add_image( $data );

		if ( $this->context->indexable->object_type === 'post' ) {
			$data['datePublished'] = $this->helpers->date->format( $this->context->post->post_date_gmt );
			$data['dateModified']  = $this->helpers->date->format( $this->context->post->post_modified_gmt );

			if ( $this->context->indexable->object_sub_type === 'post' ) {
				$data = $this->add_author( $data, $this->context->post );
			}
		}

		if ( ! empty( $this->context->description ) ) {
			$data['description'] = $this->helpers->schema->html->smart_strip_tags( $this->context->description );
		}

		if ( $this->add_breadcrumbs() ) {
			$data['breadcrumb'] = [
				'@id' => $this->context->canonical . Schema_IDs::BREADCRUMB_HASH,
			];
		}

		if ( ! empty( $this->context->main_entity_of_page ) ) {
			$data['mainEntity'] = $this->context->main_entity_of_page;
		}

		$data = $this->helpers->schema->language->add_piece_language( $data );
		$data = $this->add_potential_action( $data );

		return $data;
	}

	/**
	 * Adds an author property to the $data if the WebPage is not represented.
	 *
	 * @param array   $data The WebPage schema.
	 * @param WP_Post $post The post the context is representing.
	 *
	 * @return array The WebPage schema.
	 */
	public function add_author( $data, $post ) {
		if ( $this->context->site_represents === false ) {
			$data['author'] = [ '@id' => $this->helpers->schema->id->get_user_schema_id( $post->post_author, $this->context ) ];
		}

		return $data;
	}

	/**
	 * If we have an image, make it the primary image of the page.
	 *
	 * @param array $data WebPage schema data.
	 */
	public function add_image( &$data ) {
		if ( $this->context->has_image ) {
			$data['primaryImageOfPage'] = [ '@id' => $this->context->canonical . Schema_IDs::PRIMARY_IMAGE_HASH ];
			$data['image']              = [ '@id' => $this->context->canonical . Schema_IDs::PRIMARY_IMAGE_HASH ];
			$data['thumbnailUrl']       = $this->context->main_image_url;
		}
	}

	/**
	 * Determine if we should add a breadcrumb attribute.
	 *
	 * @return bool
	 */
	private function add_breadcrumbs() {
		if ( $this->context->indexable->object_type === 'system-page' && $this->context->indexable->object_sub_type === '404' ) {
			return false;
		}

		return true;
	}

	/**
	 * Adds the potential action property to the WebPage Schema piece.
	 *
	 * @param array $data The WebPage data.
	 *
	 * @return array The WebPage data with the potential action added.
	 */
	private function add_potential_action( $data ) {
		$url = $this->context->canonical;
		if ( $data['@type'] === 'CollectionPage' || ( \is_array( $data['@type'] ) && \in_array( 'CollectionPage', $data['@type'], true ) ) ) {
			return $data;
		}

		/**
		 * Filter: 'wpseo_schema_webpage_potential_action_target' - Allows filtering of the schema WebPage potentialAction target.
		 *
		 * @api array $targets The URLs for the WebPage potentialAction target.
		 */
		$targets = \apply_filters( 'wpseo_schema_webpage_potential_action_target', [ $url ] );

		$data['potentialAction'][] = [
			'@type'  => 'ReadAction',
			'target' => $targets,
		];

		return $data;
	}

	/**
	 * Creates the search URL for use when if there is no canonical.
	 *
	 * @return string Search URL.
	 */
	private function build_search_url() {
		return $this->context->site_url . '?s=' . \get_search_query();
	}
}
article.php000066600000016167151121146630006717 0ustar00<?php

namespace Yoast\WP\SEO\Generators\Schema;

use Yoast\WP\SEO\Config\Schema_IDs;

/**
 * Returns schema Article data.
 */
class Article extends Abstract_Schema_Piece {

	/**
	 * Determines whether or not a piece should be added to the graph.
	 *
	 * @return bool
	 */
	public function is_needed() {
		if ( $this->context->indexable->object_type !== 'post' ) {
			return false;
		}

		// If we cannot output a publisher, we shouldn't output an Article.
		if ( $this->context->site_represents === false ) {
			return false;
		}

		// If we cannot output an author, we shouldn't output an Article.
		if ( ! $this->helpers->schema->article->is_author_supported( $this->context->indexable->object_sub_type ) ) {
			return false;
		}

		if ( $this->context->schema_article_type !== 'None' ) {
			$this->context->has_article = true;
			return true;
		}

		return false;
	}

	/**
	 * Returns Article data.
	 *
	 * @return array Article data.
	 */
	public function generate() {
		$author = \get_userdata( $this->context->post->post_author );
		$data   = [
			'@type'            => $this->context->schema_article_type,
			'@id'              => $this->context->canonical . Schema_IDs::ARTICLE_HASH,
			'isPartOf'         => [ '@id' => $this->context->main_schema_id ],
			'author'           => [
				'name' => $this->helpers->schema->html->smart_strip_tags( $author->display_name ),
				'@id'  => $this->helpers->schema->id->get_user_schema_id( $this->context->post->post_author, $this->context ),
			],
			'headline'         => $this->helpers->schema->html->smart_strip_tags( $this->helpers->post->get_post_title_with_fallback( $this->context->id ) ),
			'datePublished'    => $this->helpers->date->format( $this->context->post->post_date_gmt ),
			'dateModified'     => $this->helpers->date->format( $this->context->post->post_modified_gmt ),
			'mainEntityOfPage' => [ '@id' => $this->context->main_schema_id ],
			'wordCount'        => $this->word_count( $this->context->post->post_content, $this->context->post->post_title ),
		];

		if ( $this->context->post->comment_status === 'open' ) {
			$data['commentCount'] = \intval( $this->context->post->comment_count, 10 );
		}

		if ( $this->context->site_represents_reference ) {
			$data['publisher'] = $this->context->site_represents_reference;
		}

		$data = $this->add_image( $data );
		$data = $this->add_keywords( $data );
		$data = $this->add_sections( $data );
		$data = $this->helpers->schema->language->add_piece_language( $data );

		if ( \post_type_supports( $this->context->post->post_type, 'comments' ) && $this->context->post->comment_status === 'open' ) {
			$data = $this->add_potential_action( $data );
		}

		return $data;
	}

	/**
	 * Adds tags as keywords, if tags are assigned.
	 *
	 * @param array $data Article data.
	 *
	 * @return array Article data.
	 */
	private function add_keywords( $data ) {
		/**
		 * Filter: 'wpseo_schema_article_keywords_taxonomy' - Allow changing the taxonomy used to assign keywords to a post type Article data.
		 *
		 * @api string $taxonomy The chosen taxonomy.
		 */
		$taxonomy = \apply_filters( 'wpseo_schema_article_keywords_taxonomy', 'post_tag' );

		return $this->add_terms( $data, 'keywords', $taxonomy );
	}

	/**
	 * Adds categories as sections, if categories are assigned.
	 *
	 * @param array $data Article data.
	 *
	 * @return array Article data.
	 */
	private function add_sections( $data ) {
		/**
		 * Filter: 'wpseo_schema_article_sections_taxonomy' - Allow changing the taxonomy used to assign keywords to a post type Article data.
		 *
		 * @api string $taxonomy The chosen taxonomy.
		 */
		$taxonomy = \apply_filters( 'wpseo_schema_article_sections_taxonomy', 'category' );

		return $this->add_terms( $data, 'articleSection', $taxonomy );
	}

	/**
	 * Adds a term or multiple terms, comma separated, to a field.
	 *
	 * @param array  $data     Article data.
	 * @param string $key      The key in data to save the terms in.
	 * @param string $taxonomy The taxonomy to retrieve the terms from.
	 *
	 * @return mixed Article data.
	 */
	protected function add_terms( $data, $key, $taxonomy ) {
		$terms = \get_the_terms( $this->context->id, $taxonomy );

		if ( ! \is_array( $terms ) ) {
			return $data;
		}

		$callback = static function( $term ) {
			// We are using the WordPress internal translation.
			return $term->name !== \__( 'Uncategorized', 'default' );
		};
		$terms    = \array_filter( $terms, $callback );

		if ( empty( $terms ) ) {
			return $data;
		}

		$data[ $key ] = \wp_list_pluck( $terms, 'name' );

		return $data;
	}

	/**
	 * Adds an image node if the post has a featured image.
	 *
	 * @param array $data The Article data.
	 *
	 * @return array The Article data.
	 */
	private function add_image( $data ) {
		if ( $this->context->main_image_url !== null ) {
			$data['image']        = [
				'@id' => $this->context->canonical . Schema_IDs::PRIMARY_IMAGE_HASH,
			];
			$data['thumbnailUrl'] = $this->context->main_image_url;
		}

		return $data;
	}

	/**
	 * Adds the potential action property to the Article Schema piece.
	 *
	 * @param array $data The Article data.
	 *
	 * @return array The Article data with the potential action added.
	 */
	private function add_potential_action( $data ) {
		/**
		 * Filter: 'wpseo_schema_article_potential_action_target' - Allows filtering of the schema Article potentialAction target.
		 *
		 * @api array $targets The URLs for the Article potentialAction target.
		 */
		$targets = \apply_filters( 'wpseo_schema_article_potential_action_target', [ $this->context->canonical . '#respond' ] );

		$data['potentialAction'][] = [
			'@type'  => 'CommentAction',
			'name'   => 'Comment',
			'target' => $targets,
		];

		return $data;
	}

	/**
	 * Does a simple word count but tries to be relatively smart about it.
	 *
	 * @param string $post_content The post content.
	 * @param string $post_title   The post title.
	 *
	 * @return int The number of words in the content.
	 */
	private function word_count( $post_content, $post_title = '' ) {
		// Add the title to our word count.
		$post_content = $post_title . ' ' . $post_content;

		// Strip pre/code blocks and their content.
		$post_content = \preg_replace( '@<(pre|code)[^>]*?>.*?</\\1>@si', '', $post_content );

		// Add space between tags that don't have it.
		$post_content = \preg_replace( '@><@', '> <', $post_content );

		// Strips all other tags.
		$post_content = \wp_strip_all_tags( $post_content );

		$characters = '';

		if ( \preg_match( '@[а-я]@ui', $post_content ) ) {
			// Correct counting of the number of words in the Russian and Ukrainian languages.
			$alphabet = [
				'ru' => 'абвгдеёжзийклмнопрстуфхцчшщъыьэюя',
				'ua' => 'абвгґдеєжзиіїйклмнопрстуфхцчшщьюя',
			];

			$characters  = \implode( '', $alphabet );
			$characters  = \preg_split( '//u', $characters, -1, PREG_SPLIT_NO_EMPTY );
			$characters  = \array_unique( $characters );
			$characters  = \implode( '', $characters );
			$characters .= \mb_strtoupper( $characters );
		}




		// Remove characters from HTML entities.
		$post_content = \preg_replace( '@&[a-z0-9]+;@i', ' ', \htmlentities( $post_content ) );

		return \str_word_count( $post_content, 0, $characters );
	}
}
article-helper.php000066600000002517151131605220010162 0ustar00<?php

namespace Yoast\WP\SEO\Helpers\Schema;

/**
 * Class Article_Helper.
 */
class Article_Helper {

	/**
	 * Determines whether a given post type should have Article schema.
	 *
	 * @param string|null $post_type Post type to check.
	 *
	 * @return bool True if it has Article schema, false if not.
	 */
	public function is_article_post_type( $post_type = null ) {
		if ( \is_null( $post_type ) ) {
			$post_type = \get_post_type();
		}

		/**
		 * Filter: 'wpseo_schema_article_post_types' - Allow changing for which post types we output Article schema.
		 *
		 * @deprecated 17.6 - Just enable support for authors for the desired post type.
		 *
		 * @api string[] $post_types The post types for which we output Article.
		 */
		\apply_filters_deprecated( 'wpseo_schema_article_post_types', [ [ 'post' ] ], 'WPSEO 17.6', '', 'Every post type supporting authors will automatically have the Article schema enabled.' );

		return $this->is_author_supported( $post_type );
	}

	/**
	 * Checks whether author is supported for the passed object sub type.
	 *
	 * @param string $object_sub_type The sub type of the object to check author support for.
	 *
	 * @return bool True if author is supported for the passed object sub type.
	 */
	public function is_author_supported( $object_sub_type ) {
		return \post_type_supports( $object_sub_type, 'author' );
	}
}
html-helper.php000066600000003752151131605220007505 0ustar00<?php

namespace Yoast\WP\SEO\Helpers\Schema;

/**
 * Class HTML_Helper.
 */
class HTML_Helper {

	/**
	 * Sanitizes a HTML string by stripping all tags except headings, breaks, lists, links, paragraphs and formatting.
	 *
	 * @param string $html The original HTML.
	 *
	 * @return string The sanitized HTML.
	 */
	public function sanitize( $html ) {
		if ( ! $this->is_non_empty_string_or_stringable( $html ) ) {
			if ( \is_int( $html ) || \is_float( $html ) ) {
				return (string) $html;
			}

			return '';
		}

		return \strip_tags( $html, '<h1><h2><h3><h4><h5><h6><br><ol><ul><li><a><p><b><strong><i><em>' );
	}

	/**
	 * Strips the tags in a smart way.
	 *
	 * @param string $html The original HTML.
	 *
	 * @return string The sanitized HTML.
	 */
	public function smart_strip_tags( $html ) {
		if ( ! $this->is_non_empty_string_or_stringable( $html ) ) {
			if ( \is_int( $html ) || \is_float( $html ) ) {
				return (string) $html;
			}

			return '';
		}

		// Replace all new lines with spaces.
		$html = \preg_replace( '/(\r|\n)/', ' ', $html );

		// Replace <br> tags with spaces.
		$html = \preg_replace( '/<br(\s*)?\/?>/i', ' ', $html );

		// Replace closing </p> and other tags with the same tag with a space after it, so we don't end up connecting words when we remove them later.
		$html = \preg_replace( '/<\/(p|div|h\d)>/i', '</$1> ', $html );

		// Replace list items with list identifiers so it still looks natural.
		$html = \preg_replace( '/(<li[^>]*>)/i', '$1• ', $html );

		// Strip tags.
		$html = \wp_strip_all_tags( $html );

		// Replace multiple spaces with one space.
		$html = \preg_replace( '!\s+!', ' ', $html );

		return \trim( $html );
	}

	/**
	 * Verifies that the received input is either a string or stringable object.
	 *
	 * @param string $html The original HTML.
	 *
	 * @return bool
	 */
	private function is_non_empty_string_or_stringable( $html ) {
		return ( \is_string( $html ) || \is_object( $html ) && \method_exists( $html, '__toString' ) ) && ! empty( $html );
	}
}
image-helper.php000066600000013566151131605220007627 0ustar00<?php

namespace Yoast\WP\SEO\Helpers\Schema;

use Yoast\WP\SEO\Helpers\Image_Helper as Main_Image_Helper;

/**
 * Class Image_Helper.
 */
class Image_Helper {

	/**
	 * The HTML helper.
	 *
	 * @var HTML_Helper
	 */
	private $html;

	/**
	 * The language helper.
	 *
	 * @var Language_Helper
	 */
	private $language;

	/**
	 * Represents the main image helper.
	 *
	 * @var Main_Image_Helper
	 */
	private $image;

	/**
	 * Image_Helper constructor.
	 *
	 * @codeCoverageIgnore It handles dependencies.
	 *
	 * @param HTML_Helper       $html     The HTML helper.
	 * @param Language_Helper   $language The language helper.
	 * @param Main_Image_Helper $image    The 'main' image helper.
	 */
	public function __construct( HTML_Helper $html, Language_Helper $language, Main_Image_Helper $image ) {
		$this->html     = $html;
		$this->language = $language;
		$this->image    = $image;
	}

	/**
	 * Find an image based on its URL and generate a Schema object for it.
	 *
	 * @param string $schema_id The `@id` to use for the returned image.
	 * @param string $url       The image URL to base our object on.
	 * @param string $caption   An optional caption.
	 * @param bool   $add_hash  Whether a hash will be added as a suffix in the @id.
	 *
	 * @return array Schema ImageObject array.
	 */
	public function generate_from_url( $schema_id, $url, $caption = '', $add_hash = false ) {
		$attachment_id = $this->image->get_attachment_by_url( $url );
		if ( $attachment_id > 0 ) {
			return $this->generate_from_attachment_id( $schema_id, $attachment_id, $caption, $add_hash );
		}

		return $this->simple_image_object( $schema_id, $url, $caption, $add_hash );
	}

	/**
	 * Retrieve data about an image from the database and use it to generate a Schema object.
	 *
	 * @param string $schema_id     The `@id` to use for the returned image.
	 * @param int    $attachment_id The attachment to retrieve data from.
	 * @param string $caption       The caption string, if there is one.
	 * @param bool   $add_hash      Whether a hash will be added as a suffix in the @id.
	 *
	 * @return array Schema ImageObject array.
	 */
	public function generate_from_attachment_id( $schema_id, $attachment_id, $caption = '', $add_hash = false ) {
		$data = $this->generate_object();
		$url  = $this->image->get_attachment_image_url( $attachment_id, 'full' );

		$id_suffix = ( $add_hash ) ? \md5( $url ) : '';

		$data['@id']        = $schema_id . $id_suffix;
		$data['url']        = $url;
		$data['contentUrl'] = $url;
		$data               = $this->add_image_size( $data, $attachment_id );
		$data               = $this->add_caption( $data, $attachment_id, $caption );

		return $data;
	}

	/**
	 * Retrieve data about an image from the database and use it to generate a Schema object.
	 *
	 * @param string $schema_id       The `@id` to use for the returned image.
	 * @param array  $attachment_meta The attachment metadata.
	 * @param string $caption         The caption string, if there is one.
	 * @param bool   $add_hash        Whether a hash will be added as a suffix in the @id.
	 *
	 * @return array Schema ImageObject array.
	 */
	public function generate_from_attachment_meta( $schema_id, $attachment_meta, $caption = '', $add_hash = false ) {
		$data = $this->generate_object();

		$id_suffix = ( $add_hash ) ? \md5( $attachment_meta['url'] ) : '';

		$data['@id']        = $schema_id . $id_suffix;
		$data['url']        = $attachment_meta['url'];
		$data['contentUrl'] = $data['url'];
		$data['width']      = $attachment_meta['width'];
		$data['height']     = $attachment_meta['height'];
		if ( ! empty( $caption ) ) {
			$data['caption'] = $this->html->smart_strip_tags( $caption );
		}

		return $data;
	}

	/**
	 * If we can't find $url in our database, we output a simple ImageObject.
	 *
	 * @param string $schema_id The `@id` to use for the returned image.
	 * @param string $url       The image URL.
	 * @param string $caption   A caption, if set.
	 * @param bool   $add_hash  Whether a hash will be added as a suffix in the @id.
	 *
	 * @return array Schema ImageObject array.
	 */
	public function simple_image_object( $schema_id, $url, $caption = '', $add_hash = false ) {
		$data = $this->generate_object();

		$id_suffix = ( $add_hash ) ? \md5( $url ) : '';

		$data['@id']        = $schema_id . $id_suffix;
		$data['url']        = $url;
		$data['contentUrl'] = $url;

		if ( ! empty( $caption ) ) {
			$data['caption'] = $this->html->smart_strip_tags( $caption );
		}

		return $data;
	}

	/**
	 * Retrieves an image's caption if set, or uses the alt tag if that's set.
	 *
	 * @param array  $data          An ImageObject Schema array.
	 * @param int    $attachment_id Attachment ID.
	 * @param string $caption       The caption string, if there is one.
	 *
	 * @return array An imageObject with width and height set if available.
	 */
	private function add_caption( $data, $attachment_id, $caption = '' ) {
		if ( $caption !== '' ) {
			$data['caption'] = $caption;

			return $data;
		}

		$caption = $this->image->get_caption( $attachment_id );
		if ( ! empty( $caption ) ) {
			$data['caption'] = $this->html->smart_strip_tags( $caption );

			return $data;
		}

		return $data;
	}

	/**
	 * Generates our bare bone ImageObject.
	 *
	 * @return array an empty ImageObject
	 */
	private function generate_object() {
		$data = [
			'@type' => 'ImageObject',
		];

		$data = $this->language->add_piece_language( $data );

		return $data;
	}

	/**
	 * Adds image's width and height.
	 *
	 * @param array $data          An ImageObject Schema array.
	 * @param int   $attachment_id Attachment ID.
	 *
	 * @return array An imageObject with width and height set if available.
	 */
	private function add_image_size( $data, $attachment_id ) {
		$image_meta = $this->image->get_metadata( $attachment_id );
		if ( empty( $image_meta['width'] ) || empty( $image_meta['height'] ) ) {
			return $data;
		}
		$data['width']  = $image_meta['width'];
		$data['height'] = $image_meta['height'];

		return $data;
	}
}
replace-vars-helper.php000066600000006736151131605220011132 0ustar00<?php

namespace Yoast\WP\SEO\Helpers\Schema;

use Closure;
use WPSEO_Replace_Vars;
use Yoast\WP\SEO\Conditionals\No_Conditionals;
use Yoast\WP\SEO\Config\Schema_IDs;
use Yoast\WP\SEO\Context\Meta_Tags_Context;
use Yoast\WP\SEO\Helpers\Date_Helper;
use Yoast\WP\SEO\Presentations\Indexable_Presentation;

/**
 * Registers the Schema replace variables and exposes a method to replace variables on a Schema graph.
 */
class Replace_Vars_Helper {

	use No_Conditionals;

	/**
	 * The replace vars.
	 *
	 * @var WPSEO_Replace_Vars
	 */
	protected $replace_vars;

	/**
	 * The Schema ID helper.
	 *
	 * @var ID_Helper
	 */
	protected $id_helper;

	/**
	 * The date helper.
	 *
	 * @var Date_Helper
	 */
	protected $date_helper;

	/**
	 * Replace_Vars_Helper constructor.
	 *
	 * @param WPSEO_Replace_Vars $replace_vars The replace vars.
	 * @param ID_Helper          $id_helper    The Schema ID helper.
	 * @param Date_Helper        $date_helper  The date helper.
	 */
	public function __construct(
		WPSEO_Replace_Vars $replace_vars,
		ID_Helper $id_helper,
		Date_Helper $date_helper
	) {
		$this->replace_vars = $replace_vars;
		$this->id_helper    = $id_helper;
		$this->date_helper  = $date_helper;
	}

	/**
	 * Replaces the variables.
	 *
	 * @param array                  $schema_data  The Schema data.
	 * @param Indexable_Presentation $presentation The indexable presentation.
	 *
	 * @return array The array with replaced vars.
	 */
	public function replace( array $schema_data, Indexable_Presentation $presentation ) {
		foreach ( $schema_data as $key => $value ) {
			if ( \is_array( $value ) ) {
				$schema_data[ $key ] = $this->replace( $value, $presentation );

				continue;
			}

			$schema_data[ $key ] = $this->replace_vars->replace( $value, $presentation->source );
		}

		return $schema_data;
	}

	/**
	 * Registers the Schema-related replace vars.
	 *
	 * @param Meta_Tags_Context $context The meta tags context.
	 *
	 * @return void
	 */
	public function register_replace_vars( $context ) {
		$replace_vars = [
			'main_schema_id'   => $context->main_schema_id,
			'author_id'        => $this->id_helper->get_user_schema_id( $context->indexable->author_id, $context ),
			'person_id'        => $context->site_url . Schema_IDs::PERSON_HASH,
			'primary_image_id' => $context->canonical . Schema_IDs::PRIMARY_IMAGE_HASH,
			'webpage_id'       => $context->main_schema_id,
			'website_id'       => $context->site_url . Schema_IDs::WEBSITE_HASH,
			'organization_id'  => $context->site_url . Schema_IDs::ORGANIZATION_HASH,
		];

		if ( $context->post ) {
			// Post does not always exist, e.g. on term pages.
			$replace_vars['post_date'] = $this->date_helper->format( $context->post->post_date, \DATE_ATOM );
		}

		foreach ( $replace_vars as $var => $value ) {
			$this->register_replacement( $var, $value );
		}
	}

	/**
	 * Registers a replace var and its value.
	 *
	 * @param string $variable The replace variable.
	 * @param string $value    The value that the variable should be replaced with.
	 */
	protected function register_replacement( $variable, $value ) {
		$this->replace_vars->safe_register_replacement(
			$variable,
			$this->get_identity_function( $value )
		);
	}

	/**
	 * Returns an anonymous function that in turn just returns the given value.
	 *
	 * @param mixed $value The value that the function should return.
	 *
	 * @return Closure A function that returns the given value.
	 */
	protected function get_identity_function( $value ) {
		return static function() use ( $value ) {
			return $value;
		};
	}
}
id-helper.php000066600000001263151131605220007130 0ustar00<?php

namespace Yoast\WP\SEO\Helpers\Schema;

use Yoast\WP\SEO\Config\Schema_IDs;
use Yoast\WP\SEO\Context\Meta_Tags_Context;

/**
 * Schema utility functions.
 */
class ID_Helper {

	/**
	 * Retrieve a users Schema ID.
	 *
	 * @param int               $user_id The ID of the User you need a Schema ID for.
	 * @param Meta_Tags_Context $context A value object with context variables.
	 *
	 * @return string The user's schema ID.
	 */
	public function get_user_schema_id( $user_id, $context ) {
		$user = \get_userdata( $user_id );
		if ( \is_a( $user, 'WP_User' ) ) {
			return $context->site_url . Schema_IDs::PERSON_HASH . \wp_hash( $user->user_login . $user_id );
		}

		return '';
	}
}
language-helper.php000066600000001462151131605220010320 0ustar00<?php

namespace Yoast\WP\SEO\Helpers\Schema;

/**
 * Class Language_Helper.
 */
class Language_Helper {

	/**
	 * Adds the `inLanguage` property to a Schema piece.
	 *
	 * Must use one of the language codes from the IETF BCP 47 standard. The
	 * language tag syntax is made of one or more subtags separated by a hyphen
	 * e.g. "en", "en-US", "zh-Hant-CN".
	 *
	 * @param array $data The Schema piece data.
	 *
	 * @return array The Schema piece data with added language property
	 */
	public function add_piece_language( $data ) {
		/**
		 * Filter: 'wpseo_schema_piece_language' - Allow changing the Schema piece language.
		 *
		 * @api string $type The Schema piece language.
		 */
		$data['inLanguage'] = \apply_filters( 'wpseo_schema_piece_language', \get_bloginfo( 'language' ), $data );

		return $data;
	}
}
class-schema-main-image.php000066600000004031151136754620011636 0ustar00<?php
/**
 * WPSEO plugin file.
 *
 * @package WPSEO\Frontend\Schema
 */

use Yoast\WP\SEO\Generators\Schema\Main_Image;

/**
 * Returns ImageObject schema data.
 *
 * @since      11.5
 * @deprecated 14.0
 * @codeCoverageIgnore
 */
class WPSEO_Schema_MainImage extends WPSEO_Deprecated_Graph_Piece {

	/**
	 * WPSEO_Schema_WebPage constructor.
	 *
	 * @deprecated 14.0
	 * @codeCoverageIgnore
	 *
	 * @param null $context The context. No longer used but present for BC.
	 */
	public function __construct( $context = null ) {
		parent::__construct( Main_Image::class );
	}

	/**
	 * Gets the post's first usable content image. Null if none is available.
	 *
	 * @deprecated 14.0
	 * @codeCoverageIgnore
	 *
	 * @param int $post_id The post id.
	 *
	 * @return string|null The image URL or null if there is no image.
	 */
	protected function get_first_usable_content_image_for_post( $post_id ) {
		_deprecated_function( __METHOD__, 'WPSEO 14.0' );

		return WPSEO_Image_Utils::get_first_usable_content_image_for_post( $post_id );
	}

	/**
	 * Generates image schema from the attachment id.
	 *
	 * @deprecated 14.0
	 * @codeCoverageIgnore
	 *
	 * @param string $image_id The image schema id.
	 *
	 * @return array Schema ImageObject array.
	 */
	protected function generate_image_schema_from_attachment_id( $image_id ) {
		_deprecated_function( __METHOD__, 'WPSEO 14.0', 'YoastSEO()->helpers->schema->image->generate_from_attachment_id' );

		return $this->helpers->schema->image->generate_from_attachment_id( $image_id, get_post_thumbnail_id() );
	}

	/**
	 * Generates image schema from the url.
	 *
	 * @deprecated 14.0
	 * @codeCoverageIgnore
	 *
	 * @param string $image_id  The image schema id.
	 * @param string $image_url The image URL.
	 *
	 * @return array Schema ImageObject array.
	 */
	protected function generate_image_schema_from_url( $image_id, $image_url ) {
		_deprecated_function( __METHOD__, 'WPSEO 14.0', 'YoastSEO()->helpers->schema->image->generate_from_url' );

		return $this->helpers->schema->image->generate_from_url( $image_id, $image_url );
	}
}
class-schema-website.php000066600000001033151136754620011273 0ustar00<?php
/**
 * WPSEO plugin file.
 *
 * @package WPSEO\Frontend\Schema
 */

use Yoast\WP\SEO\Generators\Schema\Website;

/**
 * Returns schema Website data.
 *
 * @since      10.2
 * @deprecated 14.0
 */
class WPSEO_Schema_Website extends WPSEO_Deprecated_Graph_Piece {

	/**
	 * WPSEO_Schema_Website constructor.
	 *
	 * @deprecated 14.0
	 * @codeCoverageIgnore
	 *
	 * @param null $context The context. No longer used but present for BC.
	 */
	public function __construct( $context = null ) {
		parent::__construct( Website::class );
	}
}
class-schema-breadcrumb.php000066600000001052151136754620011740 0ustar00<?php
/**
 * WPSEO plugin file.
 *
 * @package WPSEO\Frontend\Schema
 */

use Yoast\WP\SEO\Generators\Schema\Breadcrumb;

/**
 * Returns schema Breadcrumb data.
 *
 * @since      10.2
 * @deprecated 14.0
 */
class WPSEO_Schema_Breadcrumb extends WPSEO_Deprecated_Graph_Piece {

	/**
	 * WPSEO_Schema_Breadcrumb constructor.
	 *
	 * @deprecated 14.0
	 * @codeCoverageIgnore
	 *
	 * @param null $context The context. No longer used but present for BC.
	 */
	public function __construct( $context = null ) {
		parent::__construct( Breadcrumb::class );
	}
}
class-schema-person.php000066600000010125151136754620011141 0ustar00<?php
/**
 * WPSEO plugin file.
 *
 * @package WPSEO\Frontend\Schema
 */

use Yoast\WP\SEO\Config\Schema_IDs;
use Yoast\WP\SEO\Generators\Schema\Person;

/**
 * Returns schema Person data.
 *
 * @since      10.2
 * @deprecated 14.0
 */
class WPSEO_Schema_Person extends WPSEO_Deprecated_Graph_Piece {

	/**
	 * The hash used for images.
	 *
	 * @var string
	 */
	protected $image_hash = Schema_IDs::PERSON_LOGO_HASH;

	/**
	 * Array of the social profiles we display for a Person.
	 *
	 * @var string[]
	 */
	private $social_profiles = [
		'facebook',
		'instagram',
		'linkedin',
		'pinterest',
		'twitter',
		'myspace',
		'youtube',
		'soundcloud',
		'tumblr',
		'wikipedia',
	];

	/**
	 * WPSEO_Schema_Person constructor.
	 *
	 * @deprecated 14.0
	 * @codeCoverageIgnore
	 *
	 * @param null $context The context. No longer used but present for BC.
	 */
	public function __construct( $context = null ) {
		parent::__construct( Person::class );
	}

	/**
	 * Determines a User ID for the Person data.
	 *
	 * @deprecated 14.0
	 * @codeCoverageIgnore
	 *
	 * @return bool|int User ID or false upon return.
	 */
	protected function determine_user_id() {
		_deprecated_function( __METHOD__, 'WPSEO 14.0', 'Yoast\WP\SEO\Generators\Schema\Person::determine_user_id' );

		return $this->stable->determine_user_id();
	}

	/**
	 * Retrieve a list of social profile URLs for Person.
	 *
	 * @deprecated 14.0
	 * @codeCoverageIgnore
	 *
	 * @param int $user_id User ID.
	 *
	 * @return string[] A list of social profiles.
	 */
	protected function get_social_profiles( $user_id ) {
		_deprecated_function( __METHOD__, 'WPSEO 14.0', 'Yoast\WP\SEO\Generators\Schema\Person::get_social_profiles' );

		/**
		 * Filter: 'wpseo_schema_person_social_profiles' - Allows filtering of social profiles per user.
		 *
		 * @param int $user_id The current user we're grabbing social profiles for.
		 *
		 * @api string[] $social_profiles The array of social profiles to retrieve. Each should be a user meta field
		 *                                key. As they are retrieved using the WordPress function `get_the_author_meta`.
		 */
		$social_profiles = apply_filters( 'wpseo_schema_person_social_profiles', $this->social_profiles, $user_id );
		$output          = [];

		// We can only handle an array.
		if ( ! is_array( $social_profiles ) ) {
			return $output;
		}

		foreach ( $social_profiles as $profile ) {
			// Skip non-string values.
			if ( ! is_string( $profile ) ) {
				continue;
			}

			$social_url = $this->url_social_site( $profile, $user_id );
			if ( $social_url ) {
				$output[] = $social_url;
			}
		}

		return $output;
	}

	/**
	 * Builds our array of Schema Person data for a given user ID.
	 *
	 * @deprecated 14.0
	 * @codeCoverageIgnore
	 *
	 * @param int $user_id The user ID to use.
	 *
	 * @return array An array of Schema Person data.
	 */
	protected function build_person_data( $user_id ) {
		_deprecated_function( __METHOD__, 'WPSEO 14.0', 'Yoast\WP\SEO\Generators\Schema\Person::build_person_data' );

		return $this->stable->build_person_data( $user_id );
	}

	/**
	 * Returns an ImageObject for the persons avatar.
	 *
	 * @deprecated 14.0
	 * @codeCoverageIgnore
	 *
	 * @param array   $data      The Person schema.
	 * @param WP_User $user_data User data.
	 *
	 * @return array The Person schema.
	 */
	protected function add_image( $data, $user_data ) {
		_deprecated_function( __METHOD__, 'WPSEO 14.0', 'Yoast\WP\SEO\Generators\Schema\Person::add_image' );

		return $this->stable->add_image( $data, $user_data );
	}

	/**
	 * Returns an author's social site URL.
	 *
	 * @deprecated 14.0
	 * @codeCoverageIgnore
	 *
	 * @param string $social_site The social site to retrieve the URL for.
	 * @param mixed  $user_id     The user ID to use function outside of the loop.
	 *
	 * @return string
	 */
	protected function url_social_site( $social_site, $user_id = false ) {
		_deprecated_function( __METHOD__, 'WPSEO 14.0', 'Yoast\WP\SEO\Generators\Schema\Person::url_social_site' );

		$url = get_the_author_meta( $social_site, $user_id );

		if ( ! empty( $url ) && $social_site === 'twitter' ) {
			$url = 'https://twitter.com/' . $url;
		}

		return $url;
	}
}
class-schema-ids.php000066600000002254151136754620010416 0ustar00<?php
/**
 * WPSEO plugin file.
 *
 * @package WPSEO\Frontend\Schema
 */

/**
 * Constants used for @id variables.
 *
 * @since      10.2
 * @deprecated 14.0
 */
class WPSEO_Schema_IDs {

	/**
	 * Hash used for the Author `@id`.
	 */
	const AUTHOR_HASH = '#author';

	/**
	 * Hash used for the Author Logo's `@id`.
	 */
	const AUTHOR_LOGO_HASH = '#authorlogo';

	/**
	 * Hash used for the Breadcrumb's `@id`.
	 */
	const BREADCRUMB_HASH = '#breadcrumb';

	/**
	 * Hash used for the Person `@id`.
	 */
	const PERSON_HASH = '#/schema/person/';

	/**
	 * Hash used for the Article `@id`.
	 */
	const ARTICLE_HASH = '#article';

	/**
	 * Hash used for the Organization `@id`.
	 */
	const ORGANIZATION_HASH = '#organization';

	/**
	 * Hash used for the Organization `@id`.
	 */
	const ORGANIZATION_LOGO_HASH = '#logo';

	/**
	 * Hash used for the logo `@id`.
	 */
	const PERSON_LOGO_HASH = '#personlogo';

	/**
	 * Hash used for an Article's primary image `@id`.
	 */
	const PRIMARY_IMAGE_HASH = '#primaryimage';

	/**
	 * Hash used for the WebPage's `@id`.
	 *
	 * @deprecated 19.3
	 */
	const WEBPAGE_HASH = '';

	/**
	 * Hash used for the Website's `@id`.
	 */
	const WEBSITE_HASH = '#website';
}
class-schema-author.php000066600000005116151136754620011141 0ustar00<?php
/**
 * WPSEO plugin file.
 *
 * @package WPSEO\Frontend\Schema
 */

use Yoast\WP\SEO\Config\Schema_IDs;
use Yoast\WP\SEO\Generators\Schema\Author;

/**
 * Returns schema Person data.
 *
 * @since      10.2
 * @deprecated 14.0
 */
class WPSEO_Schema_Author extends WPSEO_Deprecated_Graph_Piece {

	/**
	 * The hash used for images.
	 *
	 * @var string
	 */
	protected $image_hash = Schema_IDs::AUTHOR_LOGO_HASH;

	/**
	 * The Schema type we use for this class.
	 *
	 * @var string[]
	 */
	protected $type = [ 'Person' ];

	/**
	 * WPSEO_Schema_Author constructor.
	 *
	 * @deprecated 14.0
	 * @codeCoverageIgnore
	 *
	 * @param null $context The context. No longer used but present for BC.
	 */
	public function __construct( $context = null ) {
		parent::__construct( Author::class );
	}

	/**
	 * Determine whether we should return Person schema.
	 *
	 * @deprecated 14.0
	 * @codeCoverageIgnore
	 *
	 * @return bool
	 */
	public function is_needed() {
		_deprecated_function( __METHOD__, 'WPSEO 14.0', 'Yoast\WP\SEO\Generators\Schema\Author::is_needed' );

		if ( $this->stable->context->indexable->object_type === 'user' ) {
			return true;
		}

		// This call to `is_post_author` is why this whole block could not be replaced with a `parent::is_needed()` call.
		if ( $this->is_post_author() ) {
			// If the author is the user the site represents, no need for an extra author block.
			if ( $this->stable->is_needed() ) {
				return (int) $this->stable->context->post->post_author !== $this->stable->context->site_user_id;
			}

			return true;
		}

		return false;
	}

	/**
	 * Gets the Schema type we use for this class.
	 *
	 * @deprecated 14.0
	 * @codeCoverageIgnore
	 *
	 * @return string[] The schema type.
	 */
	public static function get_type() {
		_deprecated_function( __METHOD__, 'WPSEO 14.0' );

		return [ 'Person' ];
	}

	/**
	 * Determine whether the current URL is worthy of Article schema.
	 *
	 * @deprecated 14.0
	 * @codeCoverageIgnore
	 *
	 * @return bool
	 */
	protected function is_post_author() {
		_deprecated_function( __METHOD__, 'WPSEO 14.0' );

		return (
			$this->stable->context->indexable->object_type === 'post'
			&& $this->helpers->schema->article->is_article_post_type( $this->stable->context->indexable->object_sub_type )
		);
	}

	/**
	 * Determines a User ID for the Person data.
	 *
	 * @deprecated 14.0
	 * @codeCoverageIgnore
	 *
	 * @return bool|int User ID or false upon return.
	 */
	protected function determine_user_id() {
		_deprecated_function( __METHOD__, 'WPSEO 14.0', 'Yoast\WP\SEO\Generators\Schema\Author::determine_user_id' );

		return parent::determine_user_id();
	}
}
class-schema-faq-question-list.php000066600000001613151136754620013222 0ustar00<?php
/**
 * WPSEO plugin file.
 *
 * @package WPSEO\Frontend\Schema
 */

/**
 * Returns a question object for each question in an FAQ block.
 *
 * @since 11.1
 *
 * @deprecated 14.0
 */
class WPSEO_Schema_FAQ_Question_List {

	/**
	 * WPSEO_Schema_FAQ_Question_List constructor.
	 *
	 * @deprecated 14.0
	 * @codeCoverageIgnore
	 *
	 * @param WP_Block_Parser_Block[] $blocks  An array of the FAQ blocks on this page.
	 * @param WPSEO_Schema_Context    $context A value object with context variables.
	 */
	public function __construct( $blocks, $context ) {
		_deprecated_function( __METHOD__, 'WPSEO 14.0' );
	}

	/**
	 * Find an image based on its URL and generate a Schema object for it.
	 *
	 * @deprecated 14.0
	 * @codeCoverageIgnore
	 *
	 * @return array The Schema with a question list added.
	 */
	public function generate() {
		_deprecated_function( __METHOD__, 'WPSEO 14.0' );

		return [];
	}
}
class-schema.php000066600000001646151136754620007645 0ustar00<?php
/**
 * WPSEO plugin file.
 *
 * @package WPSEO\Frontend\Schema
 */

/**
 * Class WPSEO_Schema
 *
 * Outputs schema code specific for Google's JSON LD stuff.
 *
 * @since      1.8
 * @deprecated 14.0
 */
class WPSEO_Schema implements WPSEO_WordPress_Integration {

	/**
	 * Registers the hooks.
	 *
	 * @deprecated 14.0
	 * @codeCoverageIgnore
	 */
	public function register_hooks() {
		_deprecated_function( __METHOD__, 'WPSEO 14.0' );
	}

	/**
	 * JSON LD output function that the functions for specific code can hook into.
	 *
	 * @deprecated 14.0
	 * @codeCoverageIgnore
	 *
	 * @since 1.8
	 */
	public function json_ld() {
		_deprecated_function( __METHOD__, 'WPSEO 14.0' );
	}

	/**
	 * Outputs the JSON LD code in a valid JSON+LD wrapper.
	 *
	 * @since      10.2
	 * @deprecated 14.0
	 * @codeCoverageIgnore
	 *
	 * @return void
	 */
	public function generate() {
		_deprecated_function( __METHOD__, 'WPSEO 14.0' );
	}
}
interface-wpseo-graph-piece.php000066600000001055151136754620012551 0ustar00<?php
/**
 * WPSEO plugin file.
 *
 * @package WPSEO\Frontend\Schema
 */

if ( ! interface_exists( 'WPSEO_Graph_Piece' ) ) {
	/**
	 * An interface for registering Schema Graph Pieces.
	 *
	 * @since      10.2
	 * @deprecated 14.0
	 */
	interface WPSEO_Graph_Piece {

		/**
		 * Add your piece of the graph.
		 *
		 * @return array|bool A graph piece on success, false on failure.
		 */
		public function generate();

		/**
		 * Determines whether or not a piece should be added to the graph.
		 *
		 * @return bool
		 */
		public function is_needed();
	}
}
class-schema-article.php000066600000002246151136754620011263 0ustar00<?php
/**
 * WPSEO plugin file.
 *
 * @package WPSEO\Frontend\Schema
 */

use Yoast\WP\SEO\Generators\Schema\Article;

/**
 * Returns schema Article data.
 *
 * @since      10.2
 * @deprecated 14.0
 */
class WPSEO_Schema_Article extends WPSEO_Deprecated_Graph_Piece {

	/**
	 * The date helper.
	 *
	 * @var WPSEO_Date_Helper
	 */
	protected $date;

	/**
	 * WPSEO_Schema_Article constructor.
	 *
	 * @deprecated 14.0
	 * @codeCoverageIgnore
	 *
	 * @param array|null $context The context. No longer used but present for BC.
	 */
	public function __construct( $context = null ) {
		parent::__construct( Article::class );

		$this->date = new WPSEO_Date_Helper();
	}

	/**
	 * Determines whether a given post type should have Article schema.
	 *
	 * @deprecated 14.0
	 * @codeCoverageIgnore
	 *
	 * @param string|null $post_type Post type to check.
	 *
	 * @return bool True if it has article schema, false if not.
	 */
	public static function is_article_post_type( $post_type = null ) {
		_deprecated_function( __METHOD__, 'WPSEO 14.0', 'YoastSEO()->helpers->schema->article->is_article_post_type' );

		return YoastSEO()->helpers->schema->article->is_article_post_type( $post_type );
	}
}
class-schema-webpage.php000066600000002755151136754620011257 0ustar00<?php
/**
 * WPSEO plugin file.
 *
 * @package WPSEO\Frontend\Schema
 */

use Yoast\WP\SEO\Generators\Schema\WebPage;

/**
 * Returns schema WebPage data.
 *
 * @since      10.2
 * @deprecated 14.0
 */
class WPSEO_Schema_WebPage extends WPSEO_Deprecated_Graph_Piece {

	/**
	 * The date helper.
	 *
	 * @var WPSEO_Date_Helper
	 */
	protected $date;

	/**
	 * WPSEO_Schema_WebPage constructor.
	 *
	 * @deprecated 14.0
	 * @codeCoverageIgnore
	 *
	 * @param null $context The context. No longer used but present for BC.
	 */
	public function __construct( $context = null ) {
		parent::__construct( WebPage::class );

		$this->date = new WPSEO_Date_Helper();
	}

	/**
	 * Adds an author property to the $data if the WebPage is not represented.
	 *
	 * @deprecated 14.0
	 * @codeCoverageIgnore
	 *
	 * @param array   $data The WebPage schema.
	 * @param WP_Post $post The post the context is representing.
	 *
	 * @return array The WebPage schema.
	 */
	public function add_author( $data, $post ) {
		_deprecated_function( __METHOD__, 'WPSEO 14.0', 'Yoast\WP\SEO\Generators\Schema\WebPage::add_author' );

		return $this->stable->add_author( $data, $post );
	}

	/**
	 * If we have an image, make it the primary image of the page.
	 *
	 * @deprecated 14.0
	 * @codeCoverageIgnore
	 *
	 * @param array $data WebPage schema data.
	 */
	public function add_image( &$data ) {
		_deprecated_function( __METHOD__, 'WPSEO 14.0', 'Yoast\WP\SEO\Generators\Schema\WebPage::add_image' );

		$this->stable->add_image( $data );
	}
}
class-schema-organization.php000066600000001064151136754620012341 0ustar00<?php
/**
 * WPSEO plugin file.
 *
 * @package WPSEO\Frontend\Schema
 */

use Yoast\WP\SEO\Generators\Schema\Organization;

/**
 * Returns schema Organization data.
 *
 * @since      10.2
 * @deprecated 14.0
 */
class WPSEO_Schema_Organization extends WPSEO_Deprecated_Graph_Piece {

	/**
	 * WPSEO_Schema_Organization constructor.
	 *
	 * @deprecated 14.0
	 * @codeCoverageIgnore
	 *
	 * @param null $context The context. No longer used but present for BC.
	 */
	public function __construct( $context = null ) {
		parent::__construct( Organization::class );
	}
}
class-schema-image.php000066600000005106151136754620010720 0ustar00<?php
/**
 * WPSEO plugin file.
 *
 * @package WPSEO\Frontend\Schema
 */

/**
 * Returns schema image data.
 *
 * @since 11.1
 *
 * @deprecated 14.0
 *
 * @property string $schema_id      The `@id` to use for the returned image.
 * @property array  $data           The ImageObject Schema array.
 * @property int    $attachment_id  The ID of the attachment used to generate the object.
 */
class WPSEO_Schema_Image {

	/**
	 * Value to use as the image id.
	 *
	 * @var string
	 */
	private $schema_id;

	/**
	 * WPSEO_Schema_Image constructor.
	 *
	 * @codeCoverageIgnore
	 * @deprecated 14.0
	 *
	 * @param string $schema_id The string to use in an image's `@id`.
	 */
	public function __construct( $schema_id ) {
		_deprecated_function( __METHOD__, 'WPSEO 14.0', 'YoastSEO()->helpers->schema->image' );
		$this->schema_id = $schema_id;
	}

	/**
	 * Find an image based on its URL and generate a Schema object for it.
	 *
	 * @codeCoverageIgnore
	 * @deprecated 14.0
	 *
	 * @param string $url     The image URL to base our object on.
	 * @param string $caption An optional caption.
	 *
	 * @return array Schema ImageObject array.
	 */
	public function generate_from_url( $url, $caption = '' ) {
		_deprecated_function( __METHOD__, 'WPSEO 14.0', 'YoastSEO()->helpers->schema->image->generate_from_url' );
		return YoastSEO()->helpers->schema->image->generate_from_url( $this->schema_id, $url, $caption );
	}

	/**
	 * Retrieve data about an image from the database and use it to generate a Schema object.
	 *
	 * @codeCoverageIgnore
	 * @deprecated 14.0
	 *
	 * @param int    $attachment_id The attachment to retrieve data from.
	 * @param string $caption       The caption string, if there is one.
	 *
	 * @return array Schema ImageObject array.
	 */
	public function generate_from_attachment_id( $attachment_id, $caption = '' ) {
		_deprecated_function( __METHOD__, 'WPSEO 14.0', 'YoastSEO()->helpers->schema->image->generate_from_attachment_id' );
		return YoastSEO()->helpers->schema->image->generate_from_attachment_id( $this->schema_id, $attachment_id, $caption );
	}

	/**
	 * If we can't find $url in our database, we output a simple ImageObject.
	 *
	 * @codeCoverageIgnore
	 * @deprecated 14.0
	 *
	 * @param string $url     The image URL.
	 * @param string $caption A caption, if set.
	 *
	 * @return array Schema ImageObject array.
	 */
	public function simple_image_object( $url, $caption = '' ) {
		_deprecated_function( __METHOD__, 'WPSEO 14.0', 'YoastSEO()->helpers->schema->image->simple_image_object' );
		return YoastSEO()->helpers->schema->image->simple_image_object( $this->schema_id, $url, $caption );
	}
}
class-schema-howto.php000066600000013754151136754620011006 0ustar00<?php
/**
 * WPSEO plugin file.
 *
 * @package WPSEO\Frontend\Schema
 */

use Yoast\WP\SEO\Config\Schema_IDs;
use Yoast\WP\SEO\Generators\Schema\HowTo;

/**
 * Returns schema FAQ data.
 *
 * @since      11.5
 * @deprecated 14.0
 */
class WPSEO_Schema_HowTo extends WPSEO_Deprecated_Graph_Piece {

	/**
	 * The HowTo blocks count on the current page.
	 *
	 * @var int
	 */
	private $counter = 0;

	/**
	 * WPSEO_Schema_FAQ constructor.
	 *
	 * @deprecated 14.0
	 * @codeCoverageIgnore
	 *
	 * @param null $context The context. No longer used but present for BC.
	 */
	public function __construct( $context = null ) {
		parent::__construct( HowTo::class );
	}

	/**
	 * Renders the How-To block into our graph.
	 *
	 * @deprecated 14.0
	 * @codeCoverageIgnore
	 *
	 * @param array $graph Our Schema data.
	 * @param array $block The How-To block content.
	 *
	 * @return mixed
	 */
	public function render( $graph, $block ) {
		_deprecated_function( __METHOD__, 'WPSEO 14.0', 'Yoast\WP\SEO\Generators\Schema\HowTo::add_how_to' );

		++$this->counter;
		$this->add_how_to( $graph, $block, $this->counter );

		return $graph;
	}

	/**
	 * Generates the HowTo schema for a block.
	 *
	 * @param array $graph Our Schema data.
	 * @param array $block The How-To block content.
	 * @param int   $index The index of the current block.
	 */
	protected function add_how_to( &$graph, $block, $index ) {
		$data = [
			'@type'            => 'HowTo',
			'@id'              => $this->stable->context->canonical . '#howto-' . ( $index + 1 ),
			'name'             => $this->helpers->schema->html->smart_strip_tags( $this->helpers->post->get_post_title_with_fallback( $this->stable->context->id ) ),
			'mainEntityOfPage' => [ '@id' => $this->stable->context->main_schema_id ],
			'description'      => '',
		];

		if ( $this->stable->context->has_article ) {
			$data['mainEntityOfPage'] = [ '@id' => $this->stable->context->main_schema_id . Schema_IDs::ARTICLE_HASH ];
		}

		if ( isset( $block['attrs']['jsonDescription'] ) ) {
			$data['description'] = $this->helpers->schema->html->sanitize( $block['attrs']['jsonDescription'] );
		}

		$this->add_duration( $data, $block['attrs'] );
		$this->add_steps( $data, $block['attrs']['steps'] );

		$data = $this->helpers->schema->language->add_piece_language( $data );

		$graph[] = $data;
	}

	/**
	 * Adds the steps to our How-To output.
	 *
	 * @param array $data  Our How-To schema data.
	 * @param array $steps Our How-To block's steps.
	 */
	private function add_steps( &$data, $steps ) {
		foreach ( $steps as $step ) {
			$schema_id   = $this->stable->context->canonical . '#' . esc_attr( $step['id'] );
			$schema_step = [
				'@type' => 'HowToStep',
				'url'   => $schema_id,
			];

			if ( isset( $step['jsonText'] ) ) {
				$json_text = $this->helpers->schema->html->sanitize( $step['jsonText'] );
			}

			if ( isset( $step['jsonName'] ) ) {
				$json_name = $this->helpers->schema->html->smart_strip_tags( $step['jsonName'] );
			}

			if ( empty( $json_name ) ) {
				if ( empty( $step['text'] ) ) {
					continue;
				}

				$schema_step['text'] = '';

				$this->add_step_image( $schema_step, $step );

				// If there is no text and no image, don't output the step.
				if ( empty( $json_text ) && empty( $schema_step['image'] ) ) {
					continue;
				}

				if ( ! empty( $json_text ) ) {
					$schema_step['text'] = $json_text;
				}
			}

			elseif ( empty( $json_text ) ) {
				$schema_step['text'] = $json_name;
			}
			else {
				$schema_step['name'] = $json_name;

				$schema_step['itemListElement'] = [
					[
						'@type' => 'HowToDirection',
						'text'  => $json_text,
					],
				];

				$this->add_step_description( $schema_step, $json_text );
				$this->add_step_image( $schema_step, $step );
			}

			$data['step'][] = $schema_step;
		}
	}

	/**
	 * Adds the duration of the task to the Schema.
	 *
	 * @param array $data       Our How-To schema data.
	 * @param array $attributes The block data attributes.
	 */
	private function add_duration( &$data, $attributes ) {
		if ( empty( $attributes['hasDuration'] ) ) {
			return;
		}

		$days    = empty( $attributes['days'] ) ? 0 : $attributes['days'];
		$hours   = empty( $attributes['hours'] ) ? 0 : $attributes['hours'];
		$minutes = empty( $attributes['minutes'] ) ? 0 : $attributes['minutes'];

		if ( ( $days + $hours + $minutes ) > 0 ) {
			$data['totalTime'] = esc_attr( 'P' . $days . 'DT' . $hours . 'H' . $minutes . 'M' );
		}
	}

	/**
	 * Determines whether we're part of an article or a webpage.
	 *
	 * @deprecated 14.0
	 * @codeCoverageIgnore
	 *
	 * @return string A reference URL.
	 */
	protected function get_main_schema_id() {
		_deprecated_function( __METHOD__, 'WPSEO 14.0' );

		return $this->stable->context->main_schema_id;
	}

	/**
	 * Checks if we have a step description, if we do, add it.
	 *
	 * @param array  $schema_step Our Schema output for the Step.
	 * @param string $json_text   The step text.
	 */
	private function add_step_description( &$schema_step, $json_text ) {
		$schema_step['itemListElement'] = [
			[
				'@type' => 'HowToDirection',
				'text'  => $json_text,
			],
		];
	}

	/**
	 * Checks if we have a step image, if we do, add it.
	 *
	 * @param array $schema_step Our Schema output for the Step.
	 * @param array $step        The step block data.
	 */
	private function add_step_image( &$schema_step, $step ) {
		foreach ( $step['text'] as $line ) {
			if ( is_array( $line ) && isset( $line['type'] ) && $line['type'] === 'img' ) {
				$schema_step['image'] = $this->get_image_schema( esc_url( $line['props']['src'] ) );
			}
		}
	}

	/**
	 * Generates the image schema from the attachment $url.
	 *
	 * @deprecated 14.0
	 * @codeCoverageIgnore
	 *
	 * @param string $url Attachment url.
	 *
	 * @return array Image schema.
	 */
	protected function get_image_schema( $url ) {
		_deprecated_function( __METHOD__, 'WPSEO 14.0', 'Yoast\WP\SEO\Generators\Schema\HowTo::get_image_schema' );

		$schema_id = $this->stable->context->canonical . '#schema-image-' . md5( $url );

		return $this->helpers->schema->image->generate_from_url( $schema_id, $url );
	}
}
class-schema-utils.php000066600000003607151136754620011002 0ustar00<?php
/**
 * WPSEO plugin file.
 *
 * @package WPSEO\Frontend\Schema
 */

/**
 * Schema utility functions.
 *
 * @since      11.6
 * @deprecated 14.0
 */
class WPSEO_Schema_Utils {

	/**
	 * Retrieves a user's Schema ID.
	 *
	 * @deprecated 14.0
	 * @codeCoverageIgnore
	 *
	 * @param int                  $user_id The ID of the User you need a Schema ID for.
	 * @param WPSEO_Schema_Context $context A value object with context variables.
	 *
	 * @return string The user's schema ID.
	 */
	public static function get_user_schema_id( $user_id, $context ) {
		_deprecated_function( __METHOD__, 'WPSEO 14.0', 'YoastSEO()->helpers->schema->id->get_user_schema_id' );
		return YoastSEO()->helpers->schema->id->get_user_schema_id( $user_id, $context );
	}

	/**
	 * Retrieves the post title with fallback to `No title`.
	 *
	 * @deprecated 14.0
	 * @codeCoverageIgnore
	 *
	 * @param int $post_id Optional. Post ID.
	 *
	 * @return string The post title with fallback to `No title`.
	 */
	public static function get_post_title_with_fallback( $post_id = 0 ) {
		_deprecated_function( __METHOD__, 'WPSEO 14.0', ' YoastSEO()->helpers->post->get_post_title_with_fallback' );
		return YoastSEO()->helpers->post->get_post_title_with_fallback( $post_id );
	}

	/**
	 * Adds the `inLanguage` property to a Schema piece.
	 *
	 * Must use one of the language codes from the IETF BCP 47 standard. The
	 * language tag syntax is made of one or more subtags separated by a hyphen
	 * e.g. "en", "en-US", "zh-Hant-CN".
	 *
	 * @deprecated 14.0
	 * @codeCoverageIgnore
	 *
	 * @param array $data The Schema piece data.
	 *
	 * @return array The Schema piece data with added language property.
	 */
	public static function add_piece_language( $data ) {
		_deprecated_function( __METHOD__, 'WPSEO 14.0', 'YoastSEO()->helpers->schema->language->add_piece_language' );
		return YoastSEO()->helpers->schema->language->add_piece_language( $data );
	}
}
class-schema-faq.php000066600000003476151136754620010415 0ustar00<?php
/**
 * WPSEO plugin file.
 *
 * @package WPSEO\Frontend\Schema
 */

use Yoast\WP\SEO\Generators\Schema\FAQ;

/**
 * Returns schema FAQ data.
 *
 * @since      11.3
 * @deprecated 14.0
 */
class WPSEO_Schema_FAQ extends WPSEO_Deprecated_Graph_Piece {

	/**
	 * WPSEO_Schema_FAQ constructor.
	 *
	 * @deprecated 14.0
	 * @codeCoverageIgnore
	 *
	 * @param null $context The context. No longer used but present for BC.
	 */
	public function __construct( $context = null ) {
		parent::__construct( FAQ::class );
	}

	/**
	 * If this fires, we know there's an FAQ block ont he page, so filter the page type.
	 *
	 * @deprecated 14.0
	 * @codeCoverageIgnore
	 *
	 * @param array $blocks The blocks of this type on the current page.
	 */
	public function prepare_schema( $blocks ) {
		_deprecated_function( __METHOD__, 'WPSEO 14.0' );
	}

	/**
	 * Change the page type to an array if it isn't one, include FAQPage.
	 *
	 * @deprecated 14.0
	 * @codeCoverageIgnore
	 *
	 * @param array|string $page_type The page type.
	 *
	 * @return array The page type that's now an array.
	 */
	public function change_schema_page_type( $page_type ) {
		_deprecated_function( __METHOD__, 'WPSEO 14.0' );

		if ( ! is_array( $page_type ) ) {
			$page_type = [ $page_type ];
		}
		$page_type[] = 'FAQPage';

		return $page_type;
	}

	/**
	 * Add the Questions in our FAQ blocks as separate pieces to the graph.
	 *
	 * @deprecated 14.0
	 * @codeCoverageIgnore
	 *
	 * @param array                 $graph   Schema data for the current page.
	 * @param WP_Block_Parser_Block $block   The block data array.
	 * @param WPSEO_Schema_Context  $context A value object with context variables.
	 *
	 * @return array Our Schema graph.
	 */
	public function render_schema_questions( $graph, $block, $context ) {
		_deprecated_function( __METHOD__, 'WPSEO 14.0' );

		return [];
	}
}
class-schema-faq-questions.php000066600000002724151136754620012440 0ustar00<?php
/**
 * WPSEO plugin file.
 *
 * @package WPSEO\Frontend\Schema
 */

/**
 * Returns a question object for each question in an FAQ block.
 *
 * @since      11.1
 * @deprecated 14.0
 */
class WPSEO_Schema_FAQ_Questions {

	/**
	 * A value object with context variables.
	 * This property is public, because originally it was dynamically declared.
	 *
	 * @var WPSEO_Schema_Context
	 */
	public $context;

	/**
	 * WPSEO_Schema_FAQ_Questions constructor.
	 *
	 * @deprecated 14.0
	 * @codeCoverageIgnore
	 *
	 * @param array                 $data    Our schema graph.
	 * @param WP_Block_Parser_Block $block   The FAQ block of this type.
	 * @param WPSEO_Schema_Context  $context A value object with context variables.
	 */
	public function __construct( $data, $block, $context ) {
		_deprecated_function( __METHOD__, 'WPSEO 14.0' );
		$this->context = $context;
	}

	/**
	 * Find an image based on its URL and generate a Schema object for it.
	 *
	 * @deprecated 14.0
	 * @codeCoverageIgnore
	 *
	 * @return array The Schema with Questions added.
	 */
	public function generate() {
		_deprecated_function( __METHOD__, 'WPSEO 14.0' );

		return [];
	}

	/**
	 * Generate a Question piece.
	 *
	 * @deprecated 14.0
	 * @codeCoverageIgnore
	 *
	 * @param array $question The question to generate schema for.
	 *
	 * @return array Schema.org Question piece.
	 */
	protected function generate_question_block( $question ) {
		_deprecated_function( __METHOD__, 'WPSEO 14.0' );

		return [];
	}
}
ActionScheduler_LoggerSchema.php000066600000005672151156276760013007 0ustar00<?php

/**
 * Class ActionScheduler_LoggerSchema
 *
 * @codeCoverageIgnore
 *
 * Creates a custom table for storing action logs
 */
class ActionScheduler_LoggerSchema extends ActionScheduler_Abstract_Schema {
	const LOG_TABLE = 'actionscheduler_logs';

	/**
	 * Schema version.
	 *
	 * Increment this value to trigger a schema update.
	 *
	 * @var int
	 */
	protected $schema_version = 3;

	/**
	 * Construct.
	 */
	public function __construct() {
		$this->tables = array(
			self::LOG_TABLE,
		);
	}

	/**
	 * Performs additional setup work required to support this schema.
	 */
	public function init() {
		add_action( 'action_scheduler_before_schema_update', array( $this, 'update_schema_3_0' ), 10, 2 );
	}

	/**
	 * Get table definition.
	 *
	 * @param string $table Table name.
	 */
	protected function get_table_definition( $table ) {
		global $wpdb;
		$table_name      = $wpdb->$table;
		$charset_collate = $wpdb->get_charset_collate();
		switch ( $table ) {

			case self::LOG_TABLE:
				$default_date = ActionScheduler_StoreSchema::DEFAULT_DATE;
				return "CREATE TABLE $table_name (
				        log_id bigint(20) unsigned NOT NULL auto_increment,
				        action_id bigint(20) unsigned NOT NULL,
				        message text NOT NULL,
				        log_date_gmt datetime NULL default '{$default_date}',
				        log_date_local datetime NULL default '{$default_date}',
				        PRIMARY KEY  (log_id),
				        KEY action_id (action_id),
				        KEY log_date_gmt (log_date_gmt)
				        ) $charset_collate";

			default:
				return '';
		}
	}

	/**
	 * Update the logs table schema, allowing datetime fields to be NULL.
	 *
	 * This is needed because the NOT NULL constraint causes a conflict with some versions of MySQL
	 * configured with sql_mode=NO_ZERO_DATE, which can for instance lead to tables not being created.
	 *
	 * Most other schema updates happen via ActionScheduler_Abstract_Schema::update_table(), however
	 * that method relies on dbDelta() and this change is not possible when using that function.
	 *
	 * @param string $table Name of table being updated.
	 * @param string $db_version The existing schema version of the table.
	 */
	public function update_schema_3_0( $table, $db_version ) {
		global $wpdb;

		if ( 'actionscheduler_logs' !== $table || version_compare( $db_version, '3', '>=' ) ) {
			return;
		}

		// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
		$table_name   = $wpdb->prefix . 'actionscheduler_logs';
		$table_list   = $wpdb->get_col( "SHOW TABLES LIKE '{$table_name}'" );
		$default_date = ActionScheduler_StoreSchema::DEFAULT_DATE;

		if ( ! empty( $table_list ) ) {
			$query = "
				ALTER TABLE {$table_name}
				MODIFY COLUMN log_date_gmt datetime NULL default '{$default_date}',
				MODIFY COLUMN log_date_local datetime NULL default '{$default_date}'
			";
			$wpdb->query( $query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
		}
		// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
	}
}
ActionScheduler_StoreSchema.php000066600000011773151156276760012663 0ustar00<?php

/**
 * Class ActionScheduler_StoreSchema
 *
 * @codeCoverageIgnore
 *
 * Creates custom tables for storing scheduled actions
 */
class ActionScheduler_StoreSchema extends ActionScheduler_Abstract_Schema {
	const ACTIONS_TABLE = 'actionscheduler_actions';
	const CLAIMS_TABLE  = 'actionscheduler_claims';
	const GROUPS_TABLE  = 'actionscheduler_groups';
	const DEFAULT_DATE  = '0000-00-00 00:00:00';

	/**
	 * Schema version.
	 *
	 * Increment this value to trigger a schema update.
	 *
	 * @var int
	 */
	protected $schema_version = 7;

	/**
	 * Construct.
	 */
	public function __construct() {
		$this->tables = array(
			self::ACTIONS_TABLE,
			self::CLAIMS_TABLE,
			self::GROUPS_TABLE,
		);
	}

	/**
	 * Performs additional setup work required to support this schema.
	 */
	public function init() {
		add_action( 'action_scheduler_before_schema_update', array( $this, 'update_schema_5_0' ), 10, 2 );
	}

	/**
	 * Get table definition.
	 *
	 * @param string $table Table name.
	 */
	protected function get_table_definition( $table ) {
		global $wpdb;
		$table_name      = $wpdb->$table;
		$charset_collate = $wpdb->get_charset_collate();
		$default_date    = self::DEFAULT_DATE;
		// phpcs:ignore Squiz.PHP.CommentedOutCode
		$max_index_length = 191; // @see wp_get_db_schema()

		$hook_status_scheduled_date_gmt_max_index_length = $max_index_length - 20 - 8; // - status, - scheduled_date_gmt

		switch ( $table ) {

			case self::ACTIONS_TABLE:
				return "CREATE TABLE {$table_name} (
				        action_id bigint(20) unsigned NOT NULL auto_increment,
				        hook varchar(191) NOT NULL,
				        status varchar(20) NOT NULL,
				        scheduled_date_gmt datetime NULL default '{$default_date}',
				        scheduled_date_local datetime NULL default '{$default_date}',
				        priority tinyint unsigned NOT NULL default '10',
				        args varchar($max_index_length),
				        schedule longtext,
				        group_id bigint(20) unsigned NOT NULL default '0',
				        attempts int(11) NOT NULL default '0',
				        last_attempt_gmt datetime NULL default '{$default_date}',
				        last_attempt_local datetime NULL default '{$default_date}',
				        claim_id bigint(20) unsigned NOT NULL default '0',
				        extended_args varchar(8000) DEFAULT NULL,
				        PRIMARY KEY  (action_id),
				        KEY hook_status_scheduled_date_gmt (hook($hook_status_scheduled_date_gmt_max_index_length), status, scheduled_date_gmt),
				        KEY status_scheduled_date_gmt (status, scheduled_date_gmt),
				        KEY scheduled_date_gmt (scheduled_date_gmt),
				        KEY args (args($max_index_length)),
				        KEY group_id (group_id),
				        KEY last_attempt_gmt (last_attempt_gmt),
				        KEY `claim_id_status_scheduled_date_gmt` (`claim_id`, `status`, `scheduled_date_gmt`)
				        ) $charset_collate";

			case self::CLAIMS_TABLE:
				return "CREATE TABLE {$table_name} (
				        claim_id bigint(20) unsigned NOT NULL auto_increment,
				        date_created_gmt datetime NULL default '{$default_date}',
				        PRIMARY KEY  (claim_id),
				        KEY date_created_gmt (date_created_gmt)
				        ) $charset_collate";

			case self::GROUPS_TABLE:
				return "CREATE TABLE {$table_name} (
				        group_id bigint(20) unsigned NOT NULL auto_increment,
				        slug varchar(255) NOT NULL,
				        PRIMARY KEY  (group_id),
				        KEY slug (slug($max_index_length))
				        ) $charset_collate";

			default:
				return '';
		}
	}

	/**
	 * Update the actions table schema, allowing datetime fields to be NULL.
	 *
	 * This is needed because the NOT NULL constraint causes a conflict with some versions of MySQL
	 * configured with sql_mode=NO_ZERO_DATE, which can for instance lead to tables not being created.
	 *
	 * Most other schema updates happen via ActionScheduler_Abstract_Schema::update_table(), however
	 * that method relies on dbDelta() and this change is not possible when using that function.
	 *
	 * @param string $table Name of table being updated.
	 * @param string $db_version The existing schema version of the table.
	 */
	public function update_schema_5_0( $table, $db_version ) {
		global $wpdb;

		if ( 'actionscheduler_actions' !== $table || version_compare( $db_version, '5', '>=' ) ) {
			return;
		}

		// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
		$table_name   = $wpdb->prefix . 'actionscheduler_actions';
		$table_list   = $wpdb->get_col( "SHOW TABLES LIKE '{$table_name}'" );
		$default_date = self::DEFAULT_DATE;

		if ( ! empty( $table_list ) ) {
			$query = "
				ALTER TABLE {$table_name}
				MODIFY COLUMN scheduled_date_gmt datetime NULL default '{$default_date}',
				MODIFY COLUMN scheduled_date_local datetime NULL default '{$default_date}',
				MODIFY COLUMN last_attempt_gmt datetime NULL default '{$default_date}',
				MODIFY COLUMN last_attempt_local datetime NULL default '{$default_date}'
		";
			$wpdb->query( $query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
		}
		// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
	}
}