PageRenderTime 100ms CodeModel.GetById 13ms app.highlight 75ms RepoModel.GetById 1ms app.codeStats 1ms

/wp-content/plugins/wordpress-seo/frontend/class-frontend.php

https://bitbucket.org/carloskikea/helpet
PHP | 1856 lines | 1344 code | 172 blank | 340 comment | 153 complexity | 471807c968dc906bda0cd3fe8a083113 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

   1<?php
   2/**
   3 * WPSEO plugin file.
   4 *
   5 * @package WPSEO\Frontend
   6 */
   7
   8/**
   9 * Main frontend class for Yoast SEO, responsible for the SEO output as well as removing
  10 * default WordPress output.
  11 */
  12class WPSEO_Frontend {
  13	/**
  14	 * @var    object    Instance of this class.
  15	 */
  16	public static $instance;
  17	/**
  18	 * @var boolean Boolean indicating whether output buffering has been started.
  19	 */
  20	private $ob_started = false;
  21	/**
  22	 * Holds the canonical URL for the current page.
  23	 *
  24	 * @var string
  25	 */
  26	private $canonical = null;
  27	/**
  28	 * Holds the canonical URL for the current page that cannot be overriden by a manual canonical input.
  29	 *
  30	 * @var string
  31	 */
  32	private $canonical_no_override = null;
  33	/**
  34	 * Holds the canonical URL for the current page without pagination.
  35	 *
  36	 * @var string
  37	 */
  38	private $canonical_unpaged = null;
  39	/**
  40	 * Holds the pages meta description.
  41	 *
  42	 * @var string
  43	 */
  44	private $metadesc = null;
  45	/**
  46	 * Holds the generated title for the page.
  47	 *
  48	 * @var string
  49	 */
  50	private $title = null;
  51	/** @var WPSEO_Frontend_Page_Type */
  52	protected $frontend_page_type;
  53	/** @var WPSEO_WooCommerce_Shop_Page */
  54	protected $woocommerce_shop_page;
  55
  56	/**
  57	 * Class constructor.
  58	 *
  59	 * Adds and removes a lot of filters.
  60	 */
  61	protected function __construct() {
  62
  63		add_action( 'wp_head', array( $this, 'front_page_specific_init' ), 0 );
  64		add_action( 'wp_head', array( $this, 'head' ), 1 );
  65
  66		// The head function here calls action wpseo_head, to which we hook all our functionality.
  67		add_action( 'wpseo_head', array( $this, 'debug_mark' ), 2 );
  68		add_action( 'wpseo_head', array( $this, 'metadesc' ), 6 );
  69		add_action( 'wpseo_head', array( $this, 'robots' ), 10 );
  70		add_action( 'wpseo_head', array( $this, 'canonical' ), 20 );
  71		add_action( 'wpseo_head', array( $this, 'adjacent_rel_links' ), 21 );
  72		add_action( 'wpseo_head', array( $this, 'publisher' ), 22 );
  73
  74		// Remove actions that we will handle through our wpseo_head call, and probably change the output of.
  75		remove_action( 'wp_head', 'rel_canonical' );
  76		remove_action( 'wp_head', 'index_rel_link' );
  77		remove_action( 'wp_head', 'start_post_rel_link' );
  78		remove_action( 'wp_head', 'adjacent_posts_rel_link_wp_head' );
  79		remove_action( 'wp_head', 'noindex', 1 );
  80
  81		// When using WP 4.4, just use the new hook.
  82		add_filter( 'pre_get_document_title', array( $this, 'title' ), 15 );
  83		add_filter( 'wp_title', array( $this, 'title' ), 15, 3 );
  84
  85		add_filter( 'thematic_doctitle', array( $this, 'title' ), 15 );
  86
  87		add_action( 'wp', array( $this, 'page_redirect' ), 99 );
  88
  89		add_action( 'template_redirect', array( $this, 'noindex_feed' ) );
  90
  91		add_filter( 'loginout', array( $this, 'nofollow_link' ) );
  92		add_filter( 'register', array( $this, 'nofollow_link' ) );
  93
  94		// Fix the WooThemes woo_title() output.
  95		add_filter( 'woo_title', array( $this, 'fix_woo_title' ), 99 );
  96
  97		if ( WPSEO_Options::get( 'disable-date', false )
  98			|| WPSEO_Options::get( 'disable-author', false )
  99			|| WPSEO_Options::get( 'disable-post_format', false )
 100		) {
 101			add_action( 'wp', array( $this, 'archive_redirect' ) );
 102		}
 103		add_action( 'template_redirect', array( $this, 'attachment_redirect' ), 1 );
 104
 105		add_filter( 'the_content_feed', array( $this, 'embed_rssfooter' ) );
 106		add_filter( 'the_excerpt_rss', array( $this, 'embed_rssfooter_excerpt' ) );
 107
 108		// For WordPress functions below 4.4.
 109		if ( WPSEO_Options::get( 'forcerewritetitle', false ) && ! current_theme_supports( 'title-tag' ) ) {
 110			add_action( 'template_redirect', array( $this, 'force_rewrite_output_buffer' ), 99999 );
 111			add_action( 'wp_footer', array( $this, 'flush_cache' ), - 1 );
 112		}
 113
 114		if ( WPSEO_Options::get( 'title_test', 0 ) > 0 ) {
 115			add_filter( 'wpseo_title', array( $this, 'title_test_helper' ) );
 116		}
 117
 118		$this->woocommerce_shop_page = new WPSEO_WooCommerce_Shop_Page();
 119		$this->frontend_page_type    = new WPSEO_Frontend_Page_Type();
 120
 121		$integrations = array(
 122			new WPSEO_Frontend_Primary_Category(),
 123			new WPSEO_JSON_LD(),
 124			new WPSEO_Remove_Reply_To_Com(),
 125			$this->woocommerce_shop_page,
 126		);
 127
 128		foreach ( $integrations as $integration ) {
 129			$integration->register_hooks();
 130		}
 131	}
 132
 133	/**
 134	 * Initialize the functions that only need to run on the frontpage.
 135	 */
 136	public function front_page_specific_init() {
 137		if ( ! is_front_page() ) {
 138			return;
 139		}
 140
 141		add_action( 'wpseo_head', array( $this, 'webmaster_tools_authentication' ), 90 );
 142	}
 143
 144	/**
 145	 * Resets the entire class so canonicals, titles etc can be regenerated.
 146	 */
 147	public function reset() {
 148		foreach ( get_class_vars( __CLASS__ ) as $name => $default ) {
 149			switch ( $name ) {
 150				// Clear the class instance to be re-initialized.
 151				case 'instance':
 152					self::$instance = null;
 153					break;
 154
 155				// Exclude these properties from being reset.
 156				case 'woocommerce_shop_page':
 157				case 'frontend_page_type':
 158					break;
 159
 160				// Reset property to the class default.
 161				default:
 162					$this->$name = $default;
 163					break;
 164			}
 165		}
 166		WPSEO_Options::ensure_options_exist();
 167	}
 168
 169	/**
 170	 * Get the singleton instance of this class.
 171	 *
 172	 * @return WPSEO_Frontend
 173	 */
 174	public static function get_instance() {
 175		if ( ! ( self::$instance instanceof self ) ) {
 176			self::$instance = new self();
 177		}
 178
 179		return self::$instance;
 180	}
 181
 182	/**
 183	 * Override Woo's title with our own.
 184	 *
 185	 * @param string $title Title string.
 186	 *
 187	 * @return string
 188	 */
 189	public function fix_woo_title( $title ) {
 190		return $this->title( $title );
 191	}
 192
 193	/**
 194	 * Determine whether this is the homepage and shows posts.
 195	 *
 196	 * @return bool
 197	 */
 198	public function is_home_posts_page() {
 199		return ( is_home() && 'posts' === get_option( 'show_on_front' ) );
 200	}
 201
 202	/**
 203	 * Determine whether the this is the static frontpage.
 204	 *
 205	 * @return bool
 206	 */
 207	public function is_home_static_page() {
 208		return ( is_front_page() && 'page' === get_option( 'show_on_front' ) && is_page( get_option( 'page_on_front' ) ) );
 209	}
 210
 211	/**
 212	 * Determine whether this is the posts page, when it's not the frontpage.
 213	 *
 214	 * @return bool
 215	 */
 216	public function is_posts_page() {
 217		return ( is_home() && 'page' === get_option( 'show_on_front' ) );
 218	}
 219
 220	/**
 221	 * Used for static home and posts pages as well as singular titles.
 222	 *
 223	 * @param object|null $object If filled, object to get the title for.
 224	 *
 225	 * @return string
 226	 */
 227	public function get_content_title( $object = null ) {
 228		if ( $object === null ) {
 229			$object = $GLOBALS['wp_query']->get_queried_object();
 230		}
 231
 232		$title = $this->get_seo_title( $object );
 233		if ( $title !== '' ) {
 234			return $title;
 235		}
 236
 237		$post_type = ( isset( $object->post_type ) ? $object->post_type : $object->query_var );
 238
 239		return $this->get_title_from_options( 'title-' . $post_type, $object );
 240	}
 241
 242	/**
 243	 * Retrieves the SEO title set in the SEO widget.
 244	 *
 245	 * @param null $object Object to retrieve the title from.
 246	 *
 247	 * @return string The SEO title for the specified object, or queried object if not supplied.
 248	 */
 249	public function get_seo_title( $object = null ) {
 250		if ( $object === null ) {
 251			$object = $GLOBALS['wp_query']->get_queried_object();
 252		}
 253
 254		if ( ! is_object( $object ) ) {
 255			return $this->get_title_from_options( 'title-404-wpseo' );
 256		}
 257
 258		$title = $this->get_seo_meta_value( 'title', $object->ID );
 259
 260		if ( $title !== '' ) {
 261			return $this->replace_vars( $title, $object );
 262		}
 263
 264		return $title;
 265	}
 266
 267	/**
 268	 * Used for category, tag, and tax titles.
 269	 *
 270	 * @return string
 271	 */
 272	public function get_taxonomy_title() {
 273		$object = $GLOBALS['wp_query']->get_queried_object();
 274
 275		$title = WPSEO_Taxonomy_Meta::get_term_meta( $object, $object->taxonomy, 'title' );
 276
 277		if ( is_string( $title ) && $title !== '' ) {
 278			return $this->replace_vars( $title, $object );
 279		}
 280
 281		return $this->get_title_from_options( 'title-tax-' . $object->taxonomy, $object );
 282	}
 283
 284	/**
 285	 * Used for author titles.
 286	 *
 287	 * @return string
 288	 */
 289	public function get_author_title() {
 290		$author_id = get_query_var( 'author' );
 291		$title     = trim( get_the_author_meta( 'wpseo_title', $author_id ) );
 292
 293		if ( $title !== '' ) {
 294			return $this->replace_vars( $title, array() );
 295		}
 296
 297		return $this->get_title_from_options( 'title-author-wpseo' );
 298	}
 299
 300	/**
 301	 * Simple function to use to pull data from $options.
 302	 *
 303	 * All titles pulled from options will be run through the $this->replace_vars function.
 304	 *
 305	 * @param string       $index      Name of the page to get the title from the settings for.
 306	 * @param object|array $var_source Possible object to pull variables from.
 307	 *
 308	 * @return string
 309	 */
 310	public function get_title_from_options( $index, $var_source = array() ) {
 311		$template = WPSEO_Options::get( $index, '' );
 312		if ( $template === '' ) {
 313			if ( is_singular() ) {
 314				return $this->replace_vars( '%%title%% %%sep%% %%sitename%%', $var_source );
 315			}
 316
 317			return '';
 318		}
 319
 320		return $this->replace_vars( $template, $var_source );
 321	}
 322
 323	/**
 324	 * Get the default title for the current page.
 325	 *
 326	 * This is the fallback title generator used when a title hasn't been set for the specific content, taxonomy, author
 327	 * details, or in the options. It scrubs off any present prefix before or after the title (based on $seplocation) in
 328	 * order to prevent duplicate seperations from appearing in the title (this happens when a prefix is supplied to the
 329	 * wp_title call on singular pages).
 330	 *
 331	 * @param string $sep         The separator used between variables.
 332	 * @param string $seplocation Whether the separator should be left or right.
 333	 * @param string $title       Possible title that's already set.
 334	 *
 335	 * @return string
 336	 */
 337	public function get_default_title( $sep, $seplocation, $title = '' ) {
 338		if ( 'right' === $seplocation ) {
 339			$regex = '`\s*' . preg_quote( trim( $sep ), '`' ) . '\s*`u';
 340		}
 341		else {
 342			$regex = '`^\s*' . preg_quote( trim( $sep ), '`' ) . '\s*`u';
 343		}
 344		$title = preg_replace( $regex, '', $title );
 345
 346		if ( ! is_string( $title ) || ( is_string( $title ) && $title === '' ) ) {
 347			$title = WPSEO_Utils::get_site_name();
 348			$title = $this->add_paging_to_title( $sep, $seplocation, $title );
 349			$title = $this->add_to_title( $sep, $seplocation, $title, wp_strip_all_tags( get_bloginfo( 'description' ), true ) );
 350
 351			return $title;
 352		}
 353
 354		$title = $this->add_paging_to_title( $sep, $seplocation, $title );
 355		$title = $this->add_to_title( $sep, $seplocation, $title, wp_strip_all_tags( get_bloginfo( 'name' ), true ) );
 356
 357		return $title;
 358	}
 359
 360	/**
 361	 * This function adds paging details to the title.
 362	 *
 363	 * @param string $sep         Separator used in the title.
 364	 * @param string $seplocation Whether the separator should be left or right.
 365	 * @param string $title       The title to append the paging info to.
 366	 *
 367	 * @return string
 368	 */
 369	public function add_paging_to_title( $sep, $seplocation, $title ) {
 370		global $wp_query;
 371
 372		if ( ! empty( $wp_query->query_vars['paged'] ) && $wp_query->query_vars['paged'] > 1 ) {
 373			return $this->add_to_title( $sep, $seplocation, $title, $wp_query->query_vars['paged'] . '/' . $wp_query->max_num_pages );
 374		}
 375
 376		return $title;
 377	}
 378
 379	/**
 380	 * Add part to title, while ensuring that the $seplocation variable is respected.
 381	 *
 382	 * @param string $sep         Separator used in the title.
 383	 * @param string $seplocation Whether the separator should be left or right.
 384	 * @param string $title       The title to append the title_part to.
 385	 * @param string $title_part  The part to append to the title.
 386	 *
 387	 * @return string
 388	 */
 389	public function add_to_title( $sep, $seplocation, $title, $title_part ) {
 390		if ( 'right' === $seplocation ) {
 391			return $title . $sep . $title_part;
 392		}
 393
 394		return $title_part . $sep . $title;
 395	}
 396
 397	/**
 398	 * Main title function.
 399	 *
 400	 * @param string $title              Title that might have already been set.
 401	 * @param string $separator          Separator determined in theme (unused).
 402	 * @param string $separator_location Whether the separator should be left or right.
 403	 *
 404	 * @return string
 405	 */
 406	public function title( $title, $separator = '', $separator_location = '' ) {
 407		if ( is_null( $this->title ) ) {
 408			$this->title = $this->generate_title( $title, $separator_location );
 409		}
 410
 411		return $this->title;
 412	}
 413
 414	/**
 415	 * Main title generation function.
 416	 *
 417	 * @param string $title              Title that might have already been set.
 418	 * @param string $separator_location Whether the separator should be left or right.
 419	 *
 420	 * @return string
 421	 */
 422	private function generate_title( $title, $separator_location ) {
 423
 424		if ( is_feed() ) {
 425			return $title;
 426		}
 427
 428		$separator = $this->replace_vars( '%%sep%%', array() );
 429		$separator = ' ' . trim( $separator ) . ' ';
 430
 431		if ( '' === trim( $separator_location ) ) {
 432			$separator_location = ( is_rtl() ) ? 'left' : 'right';
 433		}
 434
 435		// This needs to be kept track of in order to generate
 436		// default titles for singular pages.
 437		$original_title = $title;
 438
 439		// This flag is used to determine if any additional
 440		// processing should be done to the title after the
 441		// main section of title generation completes.
 442		$modified_title = true;
 443
 444		// This variable holds the page-specific title part
 445		// that is used to generate default titles.
 446		$title_part = '';
 447
 448		if ( $this->is_home_static_page() ) {
 449			$title = $this->get_content_title();
 450		}
 451		elseif ( $this->is_home_posts_page() ) {
 452			$title = $this->get_title_from_options( 'title-home-wpseo' );
 453		}
 454		elseif ( $this->woocommerce_shop_page->is_shop_page() ) {
 455			$post  = get_post( $this->woocommerce_shop_page->get_shop_page_id() );
 456			$title = $this->get_seo_title( $post );
 457
 458			if ( ! is_string( $title ) || $title === '' ) {
 459				$title = $this->get_post_type_archive_title( $separator, $separator_location );
 460			}
 461		}
 462		elseif ( $this->frontend_page_type->is_simple_page() ) {
 463			$post  = get_post( $this->frontend_page_type->get_simple_page_id() );
 464			$title = $this->get_content_title( $post );
 465
 466			if ( ! is_string( $title ) || '' === $title ) {
 467				$title_part = $original_title;
 468			}
 469		}
 470		elseif ( is_search() ) {
 471			$title = $this->get_title_from_options( 'title-search-wpseo' );
 472
 473			if ( ! is_string( $title ) || '' === $title ) {
 474				/* translators: %s expands to the search phrase. */
 475				$title_part = sprintf( __( 'Search for "%s"', 'wordpress-seo' ), esc_html( get_search_query() ) );
 476			}
 477		}
 478		elseif ( is_category() || is_tag() || is_tax() ) {
 479			$title = $this->get_taxonomy_title();
 480
 481			if ( ! is_string( $title ) || '' === $title ) {
 482				if ( is_category() ) {
 483					$title_part = single_cat_title( '', false );
 484				}
 485				elseif ( is_tag() ) {
 486					$title_part = single_tag_title( '', false );
 487				}
 488				else {
 489					$title_part = single_term_title( '', false );
 490					if ( $title_part === '' ) {
 491						$term       = $GLOBALS['wp_query']->get_queried_object();
 492						$title_part = $term->name;
 493					}
 494				}
 495			}
 496		}
 497		elseif ( is_author() ) {
 498			$title = $this->get_author_title();
 499
 500			if ( ! is_string( $title ) || '' === $title ) {
 501				$title_part = get_the_author_meta( 'display_name', get_query_var( 'author' ) );
 502			}
 503		}
 504		elseif ( is_post_type_archive() ) {
 505			$title = $this->get_post_type_archive_title( $separator, $separator_location );
 506		}
 507		elseif ( is_archive() ) {
 508			$title = $this->get_title_from_options( 'title-archive-wpseo' );
 509
 510			// @todo [JRF => Yoast] Should these not use the archive default if no title found ?
 511			// WPSEO_Options::get_default( 'wpseo_titles', 'title-archive-wpseo' )
 512			// Replacement would be needed!
 513			if ( empty( $title ) ) {
 514				if ( is_month() ) {
 515					/* translators: %s expands to a time period, i.e. month name, year or specific date. */
 516					$title_part = sprintf( __( '%s Archives', 'wordpress-seo' ), single_month_title( ' ', false ) );
 517				}
 518				elseif ( is_year() ) {
 519					/* translators: %s expands to a time period, i.e. month name, year or specific date. */
 520					$title_part = sprintf( __( '%s Archives', 'wordpress-seo' ), get_query_var( 'year' ) );
 521				}
 522				elseif ( is_day() ) {
 523					/* translators: %s expands to a time period, i.e. month name, year or specific date. */
 524					$title_part = sprintf( __( '%s Archives', 'wordpress-seo' ), get_the_date() );
 525				}
 526				else {
 527					$title_part = __( 'Archives', 'wordpress-seo' );
 528				}
 529			}
 530		}
 531		elseif ( is_404() ) {
 532
 533			$title = $this->get_title_from_options( 'title-404-wpseo' );
 534
 535			// @todo [JRF => Yoast] Should these not use the 404 default if no title found ?
 536			// WPSEO_Options::get_default( 'wpseo_titles', 'title-404-wpseo' )
 537			// Replacement would be needed!
 538			if ( empty( $title ) ) {
 539				$title_part = __( 'Page not found', 'wordpress-seo' );
 540			}
 541		}
 542		else {
 543			// In case the page type is unknown, leave the title alone.
 544			$modified_title = false;
 545
 546			// If you would like to generate a default title instead,
 547			// the following code could be used
 548			// $title_part = $title;
 549			// instead of the line above.
 550		}
 551
 552		if ( ( $modified_title && empty( $title ) ) || ! empty( $title_part ) ) {
 553			$title = $this->get_default_title( $separator, $separator_location, $title_part );
 554		}
 555
 556		if ( defined( 'ICL_LANGUAGE_CODE' ) && false !== strpos( $title, ICL_LANGUAGE_CODE ) ) {
 557			$title = str_replace( ' @' . ICL_LANGUAGE_CODE, '', $title );
 558		}
 559
 560		/**
 561		 * Filter: 'wpseo_title' - Allow changing the Yoast SEO <title> output.
 562		 *
 563		 * @api string $title The page title being put out.
 564		 */
 565
 566		return esc_html( wp_strip_all_tags( stripslashes( apply_filters( 'wpseo_title', $title ) ), true ) );
 567	}
 568
 569	/**
 570	 * Function used when title needs to be force overridden.
 571	 *
 572	 * @return string
 573	 */
 574	public function force_wp_title() {
 575		global $wp_query;
 576		$old_wp_query = null;
 577
 578		if ( ! $wp_query->is_main_query() ) {
 579			$old_wp_query = $wp_query;
 580			wp_reset_query();
 581		}
 582
 583		$title = $this->title( '' );
 584
 585		if ( ! empty( $old_wp_query ) ) {
 586			$GLOBALS['wp_query'] = $old_wp_query;
 587			unset( $old_wp_query );
 588		}
 589
 590		return $title;
 591	}
 592
 593	/**
 594	 * Outputs or returns the debug marker, which is also used for title replacement when force rewrite is active.
 595	 *
 596	 * @param bool $echo Deprecated. Since 5.9. Whether or not to echo the debug marker.
 597	 *
 598	 * @return string The marker that will be echoed.
 599	 */
 600	public function debug_mark( $echo = true ) {
 601		$marker = $this->get_debug_mark();
 602		if ( $echo === false ) {
 603			_deprecated_argument( 'WPSEO_Frontend::debug_mark', '5.9', 'WPSEO_Frontend::get_debug_mark' );
 604
 605			return $marker;
 606		}
 607
 608		echo "\n${marker}\n";
 609
 610		return '';
 611	}
 612
 613	/**
 614	 * Returns the debug marker, which is also used for title replacement when force rewrite is active.
 615	 *
 616	 * @return string The generated marker.
 617	 */
 618	public function get_debug_mark() {
 619		return sprintf(
 620			'<!-- This site is optimized with the %1$s %2$s - https://yoast.com/wordpress/plugins/seo/ -->',
 621			esc_html( $this->head_product_name() ),
 622			/**
 623			 * Filter: 'wpseo_hide_version' - can be used to hide the Yoast SEO version in the debug marker (only available in Yoast SEO Premium).
 624			 *
 625			 * @api bool
 626			 */
 627			( ( apply_filters( 'wpseo_hide_version', false ) && $this->is_premium() ) ? '' : 'v' . WPSEO_VERSION )
 628		);
 629	}
 630
 631	/**
 632	 * Output Webmaster Tools authentication strings.
 633	 */
 634	public function webmaster_tools_authentication() {
 635		// Baidu.
 636		$this->webmaster_tools_helper( 'baiduverify', 'baidu-site-verification' );
 637
 638		// Bing.
 639		$this->webmaster_tools_helper( 'msverify', 'msvalidate.01' );
 640
 641		// Google.
 642		$this->webmaster_tools_helper( 'googleverify', 'google-site-verification' );
 643
 644		// Pinterest.
 645		$this->webmaster_tools_helper( 'pinterestverify', 'p:domain_verify' );
 646
 647		// Yandex.
 648		$this->webmaster_tools_helper( 'yandexverify', 'yandex-verification' );
 649	}
 650
 651	/**
 652	 * Helper function for authentication.
 653	 *
 654	 * @param string $option_key Option key.
 655	 * @param string $tag_name   The tag name.
 656	 *
 657	 * @return void
 658	 */
 659	private function webmaster_tools_helper( $option_key, $tag_name ) {
 660		$auth = WPSEO_Options::get( $option_key, '' );
 661		if ( $auth !== '' ) {
 662			printf( '<meta name="%1$s" content="%2$s" />' . "\n", $tag_name, $auth );
 663		}
 664	}
 665
 666	/**
 667	 * Main wrapper function attached to wp_head. This combines all the output on the frontend of the Yoast SEO plugin.
 668	 */
 669	public function head() {
 670		global $wp_query;
 671
 672		$old_wp_query = null;
 673
 674		if ( ! $wp_query->is_main_query() ) {
 675			$old_wp_query = $wp_query;
 676			wp_reset_query();
 677		}
 678
 679		/**
 680		 * Action: 'wpseo_head' - Allow other plugins to output inside the Yoast SEO section of the head section.
 681		 */
 682		do_action( 'wpseo_head' );
 683
 684		echo $this->show_closing_debug_mark();
 685
 686		if ( ! empty( $old_wp_query ) ) {
 687			$GLOBALS['wp_query'] = $old_wp_query;
 688			unset( $old_wp_query );
 689		}
 690	}
 691
 692	/**
 693	 * Output the meta robots value.
 694	 *
 695	 * @return string
 696	 */
 697	public function robots() {
 698		global $wp_query, $post;
 699
 700		$robots           = array();
 701		$robots['index']  = 'index';
 702		$robots['follow'] = 'follow';
 703		$robots['other']  = array();
 704
 705		if ( is_object( $post ) && is_singular() ) {
 706			$private = 'private' === $post->post_status;
 707			$noindex = ! WPSEO_Post_Type::is_post_type_indexable( $post->post_type );
 708
 709			if ( $noindex || $private ) {
 710				$robots['index'] = 'noindex';
 711			}
 712
 713			$robots = $this->robots_for_single_post( $robots );
 714		}
 715		else {
 716			if ( is_search() || is_404() ) {
 717				$robots['index'] = 'noindex';
 718			}
 719			elseif ( is_tax() || is_tag() || is_category() ) {
 720				$term = $wp_query->get_queried_object();
 721				if ( is_object( $term ) && ( WPSEO_Options::get( 'noindex-tax-' . $term->taxonomy, false ) ) ) {
 722					$robots['index'] = 'noindex';
 723				}
 724
 725				// Three possible values, index, noindex and default, do nothing for default.
 726				$term_meta = WPSEO_Taxonomy_Meta::get_term_meta( $term, $term->taxonomy, 'noindex' );
 727				if ( is_string( $term_meta ) && 'default' !== $term_meta ) {
 728					$robots['index'] = $term_meta;
 729				}
 730
 731				if ( $this->is_multiple_terms_query() ) {
 732					$robots['index'] = 'noindex';
 733				}
 734			}
 735			elseif ( is_author() ) {
 736				if ( WPSEO_Options::get( 'noindex-author-wpseo', false ) ) {
 737					$robots['index'] = 'noindex';
 738				}
 739				$curauth = $wp_query->get_queried_object();
 740				if ( WPSEO_Options::get( 'noindex-author-noposts-wpseo', false ) && count_user_posts( $curauth->ID, 'any' ) === 0 ) {
 741					$robots['index'] = 'noindex';
 742				}
 743				if ( get_user_meta( $curauth->ID, 'wpseo_noindex_author', true ) === 'on' ) {
 744					$robots['index'] = 'noindex';
 745				}
 746			}
 747			elseif ( is_date() && WPSEO_Options::get( 'noindex-archive-wpseo', false ) ) {
 748				$robots['index'] = 'noindex';
 749			}
 750			elseif ( is_home() ) {
 751				$page_for_posts = get_option( 'page_for_posts' );
 752				if ( $page_for_posts ) {
 753					$robots = $this->robots_for_single_post( $robots, $page_for_posts );
 754				}
 755				unset( $page_for_posts );
 756			}
 757			elseif ( is_post_type_archive() ) {
 758				$post_type = $this->get_queried_post_type();
 759
 760				if ( WPSEO_Options::get( 'noindex-ptarchive-' . $post_type, false ) ) {
 761					$robots['index'] = 'noindex';
 762				}
 763			}
 764
 765			unset( $robot );
 766		}
 767
 768		// Force override to respect the WP settings.
 769		if ( '0' === (string) get_option( 'blog_public' ) || isset( $_GET['replytocom'] ) ) {
 770			$robots['index'] = 'noindex';
 771		}
 772
 773		$robotsstr = $robots['index'] . ',' . $robots['follow'];
 774
 775		if ( $robots['other'] !== array() ) {
 776			$robots['other'] = array_unique( $robots['other'] ); // @todo Most likely no longer needed, needs testing.
 777			$robotsstr      .= ',' . implode( ',', $robots['other'] );
 778		}
 779
 780		$robotsstr = preg_replace( '`^index,follow,?`', '', $robotsstr );
 781		$robotsstr = str_replace( array( 'noodp,', 'noodp' ), '', $robotsstr );
 782
 783		/**
 784		 * Filter: 'wpseo_robots' - Allows filtering of the meta robots output of Yoast SEO.
 785		 *
 786		 * @api string $robotsstr The meta robots directives to be echoed.
 787		 */
 788		$robotsstr = apply_filters( 'wpseo_robots', $robotsstr );
 789
 790		if ( is_string( $robotsstr ) && $robotsstr !== '' ) {
 791			echo '<meta name="robots" content="', esc_attr( $robotsstr ), '"/>', "\n";
 792		}
 793
 794		// If a page has a noindex, it should _not_ have a canonical, as these are opposing indexing directives.
 795		if ( strpos( $robotsstr, 'noindex' ) !== false ) {
 796			remove_action( 'wpseo_head', array( $this, 'canonical' ), 20 );
 797		}
 798
 799		return $robotsstr;
 800	}
 801
 802	/**
 803	 * Determine $robots values for a single post.
 804	 *
 805	 * @param array $robots  Robots data array.
 806	 * @param int   $post_id The post ID for which to determine the $robots values, defaults to current post.
 807	 *
 808	 * @return    array
 809	 */
 810	public function robots_for_single_post( $robots, $post_id = 0 ) {
 811		$noindex = $this->get_seo_meta_value( 'meta-robots-noindex', $post_id );
 812		if ( $noindex === '1' ) {
 813			$robots['index'] = 'noindex';
 814		}
 815		elseif ( $noindex === '2' ) {
 816			$robots['index'] = 'index';
 817		}
 818
 819		if ( $this->get_seo_meta_value( 'meta-robots-nofollow', $post_id ) === '1' ) {
 820			$robots['follow'] = 'nofollow';
 821		}
 822
 823		$meta_robots_adv = $this->get_seo_meta_value( 'meta-robots-adv', $post_id );
 824
 825		if ( $meta_robots_adv !== '' && ( $meta_robots_adv !== '-' && $meta_robots_adv !== 'none' ) ) {
 826			$meta_robots_adv = explode( ',', $meta_robots_adv );
 827			foreach ( $meta_robots_adv as $robot ) {
 828				$robots['other'][] = $robot;
 829			}
 830			unset( $robot );
 831		}
 832		unset( $meta_robots_adv );
 833
 834		return $robots;
 835	}
 836
 837	/**
 838	 * This function normally outputs the canonical but is also used in other places to retrieve
 839	 * the canonical URL for the current page.
 840	 *
 841	 * @param bool $echo        Whether or not to output the canonical element.
 842	 * @param bool $un_paged    Whether or not to return the canonical with or without pagination added to the URL.
 843	 * @param bool $no_override Whether or not to return a manually overridden canonical.
 844	 *
 845	 * @return string $canonical
 846	 */
 847	public function canonical( $echo = true, $un_paged = false, $no_override = false ) {
 848		if ( is_null( $this->canonical ) ) {
 849			$this->generate_canonical();
 850		}
 851
 852		$canonical = $this->canonical;
 853
 854		if ( $un_paged ) {
 855			$canonical = $this->canonical_unpaged;
 856		}
 857		elseif ( $no_override ) {
 858			$canonical = $this->canonical_no_override;
 859		}
 860
 861		if ( $echo === false ) {
 862			return $canonical;
 863		}
 864
 865		if ( is_string( $canonical ) && '' !== $canonical ) {
 866			echo '<link rel="canonical" href="' . esc_url( $canonical, null, 'other' ) . '" />' . "\n";
 867		}
 868	}
 869
 870	/**
 871	 * This function normally outputs the canonical but is also used in other places to retrieve
 872	 * the canonical URL for the current page.
 873	 *
 874	 * @return void
 875	 */
 876	private function generate_canonical() {
 877		$canonical          = false;
 878		$canonical_override = false;
 879
 880		// Set decent canonicals for homepage, singulars and taxonomy pages.
 881		if ( is_singular() ) {
 882			$obj       = get_queried_object();
 883			$canonical = get_permalink( $obj->ID );
 884
 885			$this->canonical_unpaged = $canonical;
 886
 887			$canonical_override = $this->get_seo_meta_value( 'canonical' );
 888
 889			// Fix paginated pages canonical, but only if the page is truly paginated.
 890			if ( get_query_var( 'page' ) > 1 ) {
 891				$num_pages = ( substr_count( $obj->post_content, '<!--nextpage-->' ) + 1 );
 892				if ( $num_pages && get_query_var( 'page' ) <= $num_pages ) {
 893					if ( ! $GLOBALS['wp_rewrite']->using_permalinks() ) {
 894						$canonical = add_query_arg( 'page', get_query_var( 'page' ), $canonical );
 895					}
 896					else {
 897						$canonical = user_trailingslashit( trailingslashit( $canonical ) . get_query_var( 'page' ) );
 898					}
 899				}
 900			}
 901		}
 902		else {
 903			if ( is_search() ) {
 904				$search_query = get_search_query();
 905
 906				// Regex catches case when /search/page/N without search term is itself mistaken for search term. R.
 907				if ( ! empty( $search_query ) && ! preg_match( '|^page/\d+$|', $search_query ) ) {
 908					$canonical = get_search_link();
 909				}
 910			}
 911			elseif ( is_front_page() ) {
 912				$canonical = WPSEO_Utils::home_url();
 913			}
 914			elseif ( $this->is_posts_page() ) {
 915
 916				$posts_page_id = get_option( 'page_for_posts' );
 917				$canonical     = $this->get_seo_meta_value( 'canonical', $posts_page_id );
 918
 919				if ( empty( $canonical ) ) {
 920					$canonical = get_permalink( $posts_page_id );
 921				}
 922			}
 923			elseif ( is_tax() || is_tag() || is_category() ) {
 924
 925				$term = get_queried_object();
 926
 927				if ( ! empty( $term ) && ! $this->is_multiple_terms_query() ) {
 928
 929					$canonical_override = WPSEO_Taxonomy_Meta::get_term_meta( $term, $term->taxonomy, 'canonical' );
 930					$term_link          = get_term_link( $term, $term->taxonomy );
 931
 932					if ( ! is_wp_error( $term_link ) ) {
 933						$canonical = $term_link;
 934					}
 935				}
 936			}
 937			elseif ( is_post_type_archive() ) {
 938				$post_type = $this->get_queried_post_type();
 939				$canonical = get_post_type_archive_link( $post_type );
 940			}
 941			elseif ( is_author() ) {
 942				$canonical = get_author_posts_url( get_query_var( 'author' ), get_query_var( 'author_name' ) );
 943			}
 944			elseif ( is_archive() ) {
 945				if ( is_date() ) {
 946					if ( is_day() ) {
 947						$canonical = get_day_link( get_query_var( 'year' ), get_query_var( 'monthnum' ), get_query_var( 'day' ) );
 948					}
 949					elseif ( is_month() ) {
 950						$canonical = get_month_link( get_query_var( 'year' ), get_query_var( 'monthnum' ) );
 951					}
 952					elseif ( is_year() ) {
 953						$canonical = get_year_link( get_query_var( 'year' ) );
 954					}
 955				}
 956			}
 957
 958			$this->canonical_unpaged = $canonical;
 959
 960			if ( $canonical && get_query_var( 'paged' ) > 1 ) {
 961				global $wp_rewrite;
 962				if ( ! $wp_rewrite->using_permalinks() ) {
 963					if ( is_front_page() ) {
 964						$canonical = trailingslashit( $canonical );
 965					}
 966					$canonical = add_query_arg( 'paged', get_query_var( 'paged' ), $canonical );
 967				}
 968				else {
 969					if ( is_front_page() ) {
 970						$canonical = WPSEO_Sitemaps_Router::get_base_url( '' );
 971					}
 972					$canonical = user_trailingslashit( trailingslashit( $canonical ) . trailingslashit( $wp_rewrite->pagination_base ) . get_query_var( 'paged' ) );
 973				}
 974			}
 975		}
 976
 977		$this->canonical_no_override = $canonical;
 978
 979		if ( is_string( $canonical ) && $canonical !== '' ) {
 980			// Force canonical links to be absolute, relative is NOT an option.
 981			if ( WPSEO_Utils::is_url_relative( $canonical ) === true ) {
 982				$canonical = $this->base_url( $canonical );
 983			}
 984		}
 985
 986		if ( is_string( $canonical_override ) && $canonical_override !== '' ) {
 987			$canonical = $canonical_override;
 988		}
 989
 990		/**
 991		 * Filter: 'wpseo_canonical' - Allow filtering of the canonical URL put out by Yoast SEO.
 992		 *
 993		 * @api string $canonical The canonical URL.
 994		 */
 995		$this->canonical = apply_filters( 'wpseo_canonical', $canonical );
 996	}
 997
 998	/**
 999	 * Parse the home URL setting to find the base URL for relative URLs.
1000	 *
1001	 * @param string $path Optional path string.
1002	 *
1003	 * @return string
1004	 */
1005	private function base_url( $path = null ) {
1006		$url = get_option( 'home' );
1007
1008		$parts = wp_parse_url( $url );
1009
1010		$base_url = trailingslashit( $parts['scheme'] . '://' . $parts['host'] );
1011
1012		if ( ! is_null( $path ) ) {
1013			$base_url .= ltrim( $path, '/' );
1014		}
1015
1016		return $base_url;
1017	}
1018
1019	/**
1020	 * Adds 'prev' and 'next' links to archives.
1021	 *
1022	 * @link  http://googlewebmastercentral.blogspot.com/2011/09/pagination-with-relnext-and-relprev.html
1023	 * @since 1.0.3
1024	 */
1025	public function adjacent_rel_links() {
1026		// Don't do this for Genesis, as the way Genesis handles homepage functionality is different and causes issues sometimes.
1027		/**
1028		 * Filter 'wpseo_genesis_force_adjacent_rel_home' - Allows devs to allow echoing rel="next" / rel="prev" by Yoast SEO on Genesis installs.
1029		 *
1030		 * @api bool $unsigned Whether or not to rel=next / rel=prev .
1031		 */
1032		if ( is_home() && function_exists( 'genesis' ) && apply_filters( 'wpseo_genesis_force_adjacent_rel_home', false ) === false ) {
1033			return;
1034		}
1035
1036		/**
1037		 * Filter: 'wpseo_disable_adjacent_rel_links' - Allows disabling of Yoast adjacent links if this is being handled by other code.
1038		 *
1039		 * @api bool $links_generated Indicates if other code has handled adjacent links.
1040		 */
1041		if ( true === apply_filters( 'wpseo_disable_adjacent_rel_links', false ) ) {
1042			return;
1043		}
1044
1045		if ( is_singular() ) {
1046			$this->rel_links_single();
1047			return;
1048		}
1049
1050		$this->rel_links_archive();
1051	}
1052
1053	/**
1054	 * Output the rel next/prev links for a single post / page.
1055	 *
1056	 * @return void
1057	 */
1058	protected function rel_links_single() {
1059		$num_pages = 1;
1060
1061		$queried_object = get_queried_object();
1062		if ( ! empty( $queried_object ) ) {
1063			$num_pages = ( substr_count( $queried_object->post_content, '<!--nextpage-->' ) + 1 );
1064		}
1065
1066		if ( $num_pages === 1 ) {
1067			return;
1068		}
1069
1070		$page = max( 1, (int) get_query_var( 'page' ) );
1071		$url  = get_permalink( get_queried_object_id() );
1072
1073		if ( $page > 1 ) {
1074			$this->adjacent_rel_link( 'prev', $url, ( $page - 1 ), 'page' );
1075		}
1076
1077		if ( $page < $num_pages ) {
1078			$this->adjacent_rel_link( 'next', $url, ( $page + 1 ), 'page' );
1079		}
1080	}
1081
1082	/**
1083	 * Output the rel next/prev links for an archive page.
1084	 */
1085	protected function rel_links_archive() {
1086		$url = $this->canonical( false, true, true );
1087
1088		if ( ! is_string( $url ) || $url === '' ) {
1089			return;
1090		}
1091
1092		$paged = max( 1, (int) get_query_var( 'paged' ) );
1093
1094		if ( $paged === 2 ) {
1095			$this->adjacent_rel_link( 'prev', $url, ( $paged - 1 ) );
1096		}
1097
1098		// Make sure to use index.php when needed, done after paged == 2 check so the prev links to homepage will not have index.php erroneously.
1099		if ( is_front_page() ) {
1100			$url = WPSEO_Sitemaps_Router::get_base_url( '' );
1101		}
1102
1103		if ( $paged > 2 ) {
1104			$this->adjacent_rel_link( 'prev', $url, ( $paged - 1 ) );
1105		}
1106
1107		if ( $paged < $GLOBALS['wp_query']->max_num_pages ) {
1108			$this->adjacent_rel_link( 'next', $url, ( $paged + 1 ) );
1109		}
1110	}
1111
1112	/**
1113	 * Get adjacent pages link for archives.
1114	 *
1115	 * @since 1.0.2
1116	 * @since 7.1    Added $query_arg parameter for single post/page pagination.
1117	 *
1118	 * @param string $rel       Link relationship, prev or next.
1119	 * @param string $url       The un-paginated URL of the current archive.
1120	 * @param string $page      The page number to add on to $url for the $link tag.
1121	 * @param string $query_arg Optional. The argument to use to set for the page to load.
1122	 *
1123	 * @return void
1124	 */
1125	private function adjacent_rel_link( $rel, $url, $page, $query_arg = 'paged' ) {
1126		global $wp_rewrite;
1127		if ( ! $wp_rewrite->using_permalinks() ) {
1128			if ( $page > 1 ) {
1129				$url = add_query_arg( $query_arg, $page, $url );
1130			}
1131		}
1132		else {
1133			if ( $page > 1 ) {
1134				$url = user_trailingslashit( trailingslashit( $url ) . $this->get_pagination_base() . $page );
1135			}
1136		}
1137
1138		/**
1139		 * Filter: 'wpseo_' . $rel . '_rel_link' - Allow changing link rel output by Yoast SEO.
1140		 *
1141		 * @api string $unsigned The full `<link` element.
1142		 */
1143		$link = apply_filters( 'wpseo_' . $rel . '_rel_link', '<link rel="' . esc_attr( $rel ) . '" href="' . esc_url( $url ) . "\" />\n" );
1144
1145		if ( is_string( $link ) && $link !== '' ) {
1146			echo $link;
1147		}
1148	}
1149
1150	/**
1151	 * Return the base for pagination.
1152	 *
1153	 * @return string The pagination base.
1154	 */
1155	private function get_pagination_base() {
1156		// If the current page is the frontpage, pagination should use /base/.
1157		$base = '';
1158		if ( ! is_singular() || $this->is_home_static_page() ) {
1159			$base = trailingslashit( $GLOBALS['wp_rewrite']->pagination_base );
1160		}
1161		return $base;
1162	}
1163
1164	/**
1165	 * Output the rel=publisher code on every page of the site.
1166	 *
1167	 * @return boolean Boolean indicating whether the publisher link was printed.
1168	 */
1169	public function publisher() {
1170		$publisher = WPSEO_Options::get( 'plus-publisher', '' );
1171		if ( $publisher !== '' ) {
1172			echo '<link rel="publisher" href="', esc_url( $publisher ), '"/>', "\n";
1173
1174			return true;
1175		}
1176
1177		return false;
1178	}
1179
1180	/**
1181	 * Outputs the meta description element or returns the description text.
1182	 *
1183	 * @param bool $echo Echo or return output flag.
1184	 *
1185	 * @return string
1186	 */
1187	public function metadesc( $echo = true ) {
1188		if ( is_null( $this->metadesc ) ) {
1189			$this->generate_metadesc();
1190		}
1191
1192		if ( $echo === false ) {
1193			return $this->metadesc;
1194		}
1195
1196		if ( is_string( $this->metadesc ) && $this->metadesc !== '' ) {
1197			echo '<meta name="description" content="', esc_attr( wp_strip_all_tags( stripslashes( $this->metadesc ) ) ), '"/>', "\n";
1198			return '';
1199		}
1200
1201		if ( current_user_can( 'wpseo_manage_options' ) && is_singular() ) {
1202			echo '<!-- ';
1203			printf(
1204				/* Translators: %1$s resolves to the SEO menu item, %2$s resolves to the Search Appearance submenu item. */
1205				esc_html__( 'Admin only notice: this page does not show a meta description because it does not have one, either write it for this page specifically or go into the [%1$s - %2$s] menu and set up a template.', 'wordpress-seo' ),
1206				__( 'SEO', 'wordpress-seo' ),
1207				__( 'Search Appearance', 'wordpress-seo' )
1208			);
1209			echo ' -->' . "\n";
1210		}
1211	}
1212
1213	/**
1214	 * Generates the meta description text.
1215	 */
1216	private function generate_metadesc() {
1217		global $post, $wp_query;
1218
1219		$metadesc          = '';
1220		$metadesc_override = false;
1221		$post_type         = '';
1222		$template          = '';
1223
1224		if ( is_object( $post ) && ( isset( $post->post_type ) && $post->post_type !== '' ) ) {
1225			$post_type = $post->post_type;
1226		}
1227
1228		if ( $this->woocommerce_shop_page->is_shop_page() ) {
1229			$post      = get_post( $this->woocommerce_shop_page->get_shop_page_id() );
1230			$post_type = $this->get_queried_post_type();
1231
1232			if ( ( $metadesc === '' && $post_type !== '' ) && WPSEO_Options::get( 'metadesc-ptarchive-' . $post_type, '' ) !== '' ) {
1233				$template = WPSEO_Options::get( 'metadesc-ptarchive-' . $post_type );
1234				$term     = $post;
1235			}
1236			$metadesc_override = $this->get_seo_meta_value( 'metadesc', $post->ID );
1237		}
1238		elseif ( $this->frontend_page_type->is_simple_page() ) {
1239			$post      = get_post( $this->frontend_page_type->get_simple_page_id() );
1240			$post_type = $post->post_type;
1241
1242			if ( ( $metadesc === '' && $post_type !== '' ) && WPSEO_Options::get( 'metadesc-' . $post_type, '' ) !== '' ) {
1243				$template = WPSEO_Options::get( 'metadesc-' . $post_type );
1244				$term     = $post;
1245			}
1246			$metadesc_override = $this->get_seo_meta_value( 'metadesc', $post->ID );
1247		}
1248		else {
1249			if ( is_search() ) {
1250				$metadesc = '';
1251			}
1252			elseif ( $this->is_home_posts_page() ) {
1253				$template = WPSEO_Options::get( 'metadesc-home-wpseo' );
1254				$term     = array();
1255
1256				if ( empty( $template ) ) {
1257					$template = get_bloginfo( 'description' );
1258				}
1259			}
1260			elseif ( $this->is_home_static_page() ) {
1261				$metadesc = $this->get_seo_meta_value( 'metadesc' );
1262				if ( ( $metadesc === '' && $post_type !== '' ) && WPSEO_Options::get( 'metadesc-' . $post_type, '' ) !== '' ) {
1263					$template = WPSEO_Options::get( 'metadesc-' . $post_type );
1264				}
1265			}
1266			elseif ( is_category() || is_tag() || is_tax() ) {
1267				$term              = $wp_query->get_queried_object();
1268				$metadesc_override = WPSEO_Taxonomy_Meta::get_term_meta( $term, $term->taxonomy, 'desc' );
1269				if ( is_object( $term ) && isset( $term->taxonomy ) && WPSEO_Options::get( 'metadesc-tax-' . $term->taxonomy, '' ) !== '' ) {
1270					$template = WPSEO_Options::get( 'metadesc-tax-' . $term->taxonomy );
1271				}
1272			}
1273			elseif ( is_author() ) {
1274				$author_id = get_query_var( 'author' );
1275				$metadesc  = get_the_author_meta( 'wpseo_metadesc', $author_id );
1276				if ( ( ! is_string( $metadesc ) || $metadesc === '' ) && WPSEO_Options::get( 'metadesc-author-wpseo', '' ) !== '' ) {
1277					$template = WPSEO_Options::get( 'metadesc-author-wpseo' );
1278				}
1279			}
1280			elseif ( is_post_type_archive() ) {
1281				$post_type = $this->get_queried_post_type();
1282				if ( WPSEO_Options::get( 'metadesc-ptarchive-' . $post_type, '' ) !== '' ) {
1283					$template = WPSEO_Options::get( 'metadesc-ptarchive-' . $post_type );
1284				}
1285			}
1286			elseif ( is_archive() ) {
1287				$template = WPSEO_Options::get( 'metadesc-archive-wpseo' );
1288			}
1289
1290			// If we're on a paginated page, and the template doesn't change for paginated pages, bail.
1291			if ( ( ! is_string( $metadesc ) || $metadesc === '' ) && get_query_var( 'paged' ) && get_query_var( 'paged' ) > 1 && $template !== '' ) {
1292				if ( strpos( $template, '%%page' ) === false ) {
1293					$metadesc = '';
1294				}
1295			}
1296		}
1297
1298		$post_data = $post;
1299
1300		if ( is_string( $metadesc_override ) && '' !== $metadesc_override ) {
1301			$metadesc = $metadesc_override;
1302			if ( isset( $term ) ) {
1303				$post_data = $term;
1304			}
1305		}
1306		elseif ( ( ! is_string( $metadesc ) || '' === $metadesc ) && '' !== $template ) {
1307			if ( ! isset( $term ) ) {
1308				$term = $wp_query->get_queried_object();
1309			}
1310
1311			$metadesc  = $template;
1312			$post_data = $term;
1313		}
1314
1315		$metadesc = $this->replace_vars( $metadesc, $post_data );
1316
1317		/**
1318		 * Filter: 'wpseo_metadesc' - Allow changing the Yoast SEO meta description sentence.
1319		 *
1320		 * @api string $metadesc The description sentence.
1321		 */
1322		$this->metadesc = apply_filters( 'wpseo_metadesc', trim( $metadesc ) );
1323	}
1324
1325	/**
1326	 * Based on the redirect meta value, this function determines whether it should redirect the current post / page.
1327	 *
1328	 * @return boolean
1329	 */
1330	public function page_redirect() {
1331		if ( is_singular() ) {
1332			global $post;
1333			if ( ! isset( $post ) || ! is_object( $post ) ) {
1334				return false;
1335			}
1336
1337			$redir = $this->get_seo_meta_value( 'redirect', $post->ID );
1338			if ( $redir !== '' ) {
1339				wp_redirect( $redir, 301 );
1340				exit;
1341			}
1342		}
1343
1344		return false;
1345	}
1346
1347	/**
1348	 * Outputs noindex values for the current page.
1349	 */
1350	public function noindex_page() {
1351		remove_action( 'wpseo_head', array( $this, 'canonical' ), 20 );
1352		echo '<meta name="robots" content="noindex" />', "\n";
1353	}
1354
1355	/**
1356	 * Send a Robots HTTP header preventing URL from being indexed in the search results while allowing search engines
1357	 * to follow the links in the object at the URL.
1358	 *
1359	 * @since 1.1.7
1360	 * @return boolean Boolean indicating whether the noindex header was sent.
1361	 */
1362	public function noindex_feed() {
1363
1364		if ( ( is_feed() || is_robots() ) && headers_sent() === false ) {
1365			header( 'X-Robots-Tag: noindex, follow', true );
1366
1367			return true;
1368		}
1369
1370		return false;
1371	}
1372
1373	/**
1374	 * Adds rel="nofollow" to a link, only used for login / registration links.
1375	 *
1376	 * @param string $input The link element as a string.
1377	 *
1378	 * @return string
1379	 */
1380	public function nofollow_link( $input ) {
1381		return str_replace( '<a ', '<a rel="nofollow" ', $input );
1382	}
1383
1384	/**
1385	 * When certain archives are disabled, this redirects those to the homepage.
1386	 *
1387	 * @return boolean False when no redirect was triggered.
1388	 */
1389	public function archive_redirect() {
1390		global $wp_query;
1391
1392		if (
1393			( WPSEO_Options::get( 'disable-date', false ) && $wp_query->is_date ) ||
1394			( WPSEO_Options::get( 'disable-author', false ) && $wp_query->is_author ) ||
1395			( WPSEO_Options::get( 'disable-post_format', false ) && $wp_query->is_tax( 'post_format' ) )
1396		) {
1397			$this->redirect( get_bloginfo( 'url' ), 301 );
1398
1399			return true;
1400		}
1401
1402		return false;
1403	}
1404
1405	/**
1406	 * If the option to disable attachment URLs is checked, this performs the redirect to the attachment.
1407	 *
1408	 * @return bool Returns succes status.
1409	 */
1410	public function attachment_redirect() {
1411		if ( WPSEO_Options::get( 'disable-attachment', false ) === false ) {
1412			return false;
1413		}
1414		if ( ! is_attachment() ) {
1415			return false;
1416		}
1417
1418		$url = wp_get_attachment_url( get_queried_object_id() );
1419
1420		if ( ! empty( $url ) ) {
1421			$this->redirect( $url, 301 );
1422
1423			return true;
1424		}
1425
1426		return false;
1427	}
1428
1429	/**
1430	 * Replaces the possible RSS variables with their actual values.
1431	 *
1432	 * @param string $content The RSS content that should have the variables replaced.
1433	 *
1434	 * @return string
1435	 */
1436	public function rss_replace_vars( $content ) {
1437		global $post;
1438
1439		/**
1440		 * 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.
1441		 *
1442		 * @api   bool $unsigned Whether or not to follow the links in RSS feed, defaults to true.
1443		 *
1444		 * @since 1.4.20
1445		 */
1446		$no_follow      = apply_filters( 'nofollow_rss_links', true );
1447		$no_follow_attr = '';
1448		if ( $no_follow === true ) {
1449			$no_follow_attr = 'rel="nofollow" ';
1450		}
1451
1452		$author_link = '';
1453		if ( is_object( $post ) ) {
1454			$author_link = '<a ' . $no_follow_attr . 'href="' . esc_url( get_author_posts_url( $post->post_author ) ) . '">' . esc_html( get_the_author() ) . '</a>';
1455		}
1456
1457		$post_link      = '<a ' . $no_follow_attr . 'href="' . esc_url( get_permalink() ) . '">' . esc_html( get_the_title() ) . '</a>';
1458		$blog_link      = '<a ' . $no_follow_attr . 'href="' . esc_url( get_bloginfo( 'url' ) ) . '">' . esc_html( get_bloginfo( 'name' ) ) . '</a>';
1459		$blog_desc_link = '<a ' . $no_follow_attr . 'href="' . esc_url( get_bloginfo( 'url' ) ) . '">' . esc_html( get_bloginfo( 'name' ) ) . ' - ' . esc_html( get_bloginfo( 'description' ) ) . '</a>';
1460
1461		$content = stripslashes( trim( $content ) );
1462		$content = str_replace( '%%AUTHORLINK%%', $author_link, $content );
1463		$content = str_replace( '%%POSTLINK%%', $post_link, $content );
1464		$content = str_replace( '%%BLOGLINK%%', $blog_link, $content );
1465		$content = str_replace( '%%BLOGDESCLINK%%', $blog_desc_link, $content );
1466
1467		return $content;
1468	}
1469
1470	/**
1471	 * Adds the RSS footer (or header) to the full RSS feed item.
1472	 *
1473	 * @param string $content Feed item content.
1474	 *
1475	 * @return string
1476	 */
1477	public function embed_rssfooter( $content ) {
1478		return $this->embed_rss( $content, 'full' );
1479	}
1480
1481	/**
1482	 * Adds the RSS footer (or header) to the excerpt RSS feed item.
1483	 *
1484	 * @param string $content Feed item excerpt.
1485	 *
1486	 * @return string
1487	 */
1488	public function embed_rssfooter_excerpt( $content ) {
1489		return $this->embed_rss( $content, 'excerpt' );
1490	}
1491
1492	/**
1493	 * Adds the RSS footer and/or header to an RSS feed item.
1494	 *
1495	 * @since 1.4.14
1496	 *
1497	 * @param string $content Feed item content.
1498	 * @param string $context Feed item context, either 'excerpt' or 'full'.
1499	 *
1500	 * @return string
1501	 */
1502	public function embed_rss( $content, $context = 'full' ) {
1503
1504		/**
1505		 * Filter: 'wpseo_include_rss_footer' - Allow the RSS footer to be dynamically shown/hidden.
1506		 *
1507		 * @api boolean $show_embed Indicates if the RSS footer should be shown or not.
1508		 *
1509		 * @param string $context The context of the RSS content - 'full' or 'excerpt'.
1510		 */
1511		if ( ! apply_filters( 'wpseo_include_rss_footer', true, $context ) ) {
1512			return $content;
1513		}
1514
1515		if ( is_feed() ) {
1516			$before = '';
1517			$after  = '';
1518
1519			if ( WPSEO_Options::get( 'rssbefore', '' ) !== '' ) {
1520				$before = wpautop( $this->rss_replace_vars( WPSEO_Options::get( 'rssbefore' ) ) );
1521			}
1522			if ( WPSEO_Options::get( 'rssafter', '' ) !== '' ) {
1523				$after = wpautop( $this->rss_replace_vars( WPSEO_Options::get( 'rssafter' ) ) );
1524			}
1525			if ( $before !== '' || $after !== '' ) {
1526				if ( ( isset( $context ) && $context === 'excerpt' ) && trim( $content ) !== '' ) {
1527					$content = wpautop( $content );
1528				}
1529				$content = $before . $content . $after;
1530			}
1531		}
1532
1533		return $content;
1534	}
1535
1536	/**
1537	 * Used in the force rewrite functionality this retrieves the output, replaces the title with the proper SEO
1538	 * title and then flushes the output.
1539	 */
1540	public function flush_cache() {
1541
1542		global $wp_query;
1543
1544		if ( $this->ob_started !== true ) {
1545			return false;
1546		}
1547
1548		$content = ob_get_clean();
1549
1550		$old_wp_query = $wp_query;
1551
1552		wp_reset_query();
1553
1554		// Only replace the debug marker when it is hooked.
1555		if ( $this->show_debug_marker() ) {
1556			$title      = $this->title( '' );
1557			$debug_mark = $this->get_debug_mark();
1558
1559			// Find all titles, strip them out and add the new one in within the debug marker, so it's easily identified whether a site uses force rewrite.
1560			$content = preg_replace( '/<title.*?\/title>/i', '', $content );
1561			$content = str_replace( $debug_mark, $debug_mark . "\n" . '<title>' . esc_html( $title ) . '</title>', $content );
1562		}
1563
1564		$GLOBALS['wp_query'] = $old_wp_query;
1565
1566		echo $content;
1567
1568		return true;
1569	}
1570
1571	/**
1572	 * Starts the output buffer so it can later be fixed by flush_cache().
1573	 */
1574	public function force_rewrite_output_buffer() {
1575		$this->ob_started = true;
1576		ob_start();
1577	}
1578
1579	/**
1580	 * Function used in testing whether the title should be force rewritten or not.
1581	 *
1582	 * @param string $title Title string.
1583	 *
1584	 * @return string
1585	 */
1586	public function title_test_helper( $title ) {
1587		WPSEO_Options::set( 'title_test', ( WPSEO_Options::get( 'title_test' ) + 1 ) );
1588
1589		// Prevent this setting from being on forever when something breaks, as it breaks caching.
1590		if ( WPSEO_Options::get( 'title_test' ) > 5 ) {
1591			WPSEO_Options::set( 'title_test', 0 );
1592
1593			remove_filter( 'wpseo_title', array( $this, 'title_test_helper' ) );
1594
1595			return $title;
1596		}
1597
1598		if ( ! defined( 'DONOTCACHEPAGE' ) ) {
1599			define( 'DONOTCACHEPAGE', true );
1600		}
1601		if ( ! defined( 'DONOTCACHCEOBJECT' ) ) {
1602			define( 'DONOTCACHCEOBJECT', true );
1603		}
1604		if ( ! defined( 'DONOTMINIFY' ) ) {
1605			define( 'DONOTMINIFY', true );
1606		}
1607
1608		if ( $_SERVER['HTTP_USER_AGENT'] === "WordPress/{$GLOBALS['wp_version']}; " . get_bloginfo( 'url' ) . ' - Yoast' ) {
1609			return 'This is a Yoast Test Title';
1610		}
1611
1612		return $title;
1613	}
1614
1615	/**
1616	 * Get the product name in the head section.
1617	 *
1618	 * @return string
1619	 */
1620	private function head_product_name() {
1621		if ( $this->is_premium() ) {
1622			return 'Yoast SEO Premium plugin';
1623		}
1624
1625		return 'Yoast SEO plugin';
1626	}
1627
1628	/**
1629	 * Check if this plugin is the premium version of WPSEO.
1630	 *
1631	 * @return bool
1632	 */
1633	protected function is_premium() {
1634		return WPSEO_Utils::is_yoast_seo_premium();
1635	}
1636
1637	/**
1638	 * Check if term archive query is for multiple terms (/term-1,term2/ or /term-1+term-2/).
1639	 *
1640	 * @return bool
1641	 */
1642	protected function is_multiple_terms_query() {
1643
1644		global $wp_query;
1645
1646		if ( ! is_tax() && ! is_tag() && ! is_category() ) {
1647			return false;
1648		}
1649
1650		$term          = get_queried_object();
1651		$queried_terms = $wp_query->tax_query->queried_terms;
1652
1653		if ( empty( $queried_terms[ $term->taxonomy ]['terms'] ) ) {
1654			return false;
1655		}
1656
1657		return count( $queried_terms[ $term->taxonomy ]['terms'] ) > 1;
1658	}
1659
1660	/**
1661	 * Wraps wp_safe_redirect to allow testing for redirects.
1662	 *
1663	 * @param string $location The path to redirect to.
1664	 * @param int    $status   Status code to use.
1665	 */
1666	public function redirect( $location, $status = 302 ) {
1667		wp_safe_redirect( $location, $status );
1668		exit;
1669	}
1670
1671	/**
1672	 * Checks if the debug mark action has been added.
1673	 *
1674	 * @return bool True when the action exists.
1675	 */
1676	protected function show_debug_marker() {
1677		return has_action( 'wpseo_head', array( $this, 'debug_mark' ) ) !== false;
1678	}
1679
1680	/**
1681	 * Shows the cl…

Large files files are truncated, but you can click here to view the full file