class-frontend.php 56 KB

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