PageRenderTime 58ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

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