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/front-end.tar

indexing-controls.php000066600000005253151127152550010740 0ustar00<?php

namespace Yoast\WP\SEO\Integrations\Front_End;

use Yoast\WP\SEO\Conditionals\Front_End_Conditional;
use Yoast\WP\SEO\Helpers\Robots_Helper;
use Yoast\WP\SEO\Integrations\Integration_Interface;

/**
 * Class Indexing_Controls.
 */
class Indexing_Controls implements Integration_Interface {

	/**
	 * The robots helper.
	 *
	 * @var Robots_Helper
	 */
	protected $robots;

	/**
	 * Returns the conditionals based in which this loadable should be active.
	 *
	 * @return array
	 */
	public static function get_conditionals() {
		return [ Front_End_Conditional::class ];
	}

	/**
	 * The constructor.
	 *
	 * @codeCoverageIgnore Sets the dependencies.
	 *
	 * @param Robots_Helper $robots The robots helper.
	 */
	public function __construct( Robots_Helper $robots ) {
		$this->robots = $robots;
	}

	/**
	 * Initializes the integration.
	 *
	 * This is the place to register hooks and filters.
	 *
	 * @codeCoverageIgnore
	 *
	 * @return void
	 */
	public function register_hooks() {
		// The option `blog_public` is set in Settings > Reading > Search Engine Visibility.
		if ( (string) \get_option( 'blog_public' ) === '0' ) {
			\add_filter( 'wpseo_robots_array', [ $this->robots, 'set_robots_no_index' ] );
		}

		\add_action( 'template_redirect', [ $this, 'noindex_robots' ] );
		\add_filter( 'loginout', [ $this, 'nofollow_link' ] );
		\add_filter( 'register', [ $this, 'nofollow_link' ] );

		// Remove actions that we will handle through our wpseo_head call, and probably change the output of.
		\remove_action( 'wp_head', 'rel_canonical' );
		\remove_action( 'wp_head', 'index_rel_link' );
		\remove_action( 'wp_head', 'start_post_rel_link' );
		\remove_action( 'wp_head', 'adjacent_posts_rel_link_wp_head' );
		\remove_action( 'wp_head', 'noindex', 1 );
	}

	/**
	 * Sends a Robots HTTP header preventing URL from being indexed in the search results while allowing search engines
	 * to follow the links in the object at the URL.
	 *
	 * @return bool Boolean indicating whether the noindex header was sent.
	 */
	public function noindex_robots() {
		if ( ! \is_robots() ) {
			return false;
		}

		return $this->set_robots_header();
	}

	/**
	 * Adds rel="nofollow" to a link, only used for login / registration links.
	 *
	 * @param string $input The link element as a string.
	 *
	 * @return string
	 */
	public function nofollow_link( $input ) {
		return \str_replace( '<a ', '<a rel="nofollow" ', $input );
	}

	/**
	 * Sets the x-robots-tag to noindex follow.
	 *
	 * @codeCoverageIgnore Too difficult to test.
	 *
	 * @return bool
	 */
	protected function set_robots_header() {
		if ( \headers_sent() === false ) {
			\header( 'X-Robots-Tag: noindex, follow', true );

			return true;
		}

		return false;
	}
}
handle-404.php000066600000005127151127152550007032 0ustar00<?php

namespace Yoast\WP\SEO\Integrations\Front_End;

use Yoast\WP\SEO\Conditionals\Front_End_Conditional;
use Yoast\WP\SEO\Integrations\Integration_Interface;
use Yoast\WP\SEO\Wrappers\WP_Query_Wrapper;

/**
 * Handles intercepting requests.
 */
class Handle_404 implements Integration_Interface {

	/**
	 * The WP Query wrapper.
	 *
	 * @var WP_Query_Wrapper
	 */
	private $query_wrapper;

	/**
	 * Returns the conditionals based in which this loadable should be active.
	 *
	 * @return array
	 */
	public static function get_conditionals() {
		return [ Front_End_Conditional::class ];
	}

	/**
	 * Initializes the integration.
	 *
	 * This is the place to register hooks and filters.
	 *
	 * @return void
	 */
	public function register_hooks() {
		\add_filter( 'pre_handle_404', [ $this, 'handle_404' ] );
	}

	/**
	 * Handle_404 constructor.
	 *
	 * @codeCoverageIgnore Handles dependencies.
	 *
	 * @param WP_Query_Wrapper $query_wrapper The query wrapper.
	 */
	public function __construct( WP_Query_Wrapper $query_wrapper ) {
		$this->query_wrapper = $query_wrapper;
	}

	/**
	 * Handles the 404 status code.
	 *
	 * @param bool $handled Whether we've handled the request.
	 *
	 * @return bool True if it's 404.
	 */
	public function handle_404( $handled ) {
		if ( ! $this->is_feed_404() ) {
			return $handled;
		}

		$this->set_404();
		$this->set_headers();

		\add_filter( 'old_slug_redirect_url', '__return_false' );
		\add_filter( 'redirect_canonical', '__return_false' );

		return true;
	}

	/**
	 * If there are no posts in a feed, make it 404 instead of sending an empty RSS feed.
	 *
	 * @return bool True if it's 404.
	 */
	protected function is_feed_404() {
		if ( ! \is_feed() ) {
			return false;
		}

		$wp_query = $this->query_wrapper->get_query();

		// Don't 404 if the query contains post(s) or an object.
		if ( $wp_query->posts || $wp_query->get_queried_object() ) {
			return false;
		}

		// Don't 404 if it isn't archive or singular.
		if ( ! $wp_query->is_archive() && ! $wp_query->is_singular() ) {
			return false;
		}

		return true;
	}

	/**
	 * Sets the 404 status code.
	 */
	protected function set_404() {
		$wp_query          = $this->query_wrapper->get_query();
		$wp_query->is_feed = false;
		$wp_query->set_404();
		$this->query_wrapper->set_query( $wp_query );
	}

	/**
	 * Sets the headers for http.
	 *
	 * @codeCoverageIgnore
	 */
	protected function set_headers() {
		// Overwrite Content-Type header.
		if ( ! \headers_sent() ) {
			\header( 'Content-Type: ' . \get_option( 'html_type' ) . '; charset=' . \get_option( 'blog_charset' ) );
		}

		\status_header( 404 );
		\nocache_headers();
	}
}
schema-accessibility-feature.php000066600000004462151127152550013011 0ustar00<?php

namespace Yoast\WP\SEO\Integrations\Front_End;

use Yoast\WP\SEO\Conditionals\Front_End_Conditional;
use Yoast\WP\SEO\Context\Meta_Tags_Context;
use Yoast\WP\SEO\Generators\Schema\Abstract_Schema_Piece;
use Yoast\WP\SEO\Generators\Schema\Article;
use Yoast\WP\SEO\Integrations\Integration_Interface;

/**
 * Adds the table of contents accessibility feature to the article piece with a fallback to the webpage piece.
 */
class Schema_Accessibility_Feature implements Integration_Interface {

	/**
	 * Returns the conditionals based in which this loadable should be active.
	 *
	 * @return array
	 */
	public static function get_conditionals() {
		return [ Front_End_Conditional::class ];
	}

	/**
	 * Initializes the integration.
	 *
	 * This is the place to register hooks and filters.
	 *
	 * @return void
	 */
	public function register_hooks() {
		\add_filter( 'wpseo_schema_webpage', [ $this, 'maybe_add_accessibility_feature' ], 10, 4 );
		\add_filter( 'wpseo_schema_article', [ $this, 'add_accessibility_feature' ], 10, 2 );
	}

