$this->robots_for_single_post( $robots, WPSEO_Frontend_Page_Type::get_simple_page_id() ); } else { if ( is_search() || is_404() ) { $robots['index'] = 'noindex'; } elseif ( is_tax() || is_tag() || is_category() ) { $term = $wp_query->get_queried_object(); if ( is_object( $term ) && ( WPSEO_Options::get( 'noindex-tax-' . $term->taxonomy, false ) ) ) { $robots['index'] = 'noindex'; } // Three possible values, index, noindex and default, do nothing for default. $term_meta = WPSEO_Taxonomy_Meta::get_term_meta( $term, $term->taxonomy, 'noindex' ); if ( is_string( $term_meta ) && 'default' !== $term_meta ) { $robots['index'] = $term_meta; } if ( $this->is_multiple_terms_query() ) { $robots['index'] = 'noindex'; } } elseif ( is_author() ) { if ( WPSEO_Options::get( 'noindex-author-wpseo', false ) ) { $robots['index'] = 'noindex'; } $curauth = $wp_query->get_queried_object(); if ( WPSEO_Options::get( 'noindex-author-noposts-wpseo', false ) && count_user_posts( $curauth->ID, 'any' ) === 0 ) { $robots['index'] = 'noindex'; } if ( get_user_meta( $curauth->ID, 'wpseo_noindex_author', true ) === 'on' ) { $robots['index'] = 'noindex'; } } elseif ( is_date() && WPSEO_Options::get( 'noindex-archive-wpseo', false ) ) { $robots['index'] = 'noindex'; } elseif ( is_home() ) { $page_for_posts = get_option( 'page_for_posts' ); if ( $page_for_posts ) { $robots = $this->robots_for_single_post( $robots, $page_for_posts ); } unset( $page_for_posts ); } elseif ( is_post_type_archive() ) { $post_type = $this->get_queried_post_type(); if ( WPSEO_Options::get( 'noindex-ptarchive-' . $post_type, false ) ) { $robots['index'] = 'noindex'; } } unset( $robot ); } // Force override to respect the WP settings. if ( '0' === (string) get_option( 'blog_public' ) || isset( $_GET['replytocom'] ) ) { $robots['index'] = 'noindex'; } $robotsstr = $robots['index'] . ',' . $robots['follow']; if ( $robots['other'] !== array() ) { $robots['other'] = array_unique( $robots['other'] ); // @todo Most likely no longer needed, needs testing. $robotsstr .= ',' . implode( ',', $robots['other'] ); } $robotsstr = preg_replace( '`^index,follow,?`', '', $robotsstr ); $robotsstr = str_replace( array( 'noodp,', 'noodp' ), '', $robotsstr ); if ( strpos( $robotsstr, 'noindex' ) === false && strpos( $robotsstr, 'nosnippet' ) === false ) { if ( $robotsstr !== '' ) { $robotsstr .= ', '; } $robotsstr .= 'max-snippet:-1, max-image-preview:large, max-video-preview:-1'; } /** * Filter: 'wpseo_robots' - Allows filtering of the meta robots output of Yoast SEO. * * @api string $robotsstr The meta robots directives to be echoed. */ $robotsstr = apply_filters( 'wpseo_robots', $robotsstr ); return $robotsstr; } /** * Outputs the meta robots value. * * @return string */ public function robots() { $robotsstr = $this->get_robots(); if ( is_string( $robotsstr ) && $robotsstr !== '' ) { echo '', "\n"; } // If a page has a noindex, it should _not_ have a canonical, as these are opposing indexing directives. if ( strpos( $robotsstr, 'noindex' ) !== false ) { remove_action( 'wpseo_head', array( $this, 'canonical' ), 20 ); } return $robotsstr; } /** * Determine $robots values for a single post. * * @param array $robots Robots data array. * @param int $post_id The post ID for which to determine the $robots values, defaults to current post. * * @return array */ public function robots_for_single_post( $robots, $post_id = 0 ) { $noindex = $this->get_seo_meta_value( 'meta-robots-noindex', $post_id ); if ( $noindex === '1' ) { $robots['index'] = 'noindex'; } elseif ( $noindex === '2' ) { $robots['index'] = 'index'; } if ( $this->get_seo_meta_value( 'meta-robots-nofollow', $post_id ) === '1' ) { $robots['follow'] = 'nofollow'; } $meta_robots_adv = $this->get_seo_meta_value( 'meta-robots-adv', $post_id ); if ( $meta_robots_adv !== '' && ( $meta_robots_adv !== '-' && $meta_robots_adv !== 'none' ) ) { $meta_robots_adv = explode( ',', $meta_robots_adv ); foreach ( $meta_robots_adv as $robot ) { $robots['other'][] = $robot; } unset( $robot ); } unset( $meta_robots_adv ); return $robots; } /** * This function normally outputs the canonical but is also used in other places to retrieve * the canonical URL for the current page. * * @param bool $echo Whether or not to output the canonical element. * @param bool $un_paged Whether or not to return the canonical with or without pagination added to the URL. * @param bool $no_override Whether or not to return a manually overridden canonical. * * @return string $canonical */ public function canonical( $echo = true, $un_paged = false, $no_override = false ) { if ( is_null( $this->canonical ) ) { $this->generate_canonical(); } $canonical = $this->canonical; if ( $un_paged ) { $canonical = $this->canonical_unpaged; } elseif ( $no_override ) { $canonical = $this->canonical_no_override; } if ( $echo === false ) { return $canonical; } if ( is_string( $canonical ) && '' !== $canonical ) { echo '' . "\n"; } } /** * This function normally outputs the canonical but is also used in other places to retrieve * the canonical URL for the current page. * * @return void */ private function generate_canonical() { $canonical = false; $canonical_override = false; // Set decent canonicals for homepage, singulars and taxonomy pages. if ( is_singular() ) { $obj = get_queried_object(); $canonical = get_permalink( $obj->ID ); $this->canonical_unpaged = $canonical; $canonical_override = $this->get_seo_meta_value( 'canonical' ); // Fix paginated pages canonical, but only if the page is truly paginated. if ( get_query_var( 'page' ) > 1 ) { $num_pages = ( substr_count( $obj->post_content, '' ) + 1 ); if ( $num_pages && get_query_var( 'page' ) <= $num_pages ) { if ( ! $GLOBALS['wp_rewrite']->using_permalinks() ) { $canonical = add_query_arg( 'page', get_query_var( 'page' ), $canonical ); } else { $canonical = user_trailingslashit( trailingslashit( $canonical ) . get_query_var( 'page' ) ); } } } } else { if ( is_search() ) { $search_query = get_search_query(); // Regex catches case when /search/page/N without search term is itself mistaken for search term. R. if ( ! empty( $search_query ) && ! preg_match( '|^page/\d+$|', $search_query ) ) { $canonical = get_search_link(); } } elseif ( is_front_page() ) { $canonical = WPSEO_Utils::home_url(); } elseif ( WPSEO_Frontend_Page_Type::is_posts_page() ) { $posts_page_id = get_option( 'page_for_posts' ); $canonical = $this->get_seo_meta_value( 'canonical', $posts_page_id ); if ( empty( $canonical ) ) { $canonical = get_permalink( $posts_page_id ); } } elseif ( is_tax() || is_tag() || is_category() ) { $term = get_queried_object(); if ( ! empty( $term ) && ! $this->is_multiple_terms_query() ) { $canonical_override = WPSEO_Taxonomy_Meta::get_term_meta( $term, $term->taxonomy, 'canonical' ); $term_link = get_term_link( $term, $term->taxonomy ); if ( ! is_wp_error( $term_link ) ) { $canonical = $term_link; } } } elseif ( is_post_type_archive() ) { $post_type = $this->get_queried_post_type(); $canonical = get_post_type_archive_link( $post_type ); } elseif ( is_author() ) { $canonical = get_author_posts_url( get_query_var( 'author' ), get_query_var( 'author_name' ) ); } elseif ( is_archive() ) { if ( is_date() ) { if ( is_day() ) { $canonical = get_day_link( get_query_var( 'year' ), get_query_var( 'monthnum' ), get_query_var( 'day' ) ); } elseif ( is_month() ) { $canonical = get_month_link( get_query_var( 'year' ), get_query_var( 'monthnum' ) ); } elseif ( is_year() ) { $canonical = get_year_link( get_query_var( 'year' ) ); } } } $this->canonical_unpaged = $canonical; if ( $canonical && get_query_var( 'paged' ) > 1 ) { global $wp_rewrite; if ( ! $wp_rewrite->using_permalinks() ) { if ( is_front_page() ) { $canonical = trailingslashit( $canonical ); } $canonical = add_query_arg( 'paged', get_query_var( 'paged' ), $canonical ); } else { if ( is_front_page() ) { $canonical = WPSEO_Sitemaps_Router::get_base_url( '' ); } $canonical = user_trailingslashit( trailingslashit( $canonical ) . trailingslashit( $wp_rewrite->pagination_base ) . get_query_var( 'paged' ) ); } } } $this->canonical_no_override = $canonical; if ( is_string( $canonical ) && $canonical !== '' ) { // Force canonical links to be absolute, relative is NOT an option. if ( WPSEO_Utils::is_url_relative( $canonical ) === true ) { $canonical = $this->base_url( $canonical ); } } if ( is_string( $canonical_override ) && $canonical_override !== '' ) { $canonical = $canonical_override; } /** * Filter: 'wpseo_canonical' - Allow filtering of the canonical URL put out by Yoast SEO. * * @api string $canonical The canonical URL. */ $this->canonical = apply_filters( 'wpseo_canonical', $canonical ); } /** * Parse the home URL setting to find the base URL for relative URLs. * * @param string $path Optional path string. * * @return string */ private function base_url( $path = null ) { $url = get_option( 'home' ); $parts = wp_parse_url( $url ); $base_url = trailingslashit( $parts['scheme'] . '://' . $parts['host'] ); if ( ! is_null( $path ) ) { $base_url .= ltrim( $path, '/' ); } return $base_url; } /** * Adds 'prev' and 'next' links to archives. * * @link http://googlewebmastercentral.blogspot.com/2011/09/pagination-with-relnext-and-relprev.html * @since 1.0.3 */ public function adjacent_rel_links() { /** * Filter: 'wpseo_disable_adjacent_rel_links' - Allows disabling of Yoast adjacent links if this is being handled by other code. * * @api bool $links_generated Indicates if other code has handled adjacent links. */ if ( true === apply_filters( 'wpseo_disable_adjacent_rel_links', false ) ) { return; } if ( is_singular() ) { $this->rel_links_single(); return; } $this->rel_links_archive(); } /** * Output the rel next/prev links for a single post / page. * * @return void */ protected function rel_links_single() { $num_pages = 1; $queried_object = get_queried_object(); if ( ! empty( $queried_object ) ) { $num_pages = ( substr_count( $queried_object->post_content, '' ) + 1 ); } if ( $num_pages === 1 ) { return; } $page = max( 1, (int) get_query_var( 'page' ) ); $url = get_permalink( get_queried_object_id() ); if ( $page > 1 ) { $this->adjacent_rel_link( 'prev', $url, ( $page - 1 ), 'page' ); } if ( $page < $num_pages ) { $this->adjacent_rel_link( 'next', $url, ( $page + 1 ), 'page' ); } } /** * Output the rel next/prev links for an archive page. */ protected function rel_links_archive() { $url = $this->canonical( false, true, true ); if ( ! is_string( $url ) || $url === '' ) { return; } $paged = max( 1, (int) get_query_var( 'paged' ) ); if ( $paged === 2 ) { $this->adjacent_rel_link( 'prev', $url, ( $paged - 1 ) ); } // 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. if ( is_front_page() ) { $url = WPSEO_Sitemaps_Router::get_base_url( '' ); } if ( $paged > 2 ) { $this->adjacent_rel_link( 'prev', $url, ( $paged - 1 ) ); } if ( $paged < $GLOBALS['wp_query']->max_num_pages ) { $this->adjacent_rel_link( 'next', $url, ( $paged + 1 ) ); } } /** * Get adjacent pages link for archives. * * @since 1.0.2 * @since 7.1 Added $query_arg parameter for single post/page pagination. * * @param string $rel Link relationship, prev or next. * @param string $url The un-paginated URL of the current archive. * @param string $page The page number to add on to $url for the $link tag. * @param string $query_arg Optional. The argument to use to set for the page to load. * * @return void */ private function adjacent_rel_link( $rel, $url, $page, $query_arg = 'paged' ) { global $wp_rewrite; if ( ! $wp_rewrite->using_permalinks() ) { if ( $page > 1 ) { $url = add_query_arg( $query_arg, $page, $url ); } } else { if ( $page > 1 ) { $url = user_trailingslashit( trailingslashit( $url ) . $this->get_pagination_base() . $page ); } } /** * Filter: 'wpseo_adjacent_rel_url' - Allow changing the URL for rel output by Yoast SEO. * * @api string $url The URL that's going to be output for $rel. * * @param string $rel Link relationship, prev or next. */ $url = apply_filters( 'wpseo_adjacent_rel_url', $url, $rel ); /** * Filter: 'wpseo_' . $rel . '_rel_link' - Allow changing link rel output by Yoast SEO. * * @api string $unsigned The full `\n" ); if ( is_string( $link ) && $link !== '' ) { echo $link; } } /** * Return the base for pagination. * * @return string The pagination base. */ private function get_pagination_base() { // If the current page is the frontpage, pagination should use /base/. $base = ''; if ( ! is_singular() || WPSEO_Frontend_Page_Type::is_home_static_page() ) { $base = trailingslashit( $GLOBALS['wp_rewrite']->pagination_base ); } return $base; } /** * Outputs the meta description element or returns the description text. * * @param bool $echo Echo or return output flag. * * @return string */ public function metadesc( $echo = true ) { if ( is_null( $this->metadesc ) ) { $this->generate_metadesc(); } if ( $echo === false ) { return $this->metadesc; } if ( is_string( $this->metadesc ) && $this->metadesc !== '' ) { echo '', "\n"; return ''; } if ( current_user_can( 'wpseo_manage_options' ) && is_singular() ) { echo '' . "\n"; } } /** * Generates the meta description text. */ private function generate_metadesc() { global $post, $wp_query; $metadesc = ''; $metadesc_override = false; $post_type = ''; $template = ''; if ( is_object( $post ) && ( isset( $post->post_type ) && $post->post_type !== '' ) ) { $post_type = $post->post_type; } if ( $this->woocommerce_shop_page->is_shop_page() ) { $post = get_post( $this->woocommerce_shop_page->get_shop_page_id() ); $post_type = $this->get_queried_post_type(); if ( ( $metadesc === '' && $post_type !== '' ) && WPSEO_Options::get( 'metadesc-ptarchive-' . $post_type, '' ) !== '' ) { $template = WPSEO_Options::get( 'metadesc-ptarchive-' . $post_type ); $term = $post; } $metadesc_override = $this->get_seo_meta_value( 'metadesc', $post->ID ); } elseif ( WPSEO_Frontend_Page_Type::is_simple_page() ) { $post = get_post( WPSEO_Frontend_Page_Type::get_simple_page_id() ); $post_type = isset( $post->post_type ) ? $post->post_type : ''; if ( ( $metadesc === '' && $post_type !== '' ) && WPSEO_Options::get( 'metadesc-' . $post_type, '' ) !== '' ) { $template = WPSEO_Options::get( 'metadesc-' . $post_type ); $term = $post; } if ( is_object( $post ) ) { $metadesc_override = $this->get_seo_meta_value( 'metadesc', $post->ID ); } } else { if ( is_search() ) { $metadesc = ''; } elseif ( WPSEO_Frontend_Page_Type::is_home_posts_page() ) { $template = WPSEO_Options::get( 'metadesc-home-wpseo' ); $term = array(); if ( empty( $template ) ) { $template = get_bloginfo( 'description' ); } } elseif ( WPSEO_Frontend_Page_Type::is_home_static_page() ) { $metadesc = $this->get_seo_meta_value( 'metadesc' ); if ( ( $metadesc === '' && $post_type !== '' ) && WPSEO_Options::get( 'metadesc-' . $post_type, '' ) !== '' ) { $template = WPSEO_Options::get( 'metadesc-' . $post_type ); } } elseif ( is_category() || is_tag() || is_tax() ) { $term = $wp_query->get_queried_object(); $metadesc_override = WPSEO_Taxonomy_Meta::get_term_meta( $term, $term->taxonomy, 'desc' ); if ( is_object( $term ) && isset( $term->taxonomy ) && WPSEO_Options::get( 'metadesc-tax-' . $term->taxonomy, '' ) !== '' ) { $template = WPSEO_Options::get( 'metadesc-tax-' . $term->taxonomy ); } } elseif ( is_author() ) { $author_id = get_query_var( 'author' ); $metadesc = get_the_author_meta( 'wpseo_metadesc', $author_id ); if ( ( ! is_string( $metadesc ) || $metadesc === '' ) && WPSEO_Options::get( 'metadesc-author-wpseo', '' ) !== '' ) { $template = WPSEO_Options::get( 'metadesc-author-wpseo' ); } } elseif ( is_post_type_archive() ) { $post_type = $this->get_queried_post_type(); if ( WPSEO_Options::get( 'metadesc-ptarchive-' . $post_type, '' ) !== '' ) { $template = WPSEO_Options::get( 'metadesc-ptarchive-' . $post_type ); } } elseif ( is_archive() ) { $template = WPSEO_Options::get( 'metadesc-archive-wpseo' ); } // If we're on a paginated page, and the template doesn't change for paginated pages, bail. if ( ( ! is_string( $metadesc ) || $metadesc === '' ) && get_query_var( 'paged' ) && get_query_var( 'paged' ) > 1 && $template !== '' ) { if ( strpos( $template, '%%page' ) === false ) { $metadesc = ''; } } } $post_data = $post; if ( is_string( $metadesc_override ) && '' !== $metadesc_override ) { $metadesc = $metadesc_override; if ( isset( $term ) ) { $post_data = $term; } } elseif ( ( ! is_string( $metadesc ) || '' === $metadesc ) && '' !== $template ) { if ( ! isset( $term ) ) { $term = $wp_query->get_queried_object(); } $metadesc = $template; $post_data = $term; } $metadesc = $this->replace_vars( $metadesc, $post_data ); /** * Filter: 'wpseo_metadesc' - Allow changing the Yoast SEO meta description sentence. * * @api string $metadesc The description sentence. */ $this->metadesc = apply_filters( 'wpseo_metadesc', trim( $metadesc ) ); } /** * Based on the redirect meta value, this function determines whether it should redirect the current post / page. * * @return boolean */ public function page_redirect() { if ( is_singular() ) { global $post; if ( ! isset( $post ) || ! is_object( $post ) ) { return false; } $redir = $this->get_seo_meta_value( 'redirect', $post->ID ); if ( $redir !== '' ) { header( 'X-Redirect-By: Yoast SEO' ); wp_redirect( $redir, 301, 'Yoast SEO' ); exit; } } return false; } /** * Outputs noindex values for the current page. */ public function noindex_page() { remove_action( 'wpseo_head', array( $this, 'canonical' ), 20 ); echo '', "\n"; } /** * Send a Robots HTTP header preventing URL from being indexed in the search results while allowing search engines * to follow the links in the object at the URL. * * @since 1.1.7 * @return boolean Boolean indicating whether the noindex header was sent. */ public function noindex_robots() { if ( ( is_robots() ) && headers_sent() === false ) { header( 'X-Robots-Tag: noindex, follow', true ); return true; } return false; } /** * Adds rel="nofollow" to a link, only used for login / registration links. * * @param string $input The link element as a string. * * @return string */ public function nofollow_link( $input ) { return str_replace( 'is_date ) || ( WPSEO_Options::get( 'disable-author', false ) && $wp_query->is_author ) || ( WPSEO_Options::get( 'disable-post_format', false ) && $wp_query->is_tax( 'post_format' ) ) ) { $this->redirect( get_bloginfo( 'url' ), 301 ); return true; } return false; } /** * If the option to disable attachment URLs is checked, this performs the redirect to the attachment. * * @return bool Returns succes status. */ public function attachment_redirect() { if ( WPSEO_Options::get( 'disable-attachment', false ) === false ) { return false; } if ( ! is_attachment() ) { return false; } /** * Allow the developer to change the target redirection URL for attachments. * * @api string $attachment_url The attachment URL for the queried object. * @api object $queried_object The queried object. * * @since 7.5.3 */ $url = apply_filters( 'wpseo_attachment_redirect_url', wp_get_attachment_url( get_queried_object_id() ), get_queried_object() ); if ( ! empty( $url ) ) { $this->do_attachment_redirect( $url ); return true; } return false; } /** * Performs the redirect from the attachment page to the image file itself. * * @param string $attachment_url The attachment image url. * * @return void */ public function do_attachment_redirect( $attachment_url ) { header( 'X-Redirect-By: Yoast SEO' ); wp_redirect( $attachment_url, 301, 'Yoast SEO' ); exit; } /** * Replaces the possible RSS variables with their actual values. * * @param string $content The RSS content that should have the variables replaced. * * @return string */ public function rss_replace_vars( $content ) { global $post; /** * Allow the developer to determine whether or not to follow the links in the bits Yoast SEO adds to the RSS feed, defaults to true. * * @api bool $unsigned Whether or not to follow the links in RSS feed, defaults to true. * * @since 1.4.20 */ $no_follow = apply_filters( 'nofollow_rss_links', true ); $no_follow_attr = ''; if ( $no_follow === true ) { $no_follow_attr = 'rel="nofollow" '; } $author_link = ''; if ( is_object( $post ) ) { $author_link = 'post_author ) ) . '">' . esc_html( get_the_author() ) . ''; } $post_link = '' . esc_html( get_the_title() ) . ''; $blog_link = '' . esc_html( get_bloginfo( 'name' ) ) . ''; $blog_desc_link = '' . esc_html( get_bloginfo( 'name' ) ) . ' - ' . esc_html( get_bloginfo( 'description' ) ) . ''; $content = stripslashes( trim( $content ) ); $content = str_replace( '%%AUTHORLINK%%', $author_link, $content ); $content = str_replace( '%%POSTLINK%%', $post_link, $content ); $content = str_replace( '%%BLOGLINK%%', $blog_link, $content ); $content = str_replace( '%%BLOGDESCLINK%%', $blog_desc_link, $content ); return $content; } /** * Adds the RSS footer (or header) to the full RSS feed item. * * @param string $content Feed item content. * * @return string */ public function embed_rssfooter( $content ) { return $this->embed_rss( $content, 'full' ); } /** * Adds the RSS footer (or header) to the excerpt RSS feed item. * * @param string $content Feed item excerpt. * * @return string */ public function embed_rssfooter_excerpt( $content ) { return $this->embed_rss( $content, 'excerpt' ); } /** * Adds the RSS footer and/or header to an RSS feed item. * * @since 1.4.14 * * @param string $content Feed item content. * @param string $context Feed item context, either 'excerpt' or 'full'. * * @return string */ public function embed_rss( $content, $context = 'full' ) { /** * Filter: 'wpseo_include_rss_footer' - Allow the RSS footer to be dynamically shown/hidden. * * @api boolean $show_embed Indicates if the RSS footer should be shown or not. * * @param string $context The context of the RSS content - 'full' or 'excerpt'. */ if ( ! apply_filters( 'wpseo_include_rss_footer', true, $context ) ) { return $content; } if ( is_feed() ) { $before = ''; $after = ''; if ( WPSEO_Options::get( 'rssbefore', '' ) !== '' ) { $before = wpautop( $this->rss_replace_vars( WPSEO_Options::get( 'rssbefore' ) ) ); } if ( WPSEO_Options::get( 'rssafter', '' ) !== '' ) { $after = wpautop( $this->rss_replace_vars( WPSEO_Options::get( 'rssafter' ) ) ); } if ( $before !== '' || $after !== '' ) { if ( ( isset( $context ) && $context === 'excerpt' ) && trim( $content ) !== '' ) { $content = wpautop( $content ); } $content = $before . $content . $after; } } return $content; } /** * Used in the force rewrite functionality this retrieves the output, replaces the title with the proper SEO * title and then flushes the output. */ public function flush_cache() { global $wp_query; if ( $this->ob_started !== true ) { return false; } $content = ob_get_clean(); $old_wp_query = $wp_query; wp_reset_query(); // Only replace the debug marker when it is hooked. if ( $this->show_debug_marker() ) { $title = $this->title( '' ); $debug_mark = $this->get_debug_mark(); /* * 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. */ $content = preg_replace( '/