class-frontend.php 56 KB

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