	/**
	 * Adds the accessibility feature to the webpage if there is no article.
	 *
	 * @param array                   $piece         The graph piece.
	 * @param Meta_Tags_Context       $context       The context.
	 * @param Abstract_Schema_Piece   $the_generator The current schema generator.
	 * @param Abstract_Schema_Piece[] $generators    The schema generators.
	 *
	 * @return array The graph piece.
	 */
	public function maybe_add_accessibility_feature( $piece, $context, $the_generator, $generators ) {
		foreach ( $generators as $generator ) {
			if ( \is_a( $generator, Article::class ) && $generator->is_needed() ) {
				return $piece;
			}
		}

		return $this->add_accessibility_feature( $piece, $context );
	}

	/**
	 * Adds the accessibility feature to a schema graph piece.
	 *
	 * @param array             $piece   The schema piece.
	 * @param Meta_Tags_Context $context The context.
	 *
	 * @return array The graph piece.
	 */
	public function add_accessibility_feature( $piece, $context ) {
		if ( empty( $context->blocks['yoast-seo/table-of-contents'] ) ) {
			return $piece;
		}

		if ( isset( $piece['accessibilityFeature'] ) ) {
			$piece['accessibilityFeature'][] = 'tableOfContents';
		}
		else {
			$piece['accessibilityFeature'] = [
				'tableOfContents',
			];
		}
		return $piece;
	}
}
robots-txt-integration.php000066600000014174151127152550011742 0ustar00<?php

namespace Yoast\WP\SEO\Integrations\Front_End;

use WPSEO_Sitemaps_Router;
use Yoast\WP\SEO\Conditionals\Robots_Txt_Conditional;
use Yoast\WP\SEO\Helpers\Options_Helper;
use Yoast\WP\SEO\Helpers\Robots_Txt_Helper;
use Yoast\WP\SEO\Integrations\Integration_Interface;
use Yoast\WP\SEO\Presenters\Robots_Txt_Presenter;

/**
 * Handles adding the sitemap to the `robots.txt`.
 */
class Robots_Txt_Integration implements Integration_Interface {

	/**
	 * Holds the options helper.
	 *
	 * @var Options_Helper
	 */
	protected $options_helper;

	/**
	 * Holds the robots txt helper.
	 *
	 * @var Robots_Txt_Helper
	 */
	protected $robots_txt_helper;

	/**
	 * Holds the robots txt presenter.
	 *
	 * @var Robots_Txt_Presenter
	 */
	protected $robots_txt_presenter;

	/**
	 * Sets the helpers.
	 *
	 * @param Options_Helper    $options_helper Options helper.
	 * @param Robots_Txt_Helper $robots_txt_helper Robots txt helper.
	 */
	public function __construct( Options_Helper $options_helper, Robots_Txt_Helper $robots_txt_helper ) {
		$this->options_helper       = $options_helper;
		$this->robots_txt_helper    = $robots_txt_helper;
		$this->robots_txt_presenter = new Robots_Txt_Presenter( $robots_txt_helper );
	}

	/**
	 * Returns the conditionals based in which this loadable should be active.
	 *
	 * @return array
	 */
	public static function get_conditionals() {
		return [ Robots_Txt_Conditional::class ];
	}

	/**
	 * Initializes the integration.
	 *
	 * This is the place to register hooks and filters.
	 *
	 * @return void
	 */
	public function register_hooks() {
		\add_filter( 'robots_txt', [ $this, 'filter_robots' ], 99999 );
	}

	/**
	 * Filters the robots.txt output.
	 *
	 * @param string $robots_txt The robots.txt output from WordPress.
	 *
	 * @return string Filtered robots.txt output.
	 */
	public function filter_robots( $robots_txt ) {
		$robots_txt = $this->remove_default_robots( $robots_txt );
		$this->maybe_add_xml_sitemap();

		/**
		 * Filter: 'wpseo_should_add_subdirectory_multisite_xml_sitemaps' - Disabling this filter removes subdirectory sites from xml sitemaps.
		 *
		 * @since 19.8
		 *
		 * @param bool $show Whether to display multisites in the xml sitemaps.
		 */
		if ( \apply_filters( 'wpseo_should_add_subdirectory_multisite_xml_sitemaps', true ) ) {
			$this->add_subdirectory_multisite_xml_sitemaps();
		}

		/**
		 * Allow registering custom robots rules to be outputted within the Yoast content block in robots.txt.
		 *
		 * @param Robots_Txt_Helper $robots_txt_helper The Robots_Txt_Helper object.
		 */
		\do_action( 'Yoast\WP\SEO\register_robots_rules', $this->robots_txt_helper );

		return \trim( $robots_txt . PHP_EOL . $this->robots_txt_presenter->present() . PHP_EOL );
	}

	/**
	 * Replaces the default WordPress robots.txt output.
	 *
	 * @param string $robots_txt Input robots.txt.
	 *
	 * @return string
	 */
	protected function remove_default_robots( $robots_txt ) {
		return \str_replace(
			"User-agent: *\nDisallow: /wp-admin/\nAllow: /wp-admin/admin-ajax.php\n",
			'',
			$robots_txt
		);
	}

	/**
	 * Adds XML sitemap reference to robots.txt.
	 *
	 * @return void
	 */
	protected function maybe_add_xml_sitemap() {
		// If the XML sitemap is disabled, bail.
		if ( ! $this->options_helper->get( 'enable_xml_sitemap', false ) ) {
			return;
		}
		$this->robots_txt_helper->add_sitemap( \esc_url( WPSEO_Sitemaps_Router::get_base_url( 'sitemap_index.xml' ) ) );
	}

	/**
	 * Adds subdomain multisite' XML sitemap references to robots.txt.
	 *
	 * @return void
	 */
	protected function add_subdirectory_multisite_xml_sitemaps() {
		// If not on a multisite subdirectory, bail.
		if ( ! \is_multisite() || \is_subdomain_install() ) {
			return;
		}

		$sitemaps_enabled = $this->get_xml_sitemaps_enabled();

		foreach ( $sitemaps_enabled as $blog_id => $is_sitemap_enabled ) {
			if ( ! $is_sitemap_enabled ) {
				continue;
			}
			$this->robots_txt_helper->add_sitemap( \esc_url( \get_home_url( $blog_id, 'sitemap_index.xml' ) ) );
		}
	}

	/**
	 * Retrieves whether the XML sitemaps are enabled, keyed by blog ID.
	 *
	 * @return array
	 */
	protected function get_xml_sitemaps_enabled() {
		$is_allowed = $this->is_sitemap_allowed();
		$blog_ids   = $this->get_blog_ids();
		$is_enabled = [];
		foreach ( $blog_ids as $blog_id ) {
			$is_enabled[ $blog_id ] = $is_allowed && $this->is_sitemap_enabled_for( $blog_id );
		}

		return $is_enabled;
	}

	/**
	 * Retrieves whether the sitemap is allowed on a sub site.
	 *
	 * @return bool
	 */
	protected function is_sitemap_allowed() {
		$options = \get_network_option( null, 'wpseo_ms' );
		if ( ! $options || ! isset( $options['allow_enable_xml_sitemap'] ) ) {
			// Default is enabled.
			return true;
		}

		return (bool) $options['allow_enable_xml_sitemap'];
	}

	/**
	 * Retrieves whether the sitemap is enabled on a site.
	 *
	 * @param int $blog_id The blog ID.
	 *
	 * @return bool
	 */
	protected function is_sitemap_enabled_for( $blog_id ) {
		if ( ! $this->is_yoast_active_on( $blog_id ) ) {
			return false;
		}

		$options = \get_blog_option( $blog_id, 'wpseo' );
		if ( ! $options || ! isset( $options['enable_xml_sitemap'] ) ) {
			// Default is enabled.
			return true;
		}

		return (bool) $options['enable_xml_sitemap'];
	}

	/**
	 * Determines whether Yoast SEO is active.
	 *
	 * @param int $blog_id The blog ID.
	 *
	 * @return bool
	 */
	protected function is_yoast_active_on( $blog_id ) {
		return \in_array( 'wordpress-seo/wp-seo.php', (array) \get_blog_option( $blog_id, 'active_plugins', [] ), true ) || $this->is_yoast_active_for_network();
	}

	/**
	 * Determines whether Yoast SEO is active for the entire network.
	 *
	 * @return bool
	 */
	protected function is_yoast_active_for_network() {
		$plugins = \get_network_option( null, 'active_sitewide_plugins' );
		if ( isset( $plugins['wordpress-seo/wp-seo.php'] ) ) {
			return true;
		}

		return false;
	}

	/**
	 * Retrieves the blog IDs of public, "active" sites on the network.
	 *
	 * @return array
	 */
	protected function get_blog_ids() {
		$criteria = [
			'archived'   => 0,
			'deleted'    => 0,
			'public'     => 1,
			'spam'       => 0,
			'fields'     => 'ids',
			'network_id' => \get_current_network_id(),
		];

		return \get_sites( $criteria );
	}
}
wp-robots-integration.php000066600000013010151127152550011535 0ustar00<?php

namespace Yoast\WP\SEO\Integrations\Front_End;

use Yoast\WP\SEO\Conditionals\Front_End_Conditional;
use Yoast\WP\SEO\Conditionals\WP_Robots_Conditional;
use Yoast\WP\SEO\Integrations\Integration_Interface;
use Yoast\WP\SEO\Memoizers\Meta_Tags_Context_Memoizer;
use Yoast\WP\SEO\Presenters\Robots_Presenter;

/**
 * Class WP_Robots_Integration
 *
 * @package Yoast\WP\SEO\Integrations\Front_End
 */
class WP_Robots_Integration implements Integration_Interface {

	/**
	 * The meta tags context memoizer.
	 *
	 * @var Meta_Tags_Context_Memoizer
	 */
	protected $context_memoizer;

	/**
	 * Sets the dependencies for this integration.
	 *
	 * @param Meta_Tags_Context_Memoizer $context_memoizer The meta tags context memoizer.
	 */
	public function __construct( Meta_Tags_Context_Memoizer $context_memoizer ) {
		$this->context_memoizer = $context_memoizer;
	}

	/**
	 * Initializes the integration.
	 *
	 * This is the place to register hooks and filters.
	 *
	 * @return void
	 */
	public function register_hooks() {
		/**
		 * Allow control of the `wp_robots` filter by prioritizing our hook 10 less than max.
		 * Use the `wpseo_robots` filter to filter the Yoast robots output, instead of WordPress core.
		 */
		\add_filter( 'wp_robots', [ $this, 'add_robots' ], ( \PHP_INT_MAX - 10 ) );
	}

	/**
	 * Returns the conditionals based on which this loadable should be active.
	 *
	 * @return array The conditionals.
	 */
	public static function get_conditionals() {
		return [
			Front_End_Conditional::class,
			WP_Robots_Conditional::class,
		];
	}

	/**
	 * Adds our robots tag value to the WordPress robots tag output.
	 *
	 * @param array $robots The current robots data.
	 *
	 * @return array The robots data.
	 */
	public function add_robots( $robots ) {
		if ( ! \is_array( $robots ) ) {
			return $this->get_robots_value();
		}

		$merged_robots   = \array_merge( $robots, $this->get_robots_value() );
		$filtered_robots = $this->enforce_robots_congruence( $merged_robots );
		$sorted_robots   = $this->sort_robots( $filtered_robots );

		// Filter all falsy-null robot values.
		return \array_filter( $sorted_robots );
	}

	/**
	 * Retrieves the robots key-value pairs.
	 *
	 * @return array The robots key-value pairs.
	 */
	protected function get_robots_value() {
		$context = $this->context_memoizer->for_current_page();

		$robots_presenter               = new Robots_Presenter();
		$robots_presenter->presentation = $context->presentation;
		return $this->format_robots( $robots_presenter->get() );
	}

	/**
	 * Formats our robots fields, to match the pattern WordPress is using.
	 *
	 * Our format: `[ 'index' => 'noindex', 'max-image-preview' => 'max-image-preview:large', ... ]`
	 * WordPress format: `[ 'noindex' => true, 'max-image-preview' => 'large', ... ]`
	 *
	 * @param array $robots Our robots value.
	 *
	 * @return array The formatted robots.
	 */
	protected function format_robots( $robots ) {
		foreach ( $robots as $key => $value ) {
			// When the entry represents for example: max-image-preview:large.
			$colon_position = \strpos( $value, ':' );
			if ( $colon_position !== false ) {
				$robots[ $key ] = \substr( $value, ( $colon_position + 1 ) );

				continue;
			}

			// When index => noindex, we want a separate noindex as entry in array.
			if ( \strpos( $value, 'no' ) === 0 ) {
				$robots[ $key ]   = false;
				$robots[ $value ] = true;

				continue;
			}

			// When the key is equal to the value, just make its value a boolean.
			if ( $key === $value ) {
				$robots[ $key ] = true;
			}
		}

		return $robots;
	}

	/**
	 * Ensures all other possible robots values are congruent with nofollow and or noindex.
	 *
	 * WordPress might add some robot values again.
	 * When the page is set to noindex we want to filter out these values.
	 *
	 * @param array $robots The robots.
	 *
	 * @return array The filtered robots.
	 */
	protected function enforce_robots_congruence( $robots ) {
		if ( ! empty( $robots['nofollow'] ) ) {
			$robots['follow'] = null;
		}
		if ( ! empty( $robots['noarchive'] ) ) {
			$robots['archive'] = null;
		}
		if ( ! empty( $robots['noimageindex'] ) ) {
			$robots['imageindex'] = null;

			// `max-image-preview` should set be to `none` when `noimageindex` is present.
			// Using `isset` rather than `! empty` here so that in the rare case of `max-image-preview`
			// being equal to an empty string due to filtering, its value would still be set to `none`.
			if ( isset( $robots['max-image-preview'] ) ) {
				$robots['max-image-preview'] = 'none';
			}
		}
		if ( ! empty( $robots['nosnippet'] ) ) {
			$robots['snippet'] = null;
		}
		if ( ! empty( $robots['noindex'] ) ) {
			$robots['index']             = null;
			$robots['imageindex']        = null;
			$robots['noimageindex']      = null;
			$robots['archive']           = null;
			$robots['noarchive']         = null;
			$robots['snippet']           = null;
			$robots['nosnippet']         = null;
			$robots['max-snippet']       = null;
			$robots['max-image-preview'] = null;
			$robots['max-video-preview'] = null;
		}

		return $robots;
	}

	/**
	 * Sorts the robots array.
	 *
	 * @param array $robots The robots array.
	 *
	 * @return array The sorted robots array.
	 */
	protected function sort_robots( $robots ) {
		\uksort(
			$robots,
			static function ( $a, $b ) {
				$order = [
					'index'             => 0,
					'noindex'           => 1,
					'follow'            => 2,
					'nofollow'          => 3,
				];
				$ai    = isset( $order[ $a ] ) ? $order[ $a ] : 4;
				$bi    = isset( $order[ $b ] ) ? $order[ $b ] : 4;

				return ( $ai - $bi );
			}
		);

		return $robots;
	}
}
rss-footer-embed.php000066600000012334151127152550010445 0ustar00<?php

namespace Yoast\WP\SEO\Integrations\Front_End;

use Yoast\WP\SEO\Conditionals\Front_End_Conditional;
use Yoast\WP\SEO\Helpers\Options_Helper;
use Yoast\WP\SEO\Integrations\Integration_Interface;

/**
 * Class RSS_Footer_Embed.
 */
class RSS_Footer_Embed implements Integration_Interface {

	/**
	 * The options helper.
	 *
	 * @var Options_Helper
	 */
	protected $options;

	/**
	 * Returns the conditionals based in which this loadable should be active.
	 *
	 * @return array
	 */
	public static function get_conditionals() {
		return [ Front_End_Conditional::class ];
	}

	/**
	 * Sets the required helpers.
	 *
	 * @codeCoverageIgnore It only handles dependencies.
	 *
	 * @param Options_Helper $options The options helper.
	 */
	public function __construct( Options_Helper $options ) {
		$this->options = $options;
	}

	/**
	 * Initializes the integration.
	 *
	 * This is the place to register hooks and filters.
	 *
	 * @return void
	 */
	public function register_hooks() {
		\add_filter( 'the_content_feed', [ $this, 'embed_rssfooter' ] );
		\add_filter( 'the_excerpt_rss', [ $this, 'embed_rssfooter_excerpt' ] );
	}

	/**
	 * Adds the RSS footer (or header) to the full RSS feed item.
	 *
	 * @param string $content Feed item content.
	 *
	 * @return string
	 */
	public function embed_rssfooter( $content ) {
		if ( ! $this->include_rss_footer( 'full' ) ) {
			return $content;
		}

		return $this->embed_rss( $content );
	}

	/**
	 * Adds the RSS footer (or header) to the excerpt RSS feed item.
	 *
	 * @param string $content Feed item excerpt.
	 *
	 * @return string
	 */
	public function embed_rssfooter_excerpt( $content ) {
		if ( ! $this->include_rss_footer( 'excerpt' ) ) {
			return $content;
		}

		return $this->embed_rss( \wpautop( $content ) );
	}

	/**
	 * Checks if the RSS footer should included.
	 *
	 * @param string $context The context of the RSS content.
	 *
	 * @return bool Whether or not the RSS footer should included.
	 */
	protected function include_rss_footer( $context ) {
		if ( ! \is_feed() ) {
			return false;
		}

		/**
		 * Filter: 'wpseo_include_rss_footer' - Allow the RSS footer to be dynamically shown/hidden.
		 *
		 * @api boolean $show_embed Indicates if the RSS footer should be shown or not.
		 *
		 * @param string $context The context of the RSS content - 'full' or 'excerpt'.
		 */
		if ( ! \apply_filters( 'wpseo_include_rss_footer', true, $context ) ) {
			return false;
		}

		return $this->is_configured();
	}

	/**
	 * Checks if the RSS feed fields are configured.
	 *
	 * @return bool True when one of the fields has a value.
	 */
	protected function is_configured() {
		return ( $this->options->get( 'rssbefore', '' ) !== '' || $this->options->get( 'rssafter', '' ) );
	}

	/**
	 * Adds the RSS footer and/or header to an RSS feed item.
	 *
	 * @param string $content Feed item content.
	 *
	 * @return string The content to add.
	 */
	protected function embed_rss( $content ) {
		$before  = $this->rss_replace_vars( $this->options->get( 'rssbefore', '' ) );
		$after   = $this->rss_replace_vars( $this->options->get( 'rssafter', '' ) );
		$content = $before . $content . $after;

		return $content;
	}

	/**
	 * Replaces the possible RSS variables with their actual values.
	 *
	 * @param string $content The RSS content that should have the variables replaced.
	 *
	 * @return string
	 */
	protected function rss_replace_vars( $content ) {
		if ( $content === '' ) {
			return $content;
		}

		$replace_vars = $this->get_replace_vars( $this->get_link_template(), \get_post() );

		$content = \stripslashes( \trim( $content ) );
		$content = \str_ireplace( \array_keys( $replace_vars ), \array_values( $replace_vars ), $content );

		return \wpautop( $content );
	}

	/**
	 * Retrieves the replacement variables.
	 *
	 * @codeCoverageIgnore It just contains too much WordPress functions.
	 *
	 * @param string $link_template The link template.
	 * @param mixed  $post          The post to use.
	 *
	 * @return array The replacement variables.
	 */
	protected function get_replace_vars( $link_template, $post ) {
		$author_link = '';
		if ( \is_object( $post ) ) {
			$author_link = \sprintf( $link_template, \esc_url( \get_author_posts_url( $post->post_author ) ), \esc_html( \get_the_author() ) );
		}

		return [
			'%%AUTHORLINK%%'   => $author_link,
			'%%POSTLINK%%'     => \sprintf( $link_template, \esc_url( \get_permalink() ), \esc_html( \get_the_title() ) ),
			'%%BLOGLINK%%'     => \sprintf( $link_template, \esc_url( \get_bloginfo( 'url' ) ), \esc_html( \get_bloginfo( 'name' ) ) ),
			'%%BLOGDESCLINK%%' => \sprintf( $link_template, \esc_url( \get_bloginfo( 'url' ) ), \esc_html( \get_bloginfo( 'name' ) ) . ' - ' . \esc_html( \get_bloginfo( 'description' ) ) ),
		];
	}

	/**
	 * Retrieves the link template.
	 *
	 * @return string The link template.
	 */
	protected function get_link_template() {
		/**
		 * Filter: 'nofollow_rss_links' - Allow the developer to determine whether or not to follow the links in
		 * the bits Yoast SEO adds to the RSS feed, defaults to true.
		 *
		 * @api bool $unsigned Whether or not to follow the links in RSS feed, defaults to true.
		 *
		 * @since 1.4.20
		 */
		if ( \apply_filters( 'nofollow_rss_links', true ) ) {
			return '<a rel="nofollow" href="%1$s">%2$s</a>';
		}

		return '<a href="%1$s">%2$s</a>';
	}
}
open-graph-oembed.php000066600000006115151127152550010561 0ustar00<?php

namespace Yoast\WP\SEO\Integrations\Front_End;

use WP_Post;
use Yoast\WP\SEO\Conditionals\Front_End_Conditional;
use Yoast\WP\SEO\Conditionals\Open_Graph_Conditional;
use Yoast\WP\SEO\Integrations\Integration_Interface;
use Yoast\WP\SEO\Surfaces\Meta_Surface;

/**
 * Class Open_Graph_OEmbed.
 */
class Open_Graph_OEmbed implements Integration_Interface {

	/**
	 * The meta surface.
	 *
	 * @var Meta_Surface
	 */
	private $meta;

	/**
	 * The oEmbed data.
	 *
	 * @var array
	 */
	private $data;

	/**
	 * The post ID for the current post.
	 *
	 * @var int
	 */
	private $post_id;

	/**
	 * Returns the conditionals based in which this loadable should be active.
	 *
	 * @return array
	 */
	public static function get_conditionals() {
		return [ Front_End_Conditional::class, Open_Graph_Conditional::class ];
	}

	/**
	 * Initializes the integration.
	 *
	 * This is the place to register hooks and filters.
	 *
	 * @return void
	 */
	public function register_hooks() {
		\add_filter( 'oembed_response_data', [ $this, 'set_oembed_data' ], 10, 2 );
	}

	/**
	 * Open_Graph_OEmbed constructor.
	 *
	 * @param Meta_Surface $meta The meta surface.
	 */
	public function __construct( Meta_Surface $meta ) {
		$this->meta = $meta;
	}

	/**
	 * Callback function to pass to the oEmbed's response data that will enable
	 * support for using the image and title set by the WordPress SEO plugin's fields. This
	 * address the concern where some social channels/subscribed use oEmebed data over Open Graph data
	 * if both are present.
	 *
	 * @link https://developer.wordpress.org/reference/hooks/oembed_response_data/ for hook info.
	 *
	 * @param array   $data The oEmbed data.
	 * @param WP_Post $post The current Post object.
	 *
	 * @return array An array of oEmbed data with modified values where appropriate.
	 */
	public function set_oembed_data( $data, $post ) {
		// Data to be returned.
		$this->data    = $data;
		$this->post_id = $post->ID;

		$this->set_title();
		$this->set_description();
		$this->set_image();

		return $this->data;
	}

	/**
	 * Sets the OpenGraph title if configured.
	 */
	protected function set_title() {
		$opengraph_title = $this->meta->for_post( $this->post_id )->open_graph_title;

		if ( ! empty( $opengraph_title ) ) {
			$this->data['title'] = $opengraph_title;
		}
	}

	/**
	 * Sets the OpenGraph description if configured.
	 */
	protected function set_description() {
		$opengraph_description = $this->meta->for_post( $this->post_id )->open_graph_description;

		if ( ! empty( $opengraph_description ) ) {
			$this->data['description'] = $opengraph_description;
		}
	}

	/**
	 * Sets the image if it has been configured.
	 */
	protected function set_image() {
		$images = $this->meta->for_post( $this->post_id )->open_graph_images;
		$image  = \reset( $images );

		if ( empty( $image ) ) {
			return;
		}

		if ( ! isset( $image['url'] ) ) {
			return;
		}

		$this->data['thumbnail_url'] = $image['url'];

		if ( isset( $image['width'] ) ) {
			$this->data['thumbnail_width'] = $image['width'];
		}

		if ( isset( $image['height'] ) ) {
			$this->data['thumbnail_height'] = $image['height'];
		}
	}
}
force-rewrite-title.php000066600000010630151127152550011161 0ustar00<?php

namespace Yoast\WP\SEO\Integrations\Front_End;

use Yoast\WP\SEO\Conditionals\Front_End_Conditional;
use Yoast\WP\SEO\Helpers\Options_Helper;
use Yoast\WP\SEO\Integrations\Integration_Interface;
use Yoast\WP\SEO\Wrappers\WP_Query_Wrapper;

/**
 * Class Force_Rewrite_Title.
 */
class Force_Rewrite_Title implements Integration_Interface {

	/**
	 * The options helper.
	 *
	 * @var Options_Helper
	 */
	private $options;

	/**
	 * Toggle indicating whether output buffering has been started.
	 *
	 * @var bool
	 */
	private $ob_started = false;

	/**
	 * The WP Query wrapper.
	 *
	 * @var WP_Query_Wrapper
	 */
	private $wp_query;

	/**
	 * Sets the helpers.
	 *
	 * @codeCoverageIgnore It just handles dependencies.
	 *
	 * @param Options_Helper   $options  Options helper.
	 * @param WP_Query_Wrapper $wp_query WP query wrapper.
	 */
	public function __construct( Options_Helper $options, WP_Query_Wrapper $wp_query ) {
		$this->options  = $options;
		$this->wp_query = $wp_query;
	}

	/**
	 * Returns the conditionals based in which this loadable should be active.
	 *
	 * @return array
	 */
	public static function get_conditionals() {
		return [ Front_End_Conditional::class ];
	}

	/**
	 * Initializes the integration.
	 *
	 * This is the place to register hooks and filters.
	 *
	 * @codeCoverageIgnore
	 *
	 * @return void
	 */
	public function register_hooks() {
		// When the option is disabled.
		if ( ! $this->options->get( 'forcerewritetitle', false ) ) {
			return;
		}

		// For WordPress versions below 4.4.
		if ( \current_theme_supports( 'title-tag' ) ) {
			return;
		}

		\add_action( 'template_redirect', [ $this, 'force_rewrite_output_buffer' ], 99999 );
		\add_action( 'wp_footer', [ $this, 'flush_cache' ], -1 );
	}

	/**
	 * Used in the force rewrite functionality this retrieves the output, replaces the title with the proper SEO
	 * title and then flushes the output.
	 *
	 * @return bool
	 */
	public function flush_cache() {
		if ( $this->ob_started !== true ) {
			return false;
		}

		$content = $this->get_buffered_output();

		$old_wp_query = $this->wp_query->get_query();

		\wp_reset_query();

		// When the file has the debug mark.
		if ( \preg_match( '/(?\'before\'.*)<!-- This site is optimized with the Yoast SEO.*<!-- \/ Yoast SEO( Premium)? plugin. -->(?\'after\'.*)/is', $content, $matches ) ) {
			$content = $this->replace_titles_from_content( $content, $matches );

			unset( $matches );
		}

		// phpcs:ignore WordPress.WP.GlobalVariablesOverride -- The query gets reset here with the original query.
		$GLOBALS['wp_query'] = $old_wp_query;

		// phpcs:ignore WordPress.Security.EscapeOutput -- The output should already have been escaped, we are only filtering it.
		echo $content;

		return true;
	}

	/**
	 * Starts the output buffer so it can later be fixed by flush_cache().
	 */
	public function force_rewrite_output_buffer() {
		$this->ob_started = true;
		$this->start_output_buffering();
	}

	/**
	 * Replaces the titles from the parts that contains a title.
	 *
	 * @param string $content          The content to remove the titles from.
	 * @param array  $parts_with_title The parts containing a title.
	 *
	 * @return string The modified content.
	 */
	protected function replace_titles_from_content( $content, $parts_with_title ) {
		if ( isset( $parts_with_title['before'] ) && \is_string( $parts_with_title['before'] ) ) {
			$content = $this->replace_title( $parts_with_title['before'], $content );
		}

		if ( isset( $parts_with_title['after'] ) ) {
			$content = $this->replace_title( $parts_with_title['after'], $content );
		}

		return $content;
	}

	/**
	 * Removes the title from the part that contains the title and put this modified part back
	 * into the content.
	 *
	 * @param string $part_with_title The part with the title that needs to be replaced.
	 * @param string $content         The entire content.
	 *
	 * @return string The altered content.
	 */
	protected function replace_title( $part_with_title, $content ) {
		$part_without_title = \preg_replace( '/<title.*?\/title>/i', '', $part_with_title );

		return \str_replace( $part_with_title, $part_without_title, $content );
	}

	/**
	 * Starts the output buffering.
	 *
	 * @codeCoverageIgnore
	 */
	protected function start_output_buffering() {
		\ob_start();
	}

	/**
	 * Retrieves the buffered output.
	 *
	 * @codeCoverageIgnore
	 *
	 * @return false|string The buffered output.
	 */
	protected function get_buffered_output() {
		return \ob_get_clean();
	}
}
redirects.php000066600000015707151127152550007263 0ustar00<?php

namespace Yoast\WP\SEO\Integrations\Front_End;

use Yoast\WP\SEO\Conditionals\Front_End_Conditional;
use Yoast\WP\SEO\Helpers\Current_Page_Helper;
use Yoast\WP\SEO\Helpers\Meta_Helper;
use Yoast\WP\SEO\Helpers\Options_Helper;
use Yoast\WP\SEO\Helpers\Redirect_Helper;
use Yoast\WP\SEO\Helpers\Url_Helper;
use Yoast\WP\SEO\Integrations\Integration_Interface;

/**
 * Class Redirects.
 */
class Redirects implements Integration_Interface {

	/**
	 * The options helper.
	 *
	 * @var Options_Helper
	 */
	protected $options;

	/**
	 * The meta helper.
	 *
	 * @var Meta_Helper
	 */
	protected $meta;

	/**
	 * The current page helper.
	 *
	 * @var Current_Page_Helper
	 */
	protected $current_page;

	/**
	 * The redirect helper.
	 *
	 * @var Redirect_Helper
	 */
	private $redirect;

	/**
	 * The URL helper.
	 *
	 * @var Url_Helper
	 */
	private $url;

	/**
	 * Holds the WP_Query variables we should get rid of.
	 *
	 * @var string[]
	 */
	private $date_query_variables = [
		'year',
		'm',
		'monthnum',
		'day',
		'hour',
		'minute',
		'second',
	];

	/**
	 * Sets the helpers.
	 *
	 * @codeCoverageIgnore
	 *
	 * @param Options_Helper      $options      Options helper.
	 * @param Meta_Helper         $meta         Meta helper.
	 * @param Current_Page_Helper $current_page The current page helper.
	 * @param Redirect_Helper     $redirect     The redirect helper.
	 * @param Url_Helper          $url          The URL helper.
	 */
	public function __construct( Options_Helper $options, Meta_Helper $meta, Current_Page_Helper $current_page, Redirect_Helper $redirect, Url_Helper $url ) {
		$this->options      = $options;
		$this->meta         = $meta;
		$this->current_page = $current_page;
		$this->redirect     = $redirect;
		$this->url          = $url;
	}

	/**
	 * Returns the conditionals based in which this loadable should be active.
	 *
	 * @return array
	 */
	public static function get_conditionals() {
		return [ Front_End_Conditional::class ];
	}

	/**
	 * Initializes the integration.
	 *
	 * This is the place to register hooks and filters.
	 *
	 * @return void
	 */
	public function register_hooks() {
		\add_action( 'wp', [ $this, 'archive_redirect' ] );
		\add_action( 'wp', [ $this, 'page_redirect' ], 99 );
		\add_action( 'wp', [ $this, 'category_redirect' ] );
		\add_action( 'template_redirect', [ $this, 'attachment_redirect' ], 1 );
		\add_action( 'template_redirect', [ $this, 'disable_date_queries' ] );
	}

	/**
	 * Disable date queries, if they're disabled in Yoast SEO settings, to prevent indexing the wrong things.
	 *
	 * @return void
	 */
	public function disable_date_queries() {
		if ( $this->options->get( 'disable-date', false ) ) {
			$exploded_url                    = \explode( '?', $this->url->recreate_current_url(), 2 );
			list( $base_url, $query_string ) = \array_pad( $exploded_url, 2, '' );
			\parse_str( $query_string, $query_vars );
			foreach ( $this->date_query_variables as $variable ) {
				if ( \in_array( $variable, \array_keys( $query_vars ), true ) ) {
					$this->do_date_redirect( $query_vars, $base_url );
				}
			}
		}
	}

	/**
	 * When certain archives are disabled, this redirects those to the homepage.
	 */
	public function archive_redirect() {
		if ( $this->need_archive_redirect() ) {
			$this->redirect->do_safe_redirect( \get_bloginfo( 'url' ), 301 );
		}
	}

	/**
	 * Based on the redirect meta value, this function determines whether it should redirect the current post / page.
	 */
	public function page_redirect() {
		if ( ! $this->current_page->is_simple_page() ) {
			return;
		}

		$post = \get_post();
		if ( ! \is_object( $post ) ) {
			return;
		}

		$redirect = $this->meta->get_value( 'redirect', $post->ID );
		if ( $redirect === '' ) {
			return;
		}

		$this->redirect->do_safe_redirect( $redirect, 301 );
	}

	/**
	 * If the option to disable attachment URLs is checked, this performs the redirect to the attachment.
	 */
	public function attachment_redirect() {
		if ( ! $this->current_page->is_attachment() ) {
			return;
		}

		if ( $this->options->get( 'disable-attachment', false ) === false ) {
			return;
		}

		$url = $this->get_attachment_url();
		if ( empty( $url ) ) {
			return;
		}

		$this->redirect->do_unsafe_redirect( $url, 301 );
	}

	/**
	 * Checks if certain archive pages are disabled to determine if a archive redirect is needed.
	 *
	 * @codeCoverageIgnore
	 *
	 * @return bool Whether or not to redirect an archive page.
	 */
	protected function need_archive_redirect() {
		if ( $this->options->get( 'disable-date', false ) && $this->current_page->is_date_archive() ) {
			return true;
		}

		if ( $this->options->get( 'disable-author', false ) && $this->current_page->is_author_archive() ) {
			return true;
		}

		if ( $this->options->get( 'disable-post_format', false ) && $this->current_page->is_post_format_archive() ) {
			return true;
		}

		return false;
	}

	/**
	 * Retrieves the attachment url for the current page.
	 *
	 * @codeCoverageIgnore It wraps WordPress functions.
	 *
	 * @return string The attachment url.
	 */
	protected function get_attachment_url() {
		/**
		 * Allows the developer to change the target redirection URL for attachments.
		 *
		 * @api string $attachment_url The attachment URL for the queried object.
		 * @api object $queried_object The queried object.
		 *
		 * @since 7.5.3
		 */
		return \apply_filters(
			'wpseo_attachment_redirect_url',
			\wp_get_attachment_url( \get_queried_object_id() ),
			\get_queried_object()
		);
	}

	/**
	 * Redirects away query variables that shouldn't work.
	 *
	 * @param array  $query_vars The query variables in the current URL.
	 * @param string $base_url   The base URL without query string.
	 *
	 * @return void
	 */
	private function do_date_redirect( $query_vars, $base_url ) {
		foreach ( $this->date_query_variables as $variable ) {
			unset( $query_vars[ $variable ] );
		}
		$url = $base_url;
		if ( \count( $query_vars ) > 0 ) {
			$url .= '?' . \http_build_query( $query_vars );
		}

		$this->redirect->do_safe_redirect( $url, 301 );
	}

	/**
	 * Strips `cat=-1` from the URL and redirects to the resulting URL.
	 */
	public function category_redirect() {
		/**
		 * Allows the developer to keep cat=-1 GET parameters
		 *
		 * @since 19.9
		 *
		 * @param bool $remove_cat_parameter Whether to remove the `cat=-1` GET parameter. Default true.
		 */
		$should_remove_parameter = \apply_filters( 'wpseo_remove_cat_parameter', true );

		// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Data is not processed or saved.
		if ( $should_remove_parameter && isset( $_GET['cat'] ) && $_GET['cat'] === '-1' ) {
			// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Data is not processed or saved.
			unset( $_GET['cat'] );
			if ( isset( $_SERVER['REQUEST_URI'] ) ) {
				// phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- This is just a replace and the data is never saved.
				$_SERVER['REQUEST_URI'] = \remove_query_arg( 'cat' );
			}
			$this->redirect->do_safe_redirect( $this->url->recreate_current_url(), 301, 'Stripping cat=-1 from the URL' );
		}
	}
}
category-term-description.php000066600000002434151127152550012373 0ustar00<?php

namespace Yoast\WP\SEO\Integrations\Front_End;

use Yoast\WP\SEO\Conditionals\Front_End_Conditional;
use Yoast\WP\SEO\Integrations\Integration_Interface;

/**
 * Adds support for shortcodes to category and term descriptions.
 */
class Category_Term_Description implements Integration_Interface {

	/**
	 * Returns the conditionals based in which this loadable should be active.
	 *
	 * @return array
	 */
	public static function get_conditionals() {
		return [ Front_End_Conditional::class ];
	}

	/**
	 * Initializes the integration.
	 *
	 * This is the place to register hooks and filters.
	 *
	 * @return void
	 */
	public function register_hooks() {
		\add_filter( 'category_description', [ $this, 'add_shortcode_support' ] );
		\add_filter( 'term_description', [ $this, 'add_shortcode_support' ] );
	}

	/**
	 * Adds shortcode support to category and term descriptions.
	 *
	 * This methods wrap in output buffering to prevent shortcodes that echo stuff
	 * instead of return from breaking things.
	 *
	 * @param string $description String to add shortcodes in.
	 *
	 * @return string Content with shortcodes filtered out.
	 */
	public function add_shortcode_support( $description ) {
		\ob_start();
		$description = \do_shortcode( $description );
		\ob_end_clean();

		return $description;
	}
}
theme-titles.php000066600000002272151127152550007674 0ustar00<?php

namespace Yoast\WP\SEO\Integrations\Front_End;

use Yoast\WP\SEO\Conditionals\Front_End_Conditional;
use Yoast\WP\SEO\Integrations\Integration_Interface;

/**
 * Notify the user by giving a deprecated notice.
 */
class Theme_Titles implements Integration_Interface {

	/**
	 * Returns the conditionals based in which this loadable should be active.
	 *
	 * @return array
	 */
	public static function get_conditionals() {
		return [ Front_End_Conditional::class ];
	}

	/**
	 * Initializes the integration.
	 *
	 * This is the place to register hooks and filters.
	 *
	 * @return void
	 */
	public function register_hooks() {
		\add_filter( 'thematic_doctitle', [ $this, 'title' ], 15 );
		\add_filter( 'woo_title', [ $this, 'title' ], 99 );
	}

	/**
	 * Filters the title for woo_title and the thematic_doctitle.
	 *
	 * @deprecated 14.0
	 *
	 * @codeCoverageIgnore
	 *
	 * @param string $title The title.
	 *
	 * @return string The title.
	 */
	public function title( $title ) {
		\_deprecated_function(
			__METHOD__,
			'WPSEO 14.0',
			\esc_html__(
				'a theme that has proper title-tag theme support, or adapt your theme to have that support',
				'wordpress-seo'
			)
		);

		return $title;
	}
}
backwards-compatibility.php000066600000003301151127152550012072 0ustar00<?php

namespace Yoast\WP\SEO\Integrations\Front_End;

use Yoast\WP\SEO\Conditionals\Front_End_Conditional;
use Yoast\WP\SEO\Helpers\Options_Helper;
use Yoast\WP\SEO\Integrations\Integration_Interface;

/**
 * Adds actions that were previously called and are now deprecated.
 */
class Backwards_Compatibility implements Integration_Interface {

	/**
	 * Represents the options helper.
	 *
	 * @var Options_Helper
	 */
	protected $options;

	/**
	 * Returns the conditionals based in which this loadable should be active.
	 *
	 * @return array
	 */
	public static function get_conditionals() {
		return [ Front_End_Conditional::class ];
	}

	/**
	 * Backwards_Compatibility constructor
	 *
	 * @param Options_Helper $options The options helper.
	 */
	public function __construct( Options_Helper $options ) {
		$this->options = $options;
	}

	/**
	 * Initializes the integration.
	 *
	 * This is the place to register hooks and filters.
	 *
	 * @return void
	 */
	public function register_hooks() {
		if ( $this->options->get( 'opengraph' ) === true ) {
			\add_action( 'wpseo_head', [ $this, 'call_wpseo_opengraph' ], 30 );
		}
		if ( $this->options->get( 'twitter' ) === true && \apply_filters( 'wpseo_output_twitter_card', true ) !== false ) {
			\add_action( 'wpseo_head', [ $this, 'call_wpseo_twitter' ], 40 );
		}
	}

	/**
	 * Calls the old wpseo_opengraph action.
	 *
	 * @return void
	 */
	public function call_wpseo_opengraph() {
		\do_action_deprecated( 'wpseo_opengraph', [], '14.0', 'wpseo_frontend_presenters' );
	}

	/**
	 * Calls the old wpseo_twitter action.
	 *
	 * @return void
	 */
	public function call_wpseo_twitter() {
		\do_action_deprecated( 'wpseo_twitter', [], '14.0', 'wpseo_frontend_presenters' );
	}
}
feed-improvements.php000066600000010677151127152550010731 0ustar00<?php

namespace Yoast\WP\SEO\Integrations\Front_End;

use Yoast\WP\SEO\Conditionals\Front_End_Conditional;
use Yoast\WP\SEO\Helpers\Options_Helper;
use Yoast\WP\SEO\Integrations\Integration_Interface;
use Yoast\WP\SEO\Surfaces\Meta_Surface;

/**
 * Class Feed_Improvements
 */
class Feed_Improvements implements Integration_Interface {

	/**
	 * Holds the options helper.
	 *
	 * @var Options_Helper
	 */
	private $options;

	/**
	 * Holds the meta helper surface.
	 *
	 * @var Meta_Surface
	 */
	private $meta;

	/**
	 * Canonical_Header constructor.
	 *
	 * @codeCoverageIgnore It only sets depedencies.
	 *
	 * @param Options_Helper $options The options helper.
	 * @param Meta_Surface   $meta    The meta surface.
	 */
	public function __construct(
		Options_Helper $options,
		Meta_Surface $meta
	) {
		$this->options = $options;
		$this->meta    = $meta;
	}

	/**
	 * Returns the conditionals based in which this loadable should be active.
	 *
	 * @return array
	 */
	public static function get_conditionals() {
		return [ Front_End_Conditional::class ];
	}

	/**
	 * Registers hooks to WordPress.
	 *
	 * @return void
	 */
	public function register_hooks() {
		\add_filter( 'get_bloginfo_rss', [ $this, 'filter_bloginfo_rss' ], 10, 2 );
		\add_filter( 'document_title_separator', [ $this, 'filter_document_title_separator' ] );

		\add_action( 'do_feed_rss', [ $this, 'handle_rss_feed' ], 9 );
		\add_action( 'do_feed_rss2', [ $this, 'send_canonical_header' ], 9 );
		\add_action( 'do_feed_rss2', [ $this, 'add_robots_headers' ], 9 );
	}

	/**
	 * Filter `bloginfo_rss` output to give the URL for what's being shown instead of just always the homepage.
	 *
	 * @param string $show The output so far.
	 * @param string $what What is being shown.
	 *
	 * @return string
	 */
	public function filter_bloginfo_rss( $show, $what ) {
		if ( $what === 'url' ) {
			return $this->get_url_for_queried_object( $show );
		}

		return $show;
	}

	/**
	 * Makes sure send canonical header always runs, because this RSS hook does not support the for_comments parameter
	 *
	 * @return void
	 */
	public function handle_rss_feed() {
		$this->send_canonical_header( false );
	}

	/**
	 * Adds a canonical link header to the main canonical URL for the requested feed object. If it is not a comment
	 * feed.
	 *
	 * @param bool $for_comments If the RRS feed is meant for a comment feed.
	 *
	 * @return void
	 */
	public function send_canonical_header( $for_comments ) {

		if ( $for_comments || \headers_sent() ) {
			return;
		}

		$url = $this->get_url_for_queried_object( $this->meta->for_home_page()->canonical );
		if ( ! empty( $url ) ) {
			\header( \sprintf( 'Link: <%s>; rel="canonical"', $url ), false );
		}
	}

	/**
	 * Adds noindex, follow tag for comment feeds.
	 *
	 * @param bool $for_comments If the RSS feed is meant for a comment feed.
	 *
	 * @return void
	 */
	public function add_robots_headers( $for_comments ) {
		if ( $for_comments && ! \headers_sent() ) {
			\header( 'X-Robots-Tag: noindex, follow', true );
		}
	}

	/**
	 * Makes sure the title separator set in Yoast SEO is used for all feeds.
	 *
	 * @param string $separator The separator from WordPress.
	 *
	 * @return string The separator from Yoast SEO's settings.
	 */
	public function filter_document_title_separator( $separator ) {
		return \html_entity_decode( $this->options->get_title_separator() );
	}

	/**
	 * Determines the main URL for the queried object.
	 *
	 * @param string $url The URL determined so far.
	 *
	 * @return string The canonical URL for the queried object.
	 */
	protected function get_url_for_queried_object( $url = '' ) {
		$queried_object = \get_queried_object();
		// Don't call get_class with null. This gives a warning.
		$class = ( $queried_object !== null ) ? \get_class( $queried_object ) : null;

		switch ( $class ) {
			// Post type archive feeds.
			case 'WP_Post_Type':
				$url = $this->meta->for_post_type_archive( $queried_object->name )->canonical;
				break;
			// Post comment feeds.
			case 'WP_Post':
				$url = $this->meta->for_post( $queried_object->ID )->canonical;
				break;
			// Term feeds.
			case 'WP_Term':
				$url = $this->meta->for_term( $queried_object->term_id )->canonical;
				break;
			// Author feeds.
			case 'WP_User':
				$url = $this->meta->for_author( $queried_object->ID )->canonical;
				break;
			// This would be NULL on the home page and on date archive feeds.
			case null:
				$url = $this->meta->for_home_page()->canonical;
				break;
			default:
				break;
		}

		return $url;
	}
}
comment-link-fixer.php000066600000007123151127152550011000 0ustar00<?php

namespace Yoast\WP\SEO\Integrations\Front_End;

use Yoast\WP\SEO\Conditionals\Front_End_Conditional;
use Yoast\WP\SEO\Helpers\Redirect_Helper;
use Yoast\WP\SEO\Helpers\Robots_Helper;
use Yoast\WP\SEO\Integrations\Integration_Interface;

/**
 * Class Comment_Link_Fixer.
 */
class Comment_Link_Fixer implements Integration_Interface {

	/**
	 * The redirects helper.
	 *
	 * @var Redirect_Helper
	 */
	protected $redirect;

	/**
	 * The robots helper.
	 *
	 * @var Robots_Helper
	 */
	protected $robots;

	/**
	 * Returns the conditionals based in which this loadable should be active.
	 *
	 * @return array
	 */
	public static function get_conditionals() {
		return [ Front_End_Conditional::class ];
	}

	/**
	 * Comment_Link_Fixer constructor.
	 *
	 * @codeCoverageIgnore It only sets depedencies.
	 *
	 * @param Redirect_Helper $redirect The redirect helper.
	 * @param Robots_Helper   $robots   The robots helper.
	 */
	public function __construct(
		Redirect_Helper $redirect, Robots_Helper $robots
	) {
		$this->redirect = $redirect;
		$this->robots   = $robots;
	}

	/**
	 * Initializes the integration.
	 *
	 * This is the place to register hooks and filters.
	 *
	 * @return void
	 */
	public function register_hooks() {
		if ( $this->clean_reply_to_com() ) {
			\add_filter( 'comment_reply_link', [ $this, 'remove_reply_to_com' ] );
			\add_action( 'template_redirect', [ $this, 'replytocom_redirect' ], 1 );
		}

		// When users view a reply to a comment, this URL parameter is set. These should never be indexed separately.
		if ( $this->has_replytocom_parameter() ) {
			\add_filter( 'wpseo_robots_array', [ $this->robots, 'set_robots_no_index' ] );
		}
	}

	/**
	 * Checks if the url contains the ?replytocom query parameter.
	 *
	 * @codeCoverageIgnore Wraps the filter input.
	 *
	 * @return string The value of replytocom.
	 */
	protected function has_replytocom_parameter() {
		return \filter_input( \INPUT_GET, 'replytocom' );
	}

	/**
	 * Removes the ?replytocom variable from the link, replacing it with a #comment-<number> anchor.
	 *
	 * @todo Should this function also allow for relative urls ?
	 *
	 * @param string $link The comment link as a string.
	 *
	 * @return string The modified link.
	 */
	public function remove_reply_to_com( $link ) {
		return \preg_replace( '`href=(["\'])(?:.*(?:\?|&|&#038;)replytocom=(\d+)#respond)`', 'href=$1#comment-$2', $link );
	}

	/**
	 * Redirects out the ?replytocom variables.
	 *
	 * @return bool True when redirect has been done.
	 */
	public function replytocom_redirect() {
		if ( isset( $_GET['replytocom'] ) && \is_singular() ) {
			$url          = \get_permalink( $GLOBALS['post']->ID );
			$hash         = \sanitize_text_field( \wp_unslash( $_GET['replytocom'] ) );
			$query_string = '';
			if ( isset( $_SERVER['QUERY_STRING'] ) ) {
				$query_string = \remove_query_arg( 'replytocom', \sanitize_text_field( \wp_unslash( $_SERVER['QUERY_STRING'] ) ) );
			}
			if ( ! empty( $query_string ) ) {
				$url .= '?' . $query_string;
			}
			$url .= '#comment-' . $hash;

			$this->redirect->do_safe_redirect( $url, 301 );

			return true;
		}

		return false;
	}

	/**
	 * Checks whether we can allow the feature that removes ?replytocom query parameters.
	 *
	 * @codeCoverageIgnore It just wraps a call to a filter.
	 *
	 * @return bool True to remove, false not to remove.
	 */
	private function clean_reply_to_com() {
		/**
		 * Filter: 'wpseo_remove_reply_to_com' - Allow disabling the feature that removes ?replytocom query parameters.
		 *
		 * @param bool $return True to remove, false not to remove.
		 */
		return (bool) \apply_filters( 'wpseo_remove_reply_to_com', true );
	}
}
.htaccess000066600000000424151143425140006346 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>