initial
This commit is contained in:
@@ -0,0 +1,170 @@
|
||||
<?php
|
||||
/**
|
||||
* Adds factual SEO columns to wp-admin post list tables.
|
||||
*
|
||||
* Surfaces the per-post SEO *state* at a glance — schema type, whether a meta
|
||||
* description is set, and search visibility — without grading it. Whether a
|
||||
* given setting should be configured depends on the post's purpose, so we
|
||||
* report facts and let the author decide.
|
||||
*
|
||||
* @package automattic/jetpack
|
||||
*/
|
||||
|
||||
/**
|
||||
* Registers read-only SEO columns on every public post-list table.
|
||||
*/
|
||||
class Jetpack_SEO_Admin_Columns {
|
||||
|
||||
/**
|
||||
* Wire all hooks.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function init() {
|
||||
add_action( 'admin_init', array( __CLASS__, 'register_columns_for_post_types' ) );
|
||||
add_action( 'admin_enqueue_scripts', array( __CLASS__, 'enqueue_assets' ) );
|
||||
add_filter( 'default_hidden_columns', array( __CLASS__, 'default_hidden_columns' ), 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide the SEO columns by default in Screen Options.
|
||||
*
|
||||
* Three extra always-on columns squeeze the title column unreadably narrow,
|
||||
* so we default them to hidden using core's `default_hidden_columns` filter —
|
||||
* the standard mechanism for choosing which columns start hidden in Screen
|
||||
* Options. It only applies to users who have never customized Screen Options
|
||||
* for the screen, so anyone who explicitly enabled the columns keeps them.
|
||||
*
|
||||
* @param string[] $hidden Column IDs hidden by default.
|
||||
* @param WP_Screen $screen Current screen.
|
||||
* @return string[]
|
||||
*/
|
||||
public static function default_hidden_columns( $hidden, $screen ) {
|
||||
if ( isset( $screen->base ) && 'edit' === $screen->base ) {
|
||||
$hidden = array_merge(
|
||||
$hidden,
|
||||
array( 'jetpack_seo_schema', 'jetpack_seo_description', 'jetpack_seo_search' )
|
||||
);
|
||||
}
|
||||
return $hidden;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register columns + renderers for each public, visible post type.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function register_columns_for_post_types() {
|
||||
$post_types = get_post_types(
|
||||
array(
|
||||
'public' => true,
|
||||
'show_ui' => true,
|
||||
'show_in_rest' => true,
|
||||
),
|
||||
'names'
|
||||
);
|
||||
unset( $post_types['attachment'] );
|
||||
|
||||
foreach ( $post_types as $post_type ) {
|
||||
add_filter( "manage_{$post_type}_posts_columns", array( __CLASS__, 'add_columns' ) );
|
||||
add_action( "manage_{$post_type}_posts_custom_column", array( __CLASS__, 'render_column' ), 10, 2 );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert the SEO columns just after the title column.
|
||||
*
|
||||
* @param array $columns Existing columns keyed by column name.
|
||||
* @return array
|
||||
*/
|
||||
public static function add_columns( $columns ) {
|
||||
$new = array();
|
||||
foreach ( $columns as $key => $label ) {
|
||||
$new[ $key ] = $label;
|
||||
if ( 'title' === $key ) {
|
||||
$new['jetpack_seo_schema'] = __( 'Schema', 'jetpack' );
|
||||
$new['jetpack_seo_description'] = __( 'Meta description', 'jetpack' );
|
||||
$new['jetpack_seo_search'] = __( 'Search', 'jetpack' );
|
||||
}
|
||||
}
|
||||
return $new;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a single cell — factual state only.
|
||||
*
|
||||
* @param string $column Column identifier.
|
||||
* @param int $post_id Current row post ID.
|
||||
* @return void
|
||||
*/
|
||||
public static function render_column( $column, $post_id ) {
|
||||
$columns = array( 'jetpack_seo_schema', 'jetpack_seo_description', 'jetpack_seo_search' );
|
||||
if ( ! in_array( $column, $columns, true ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$coverage = Jetpack_SEO_Posts::get_post_seo_coverage( $post_id );
|
||||
|
||||
switch ( $column ) {
|
||||
case 'jetpack_seo_schema':
|
||||
$schema = Jetpack_SEO_Posts::get_post_schema_type( $post_id );
|
||||
echo esc_html( '' !== $schema ? self::schema_type_label( $schema ) : '—' );
|
||||
break;
|
||||
|
||||
case 'jetpack_seo_description':
|
||||
// wp_kses_post() sanitizes the markup and signals the escaping to PHPCS;
|
||||
// the muted branch wraps its (already-escaped) label in a <span>.
|
||||
echo wp_kses_post(
|
||||
$coverage['has_description']
|
||||
? esc_html__( 'Set', 'jetpack' )
|
||||
: '<span class="jetpack-seo-col-muted">' . esc_html__( 'Not set', 'jetpack' ) . '</span>'
|
||||
);
|
||||
break;
|
||||
|
||||
case 'jetpack_seo_search':
|
||||
echo wp_kses_post(
|
||||
$coverage['noindex']
|
||||
? esc_html__( 'Hidden', 'jetpack' )
|
||||
: '<span class="jetpack-seo-col-muted">' . esc_html__( 'Visible', 'jetpack' ) . '</span>'
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display label for an allowed schema type.
|
||||
*
|
||||
* @param string $schema Schema type slug.
|
||||
* @return string
|
||||
*/
|
||||
private static function schema_type_label( $schema ) {
|
||||
switch ( $schema ) {
|
||||
case 'article':
|
||||
return __( 'Article', 'jetpack' );
|
||||
case 'faq':
|
||||
return __( 'FAQ', 'jetpack' );
|
||||
default:
|
||||
return ucfirst( $schema );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Minimal column-width styling on edit.php only (no color-coding —
|
||||
* these columns report state, not a grade).
|
||||
*
|
||||
* @param string $hook_suffix Current admin hook suffix.
|
||||
* @return void
|
||||
*/
|
||||
public static function enqueue_assets( $hook_suffix ) {
|
||||
if ( 'edit.php' !== $hook_suffix ) {
|
||||
return;
|
||||
}
|
||||
wp_register_style( 'jetpack-seo-admin-columns', false, array(), JETPACK__VERSION );
|
||||
wp_add_inline_style(
|
||||
'jetpack-seo-admin-columns',
|
||||
'.column-jetpack_seo_schema,.column-jetpack_seo_description,.column-jetpack_seo_search{width:9em}' .
|
||||
'.jetpack-seo-col-muted{color:#787c82}'
|
||||
);
|
||||
wp_enqueue_style( 'jetpack-seo-admin-columns' );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,269 @@
|
||||
<?php
|
||||
/**
|
||||
* Class containing utility static methods for managing SEO options for Posts and Pages.
|
||||
*
|
||||
* @package automattic/jetpack
|
||||
*/
|
||||
|
||||
/**
|
||||
* Provides static utility methods for managing SEO options for Posts and Pages.
|
||||
*/
|
||||
class Jetpack_SEO_Posts {
|
||||
/**
|
||||
* Key of the post meta values that will be used to store post custom data.
|
||||
*/
|
||||
const DESCRIPTION_META_KEY = 'advanced_seo_description';
|
||||
const HTML_TITLE_META_KEY = 'jetpack_seo_html_title';
|
||||
const NOINDEX_META_KEY = 'jetpack_seo_noindex';
|
||||
const SCHEMA_TYPE_META_KEY = 'jetpack_seo_schema_type';
|
||||
const POST_META_KEYS_ARRAY = array(
|
||||
self::DESCRIPTION_META_KEY,
|
||||
self::HTML_TITLE_META_KEY,
|
||||
self::NOINDEX_META_KEY,
|
||||
self::SCHEMA_TYPE_META_KEY,
|
||||
);
|
||||
|
||||
/**
|
||||
* Allowed Schema.org types that can be stored in the per-post schema-type
|
||||
* meta. Empty string means "no override" — Schema_Builder picks a sensible
|
||||
* default for the post. Single source of truth for the meta enum, the
|
||||
* block-editor panel options, and Schema_Builder.
|
||||
*/
|
||||
const ALLOWED_SCHEMA_TYPES = array( '', 'article', 'faq' );
|
||||
|
||||
/**
|
||||
* Build meta description for post SEO.
|
||||
*
|
||||
* @param WP_Post|null $post Source of data for custom description.
|
||||
*
|
||||
* @return string Post description or empty string.
|
||||
*/
|
||||
public static function get_post_description( $post = null ) {
|
||||
$post = get_post( $post );
|
||||
if ( ! ( $post instanceof WP_Post ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if ( post_password_required() || ! is_singular() ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Business users can overwrite the description.
|
||||
$custom_description = self::get_post_custom_description( $post );
|
||||
|
||||
if ( ! empty( $custom_description ) ) {
|
||||
return $custom_description;
|
||||
}
|
||||
|
||||
if ( ! empty( $post->post_excerpt ) ) {
|
||||
return $post->post_excerpt;
|
||||
}
|
||||
|
||||
// Remove content within wp:query blocks and return.
|
||||
return Jetpack_SEO_Utils::remove_query_blocks( $post->post_content );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns post's custom meta description if it is set, and if
|
||||
* SEO tools are enabled for current blog.
|
||||
*
|
||||
* @param WP_Post|null $post Source of data for custom description.
|
||||
*
|
||||
* @return string Custom description or empty string
|
||||
*/
|
||||
public static function get_post_custom_description( $post = null ) {
|
||||
$post = get_post( $post );
|
||||
if ( ! ( $post instanceof WP_Post ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$custom_description = get_post_meta( $post->ID, self::DESCRIPTION_META_KEY, true );
|
||||
|
||||
if ( empty( $custom_description ) || ! Jetpack_SEO_Utils::is_enabled_jetpack_seo() ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $custom_description;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a custom HTML title for a post if one is set, and if
|
||||
* SEO tools are enabled for the current blog.
|
||||
*
|
||||
* @param WP_Post|null $post Source of data for the custom HTML title.
|
||||
*
|
||||
* @return string Custom HTML title or an empty string if not set.
|
||||
*/
|
||||
public static function get_post_custom_html_title( $post = null ) {
|
||||
$post = get_post( $post );
|
||||
if ( ! ( $post instanceof WP_Post ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$custom_html_title = get_post_meta( $post->ID, self::HTML_TITLE_META_KEY, true );
|
||||
|
||||
if ( empty( $custom_html_title ) || ! Jetpack_SEO_Utils::is_enabled_jetpack_seo() ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $custom_html_title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the `jetpack_seo_noindex` setting for a post, if
|
||||
* SEO tools are enabled for the current blog.
|
||||
*
|
||||
* @param WP_Post|null $post Provided post or defaults to the global post.
|
||||
*
|
||||
* @return bool True if post should be marked as noindex, false otherwise.
|
||||
*/
|
||||
public static function get_post_noindex_setting( $post = null ) {
|
||||
$post = get_post( $post );
|
||||
if ( ! ( $post instanceof WP_Post ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$mark_as_noindex = get_post_meta( $post->ID, self::NOINDEX_META_KEY, true );
|
||||
|
||||
if ( empty( $mark_as_noindex ) || ! Jetpack_SEO_Utils::is_enabled_jetpack_seo() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (bool) $mark_as_noindex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter callback for `jetpack_sitemap_skip_post`; if a post has `jetpack_seo_noindex` set to true,
|
||||
* then exclude that post from the Jetpack sitemap.
|
||||
*
|
||||
* @param bool $skip Whether to skip the post in the sitemap.
|
||||
* @param WP_Post $post The post to check.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function exclude_noindex_posts_from_jetpack_sitemap( $skip, $post ) {
|
||||
$exclude = self::get_post_noindex_setting( $post );
|
||||
if ( $exclude ) {
|
||||
$skip = true;
|
||||
}
|
||||
return $skip;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the SEO post meta keys for use in the REST API:
|
||||
* - self::DESCRIPTION_META_KEY
|
||||
* - self::HTML_TITLE_META_KEY
|
||||
* - self::NOINDEX_META_KEY
|
||||
* - self::SCHEMA_TYPE_META_KEY
|
||||
*/
|
||||
public static function register_post_meta() {
|
||||
$description_args = array(
|
||||
'type' => 'string',
|
||||
'description' => __( 'Custom post description to be used in HTML <meta /> tag.', 'jetpack' ),
|
||||
'single' => true,
|
||||
'default' => '',
|
||||
'show_in_rest' => array(
|
||||
'name' => self::DESCRIPTION_META_KEY,
|
||||
),
|
||||
);
|
||||
|
||||
$html_title_args = array(
|
||||
'type' => 'string',
|
||||
'description' => __( 'Custom title to be used in HTML <title /> tag.', 'jetpack' ),
|
||||
'single' => true,
|
||||
'default' => '',
|
||||
'show_in_rest' => array(
|
||||
'name' => self::HTML_TITLE_META_KEY,
|
||||
),
|
||||
);
|
||||
|
||||
$noindex_args = array(
|
||||
'type' => 'boolean',
|
||||
'description' => __( 'Whether to hide the post from search engines and the Jetpack sitemap.', 'jetpack' ),
|
||||
'single' => true,
|
||||
'default' => false,
|
||||
'show_in_rest' => array(
|
||||
'name' => self::NOINDEX_META_KEY,
|
||||
),
|
||||
);
|
||||
|
||||
$schema_type_args = array(
|
||||
'type' => 'string',
|
||||
'description' => __( 'Schema.org type to emit as JSON-LD for this post.', 'jetpack' ),
|
||||
'single' => true,
|
||||
'default' => '',
|
||||
'sanitize_callback' => array( __CLASS__, 'sanitize_schema_type' ),
|
||||
'show_in_rest' => array(
|
||||
'name' => self::SCHEMA_TYPE_META_KEY,
|
||||
// Enum so core REST rejects an unknown schema type with a proper
|
||||
// rest_invalid_param error; the sanitize_callback is the
|
||||
// defense-in-depth fallback for non-REST writes.
|
||||
'schema' => array(
|
||||
'type' => 'string',
|
||||
'enum' => self::ALLOWED_SCHEMA_TYPES,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
register_meta( 'post', self::DESCRIPTION_META_KEY, $description_args );
|
||||
register_meta( 'post', self::HTML_TITLE_META_KEY, $html_title_args );
|
||||
register_meta( 'post', self::NOINDEX_META_KEY, $noindex_args );
|
||||
register_meta( 'post', self::SCHEMA_TYPE_META_KEY, $schema_type_args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize a schema type to the allowed list. Unknown values become ''
|
||||
* (no override) rather than erroring, so a non-REST write can't store junk.
|
||||
*
|
||||
* @param string $value The submitted value.
|
||||
* @return string A value from self::ALLOWED_SCHEMA_TYPES.
|
||||
*/
|
||||
public static function sanitize_schema_type( $value ) {
|
||||
$value = is_string( $value ) ? sanitize_key( $value ) : '';
|
||||
return in_array( $value, self::ALLOWED_SCHEMA_TYPES, true ) ? $value : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the per-post schema-type override, if any.
|
||||
*
|
||||
* @param WP_Post|int|null $post Post or post ID.
|
||||
* @return string A value from self::ALLOWED_SCHEMA_TYPES ('' = no override).
|
||||
*/
|
||||
public static function get_post_schema_type( $post = null ) {
|
||||
$post = get_post( $post );
|
||||
if ( ! ( $post instanceof WP_Post ) ) {
|
||||
return '';
|
||||
}
|
||||
return self::sanitize_schema_type( (string) get_post_meta( $post->ID, self::SCHEMA_TYPE_META_KEY, true ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Factual per-post SEO field coverage — presence/state only, never a score.
|
||||
*
|
||||
* Single source of truth shared by the Content tab, the edit.php columns,
|
||||
* and the Overview coverage card so the three never drift. Reports whether
|
||||
* each field has been *set*, independent of whether SEO tools are currently
|
||||
* active (this is an authoring/audit view, not front-end emission).
|
||||
*
|
||||
* @param WP_Post|int|null $post Post or post ID.
|
||||
* @return array{has_custom_title:bool,has_description:bool,has_schema_type:bool,noindex:bool}
|
||||
*/
|
||||
public static function get_post_seo_coverage( $post = null ) {
|
||||
$post = get_post( $post );
|
||||
if ( ! ( $post instanceof WP_Post ) ) {
|
||||
return array(
|
||||
'has_custom_title' => false,
|
||||
'has_description' => false,
|
||||
'has_schema_type' => false,
|
||||
'noindex' => false,
|
||||
);
|
||||
}
|
||||
|
||||
return array(
|
||||
'has_custom_title' => '' !== (string) get_post_meta( $post->ID, self::HTML_TITLE_META_KEY, true ),
|
||||
'has_description' => '' !== (string) get_post_meta( $post->ID, self::DESCRIPTION_META_KEY, true ),
|
||||
'has_schema_type' => '' !== self::get_post_schema_type( $post ),
|
||||
'noindex' => (bool) get_post_meta( $post->ID, self::NOINDEX_META_KEY, true ),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,350 @@
|
||||
<?php
|
||||
/**
|
||||
* Class containing utility static methods for managing SEO custom title formats.
|
||||
*
|
||||
* @package automattic/jetpack
|
||||
*/
|
||||
|
||||
/*
|
||||
* Each title format is an array of arrays containing two values:
|
||||
* - type
|
||||
* - value
|
||||
*
|
||||
* Possible values for type are: 'token' and 'string'.
|
||||
* Possible values for 'value' are: any string in case that 'type' is set
|
||||
* to 'string', or allowed token values for page type in case that 'type'
|
||||
* is set to 'token'.
|
||||
*
|
||||
* Examples of valid formats:
|
||||
*
|
||||
* [
|
||||
* 'front_page' => [
|
||||
* [ 'type' => 'string', 'value' => 'Front page title and site name:'],
|
||||
* [ 'type' => 'token', 'value' => 'site_name']
|
||||
* ],
|
||||
* 'posts' => [
|
||||
* [ 'type' => 'token', 'value' => 'site_name' ],
|
||||
* [ 'type' => 'string', 'value' => ' | ' ],
|
||||
* [ 'type' => 'token', 'value' => 'post_title' ]
|
||||
* ],
|
||||
* 'pages' => [],
|
||||
* 'groups' => [],
|
||||
* 'archives' => []
|
||||
* ]
|
||||
* Custom title for given page type is created by concatenating all of the array 'value' parts.
|
||||
* Tokens are replaced with their corresponding values for current site.
|
||||
* Empty array signals that we are not overriding the default title for particular page type.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class containing utility static methods for managing SEO custom title formats.
|
||||
*/
|
||||
class Jetpack_SEO_Titles {
|
||||
/**
|
||||
* Site option name used to store custom title formats.
|
||||
*/
|
||||
const TITLE_FORMATS_OPTION = 'advanced_seo_title_formats';
|
||||
|
||||
/**
|
||||
* Retrieves custom title formats from site option.
|
||||
*
|
||||
* @return array Array of custom title formats, or empty array.
|
||||
*/
|
||||
public static function get_custom_title_formats() {
|
||||
if ( Jetpack_SEO_Utils::is_enabled_jetpack_seo() ) {
|
||||
return get_option( self::TITLE_FORMATS_OPTION, array() );
|
||||
}
|
||||
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns tokens that are currently supported for each page type.
|
||||
*
|
||||
* @return array Array of allowed token strings.
|
||||
*/
|
||||
public static function get_allowed_tokens() {
|
||||
return array(
|
||||
'front_page' => array( 'site_name', 'tagline' ),
|
||||
'posts' => array( 'site_name', 'tagline', 'post_title' ),
|
||||
'pages' => array( 'site_name', 'tagline', 'page_title' ),
|
||||
'groups' => array( 'site_name', 'tagline', 'group_title' ),
|
||||
'archives' => array( 'site_name', 'tagline', 'date', 'archive_title' ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to modify the default title with custom SEO title.
|
||||
*
|
||||
* @param string $default_title Default title for current page.
|
||||
*
|
||||
* @return string A custom per-post title, custom title structure with replaced tokens, or default title.
|
||||
*/
|
||||
public static function get_custom_title( $default_title = '' ) {
|
||||
// Don't filter title for unsupported themes.
|
||||
if ( self::is_conflicted_theme() ) {
|
||||
return $default_title;
|
||||
}
|
||||
|
||||
$page_type = self::get_page_type();
|
||||
|
||||
// Keep default title if invalid page type is supplied.
|
||||
if ( empty( $page_type ) ) {
|
||||
return $default_title;
|
||||
}
|
||||
|
||||
if ( ! Jetpack_SEO_Utils::is_enabled_jetpack_seo() ) {
|
||||
return $default_title;
|
||||
}
|
||||
|
||||
// If it's a singular -- page or post -- check for a meta title override.
|
||||
if ( 'pages' === $page_type || 'posts' === $page_type ) {
|
||||
$post = get_post();
|
||||
if ( $post instanceof WP_Post ) {
|
||||
$custom_title = get_post_meta( $post->ID, Jetpack_SEO_Posts::HTML_TITLE_META_KEY, true );
|
||||
if ( ! empty( trim( $custom_title ) ) ) {
|
||||
return esc_html( $custom_title );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$title_formats = self::get_custom_title_formats();
|
||||
|
||||
// Keep default title if user has not defined custom title for this page type.
|
||||
if ( empty( $title_formats[ $page_type ] ) ) {
|
||||
return $default_title;
|
||||
}
|
||||
|
||||
$custom_title = '';
|
||||
$format_array = $title_formats[ $page_type ];
|
||||
|
||||
foreach ( $format_array as $item ) {
|
||||
if ( 'token' === $item['type'] ) {
|
||||
$custom_title .= self::get_token_value( $item['value'] );
|
||||
} else {
|
||||
$custom_title .= $item['value'];
|
||||
}
|
||||
}
|
||||
|
||||
return esc_html( $custom_title );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns string value for given token.
|
||||
*
|
||||
* @param string $token_name The token name value that should be replaced.
|
||||
*
|
||||
* @return string Token replacement for current site, or empty string for unknown token name.
|
||||
*/
|
||||
public static function get_token_value( $token_name ) {
|
||||
|
||||
switch ( $token_name ) {
|
||||
case 'site_name':
|
||||
return get_bloginfo( 'name' );
|
||||
|
||||
case 'tagline':
|
||||
return get_bloginfo( 'description' );
|
||||
|
||||
case 'post_title':
|
||||
case 'page_title':
|
||||
return the_title_attribute( array( 'echo' => false ) );
|
||||
|
||||
case 'group_title':
|
||||
return single_tag_title( '', false );
|
||||
|
||||
case 'date':
|
||||
case 'archive_title':
|
||||
return self::get_archive_title();
|
||||
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns page type for current page. We need this helper in order to determine what
|
||||
* user defined title format should be used for custom title.
|
||||
*
|
||||
* @return string|bool Type of current page or false if unsupported.
|
||||
*/
|
||||
public static function get_page_type() {
|
||||
|
||||
if ( is_front_page() ) {
|
||||
return 'front_page';
|
||||
}
|
||||
|
||||
if ( is_category() || is_tag() || is_tax() ) {
|
||||
return 'groups';
|
||||
}
|
||||
|
||||
if ( is_archive() && ! is_author() ) {
|
||||
return 'archives';
|
||||
}
|
||||
|
||||
if ( is_page() ) {
|
||||
return 'pages';
|
||||
}
|
||||
|
||||
if ( is_singular() ) {
|
||||
return 'posts';
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value that should be used as a replacement for the `date` or `archive_title` tokens.
|
||||
* For date-based archives, a date is returned. Otherwise the `post_type_archive_title` is returned.
|
||||
*
|
||||
* The `archive_title` token was added after the `date` token to provide a more generic option
|
||||
* that would work for non date-based archives.
|
||||
*
|
||||
* @return string Token replaced string.
|
||||
*/
|
||||
public static function get_archive_title() {
|
||||
// If archive year, month, and day are specified.
|
||||
if ( is_day() ) {
|
||||
return get_the_date();
|
||||
}
|
||||
|
||||
// If archive year, and month are specified.
|
||||
if ( is_month() ) {
|
||||
return trim( single_month_title( ' ', false ) );
|
||||
}
|
||||
|
||||
// Only archive year is specified.
|
||||
if ( is_year() ) {
|
||||
return get_query_var( 'year' );
|
||||
}
|
||||
|
||||
// Not a date based archive.
|
||||
// An example would be "Projects" for Jetpack's Portoflio CPT.
|
||||
return post_type_archive_title( '', false );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if current theme is defining custom title that won't work nicely
|
||||
* with our custom SEO title override.
|
||||
*
|
||||
* @return bool True if current theme sets custom title, false otherwise.
|
||||
*/
|
||||
public static function is_conflicted_theme() {
|
||||
/**
|
||||
* Can be used to specify a list of themes that use their own custom title format.
|
||||
*
|
||||
* If current site is using one of the themes listed as conflicting,
|
||||
* Jetpack SEO custom title formats will be disabled.
|
||||
*
|
||||
* @module seo-tools
|
||||
*
|
||||
* @since 4.4.0
|
||||
*
|
||||
* @param array List of conflicted theme names. Defaults to empty array.
|
||||
*/
|
||||
$conflicted_themes = apply_filters( 'jetpack_seo_custom_title_conflicted_themes', array() );
|
||||
|
||||
return isset( $conflicted_themes[ get_option( 'template' ) ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given format conforms to predefined SEO title templates.
|
||||
*
|
||||
* Every format type and token must be specifically allowed.
|
||||
*
|
||||
* @see get_allowed_tokens()
|
||||
*
|
||||
* @param array $title_formats Template of SEO title to check.
|
||||
*
|
||||
* @return bool True if the formats are valid, false otherwise.
|
||||
*/
|
||||
public static function are_valid_title_formats( $title_formats ) {
|
||||
$allowed_tokens = self::get_allowed_tokens();
|
||||
|
||||
if ( ! is_array( $title_formats ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ( $title_formats as $format_type => $format_array ) {
|
||||
if ( ! array_key_exists( $format_type, $allowed_tokens ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( '' === $format_array ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( ! is_array( $format_array ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ( $format_array as $item ) {
|
||||
if ( empty( $item['type'] ) || empty( $item['value'] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( 'token' === $item['type'] ) {
|
||||
if ( ! in_array( $item['value'], $allowed_tokens[ $format_type ], true ) ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitizes the arbitrary user input strings for custom SEO titles.
|
||||
*
|
||||
* @param array $title_formats Array of custom title formats.
|
||||
*
|
||||
* @return array The sanitized array.
|
||||
*/
|
||||
public static function sanitize_title_formats( $title_formats ) {
|
||||
foreach ( $title_formats as &$format_array ) {
|
||||
foreach ( $format_array as &$item ) {
|
||||
if ( 'string' === $item['type'] ) {
|
||||
// From `wp_strip_all_tags`, but omitting the `trim` portion since we want spacing preserved.
|
||||
$item['value'] = preg_replace( '@<(script|style)[^>]*?>.*?</\\1>@si', '', $item['value'] );
|
||||
$item['value'] = strip_tags( $item['value'] ); // phpcs:ignore WordPress.WP.AlternativeFunctions.strip_tags_strip_tags
|
||||
$item['value'] = preg_replace( '/[\r\n\t ]+/', ' ', $item['value'] );
|
||||
}
|
||||
}
|
||||
}
|
||||
unset( $format_array );
|
||||
unset( $item );
|
||||
|
||||
return $title_formats;
|
||||
}
|
||||
|
||||
/**
|
||||
* Combines the previous values of title formats, stored as array in site options,
|
||||
* with the new values that are provided.
|
||||
*
|
||||
* @param array $new_formats Array containing new title formats.
|
||||
*
|
||||
* @return array $result Array of updated title formats, or empty array if no update was performed.
|
||||
*/
|
||||
public static function update_title_formats( $new_formats ) {
|
||||
$new_formats = self::sanitize_title_formats( $new_formats );
|
||||
|
||||
// Empty array signals that custom title shouldn't be used.
|
||||
$empty_formats = array(
|
||||
'front_page' => array(),
|
||||
'posts' => array(),
|
||||
'pages' => array(),
|
||||
'groups' => array(),
|
||||
'archives' => array(),
|
||||
);
|
||||
|
||||
$previous_formats = self::get_custom_title_formats();
|
||||
|
||||
$result = array_merge( $empty_formats, $previous_formats, $new_formats );
|
||||
|
||||
if ( update_option( self::TITLE_FORMATS_OPTION, $result ) ) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
return array();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
<?php
|
||||
/**
|
||||
* Class containing utility static methods that other SEO tools are relying on.
|
||||
*
|
||||
* @package automattic/jetpack
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class containing utility static methods that other SEO tools are relying on.
|
||||
*/
|
||||
class Jetpack_SEO_Utils {
|
||||
/**
|
||||
* Site option name used to store front page meta description.
|
||||
*/
|
||||
const FRONT_PAGE_META_OPTION = 'advanced_seo_front_page_description';
|
||||
|
||||
/**
|
||||
* The LEGACY_META_OPTION is used to support legacy usage on WPcom simple sites (free or paid).
|
||||
* For WPorg JP sites, the JP seo-tools features were made free for all sites (free or paid).
|
||||
*/
|
||||
const LEGACY_META_OPTION = 'seo_meta_description';
|
||||
|
||||
/**
|
||||
* Used to check whether SEO tools are enabled for given site.
|
||||
*
|
||||
* @return bool True if SEO tools are enabled, false otherwise.
|
||||
*/
|
||||
public static function is_enabled_jetpack_seo() {
|
||||
/**
|
||||
* Can be used by SEO plugin authors to disable the conflicting output of SEO Tools.
|
||||
*
|
||||
* @module seo-tools
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @param bool True if SEO Tools should be disabled, false otherwise.
|
||||
*/
|
||||
if ( apply_filters( 'jetpack_disable_seo_tools', false ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
|
||||
return wpcom_site_has_feature( 'advanced-seo', get_current_blog_id() );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this option was set while it was freely available to all WPcom simple sites.
|
||||
*
|
||||
* @return bool True if we should enable legacy usage, false otherwise.
|
||||
*/
|
||||
public static function has_legacy_front_page_meta() {
|
||||
return ! self::is_enabled_jetpack_seo() && get_option( self::LEGACY_META_OPTION );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns front page meta description for current site.
|
||||
*
|
||||
* @return string Front page meta description string or empty string.
|
||||
*/
|
||||
public static function get_front_page_meta_description() {
|
||||
if ( self::is_enabled_jetpack_seo() ) {
|
||||
$front_page_meta = get_option( self::FRONT_PAGE_META_OPTION );
|
||||
return $front_page_meta ? $front_page_meta : get_option( self::LEGACY_META_OPTION, '' );
|
||||
}
|
||||
|
||||
// Support legacy usage for WPcom simple sites.
|
||||
return get_option( self::LEGACY_META_OPTION, '' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitizes the custom front page meta description input.
|
||||
*
|
||||
* @param string $value Front page meta string.
|
||||
*
|
||||
* @return string The sanitized string.
|
||||
*/
|
||||
public static function sanitize_front_page_meta_description( $value ) {
|
||||
return wp_strip_all_tags( $value );
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the site option value for front page meta description.
|
||||
*
|
||||
* @param string $value New value for front page meta description.
|
||||
*
|
||||
* @return string Saved value, or empty string if no update was performed.
|
||||
*/
|
||||
public static function update_front_page_meta_description( $value ) {
|
||||
$front_page_description = self::sanitize_front_page_meta_description( $value );
|
||||
|
||||
/**
|
||||
* Can be used to limit the length of front page meta description.
|
||||
*
|
||||
* @module seo-tools
|
||||
*
|
||||
* @since 4.4.0
|
||||
*
|
||||
* @param int Maximum length of front page meta description. Defaults to 300.
|
||||
*/
|
||||
$description_max_length = apply_filters( 'jetpack_seo_front_page_description_max_length', 300 );
|
||||
|
||||
if ( function_exists( 'mb_substr' ) ) {
|
||||
$front_page_description = mb_substr( $front_page_description, 0, $description_max_length );
|
||||
} else {
|
||||
$front_page_description = substr( $front_page_description, 0, $description_max_length );
|
||||
}
|
||||
|
||||
$can_set_meta = self::is_enabled_jetpack_seo();
|
||||
$legacy_meta_option = get_option( self::LEGACY_META_OPTION );
|
||||
$has_old_meta = ! empty( $legacy_meta_option );
|
||||
$option_name = self::has_legacy_front_page_meta() ? self::LEGACY_META_OPTION : self::FRONT_PAGE_META_OPTION;
|
||||
|
||||
$did_update = update_option( $option_name, $front_page_description );
|
||||
|
||||
if ( $did_update && $has_old_meta && $can_set_meta ) {
|
||||
// Delete legacy option if user has switched to Business or eCommerce plan and updated the front page meta description.
|
||||
delete_option( self::LEGACY_META_OPTION );
|
||||
}
|
||||
|
||||
if ( $did_update ) {
|
||||
return $front_page_description;
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove content within wp:query blocks.
|
||||
*
|
||||
* @uses jetpack_og_remove_query_blocks
|
||||
*
|
||||
* @since 14.9
|
||||
*
|
||||
* @param string $content Post content.
|
||||
*
|
||||
* @return string Post content stripped from wp:query blocks.
|
||||
*/
|
||||
public static function remove_query_blocks( $content ) {
|
||||
if ( ! function_exists( 'jetpack_og_remove_query_blocks' ) ) {
|
||||
return $content;
|
||||
}
|
||||
|
||||
return jetpack_og_remove_query_blocks( $content );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,295 @@
|
||||
<?php
|
||||
/**
|
||||
* Main class file for the SEO Tools module.
|
||||
*
|
||||
* @package automattic/jetpack
|
||||
*/
|
||||
|
||||
/**
|
||||
* An SEO expert walks into a bar, bars, pub, public house, Irish pub, drinks, beer, wine, liquor, Grey Goose, Cristal...
|
||||
*
|
||||
* @phan-constructor-used-for-side-effects
|
||||
*/
|
||||
class Jetpack_SEO {
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
add_action( 'init', array( $this, 'init' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialization method for Jetpack_SEO.
|
||||
*/
|
||||
public function init() {
|
||||
/**
|
||||
* Can be used to prevent SEO tools from inserting custom meta tags.
|
||||
*
|
||||
* @module seo-tools
|
||||
*
|
||||
* @since 4.4.0
|
||||
*
|
||||
* @param bool true Should Jetpack's SEO Meta Tags be enabled. Defaults to true.
|
||||
*/
|
||||
if ( apply_filters( 'jetpack_seo_meta_tags_enabled', true ) ) {
|
||||
add_action( 'wp_head', array( $this, 'meta_tags' ) );
|
||||
|
||||
// Add support for editing page excerpts in pages, regardless of theme support.
|
||||
add_post_type_support( 'page', 'excerpt' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Can be used to prevent SEO tools from modifying site titles.
|
||||
*
|
||||
* @module seo-tools
|
||||
*
|
||||
* @since 4.4.0
|
||||
*
|
||||
* @param bool true Should Jetpack SEO modify site titles. Defaults to true.
|
||||
*/
|
||||
if ( apply_filters( 'jetpack_seo_custom_titles', true ) ) {
|
||||
// Overwrite page title with custom SEO meta title for themes that support title-tag.
|
||||
add_filter( 'pre_get_document_title', array( 'Jetpack_SEO_Titles', 'get_custom_title' ) );
|
||||
|
||||
// Add overwrite support for themes that don't support title-tag.
|
||||
add_filter( 'wp_title', array( 'Jetpack_SEO_Titles', 'get_custom_title' ) );
|
||||
}
|
||||
|
||||
add_filter( 'jetpack_open_graph_tags', array( $this, 'set_custom_og_tags' ) );
|
||||
Jetpack_SEO_Posts::register_post_meta();
|
||||
// Exclude posts with 'jetpack_seo_noindex' set true from the Jetpack sitemap.
|
||||
add_filter( 'jetpack_sitemap_skip_post', array( 'Jetpack_SEO_Posts', 'exclude_noindex_posts_from_jetpack_sitemap' ), 10, 2 );
|
||||
add_action( 'rest_api_init', array( $this, 'add_custom_field_post_type_meta' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add custom field meta to all public post types that don't already have it.
|
||||
*/
|
||||
public function add_custom_field_post_type_meta() {
|
||||
/**
|
||||
* Filter the list of post types for which custom fields support is added.
|
||||
*
|
||||
* This filter allows modification of the post types that will be processed
|
||||
* to add support for custom fields if they do not already support it.
|
||||
*
|
||||
* @since 14.2
|
||||
*
|
||||
* @param array $post_types An array of post type names.
|
||||
*/
|
||||
$post_types = apply_filters(
|
||||
'jetpack_seo_custom_field_post_types',
|
||||
get_post_types(
|
||||
array(
|
||||
'public' => true,
|
||||
'show_ui' => true,
|
||||
'_builtin' => false,
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
foreach ( $post_types as $post_type ) {
|
||||
if ( ! post_type_supports( $post_type, 'custom-fields' ) ) {
|
||||
add_post_type_support( $post_type, 'custom-fields' );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to fetch authors.
|
||||
*/
|
||||
private function get_authors() {
|
||||
global $wp_query;
|
||||
|
||||
$authors = array();
|
||||
|
||||
foreach ( $wp_query->posts as $post ) {
|
||||
if ( ! $post instanceof WP_Post ) {
|
||||
continue;
|
||||
}
|
||||
$authors[] = get_the_author_meta( 'display_name', (int) $post->post_author );
|
||||
}
|
||||
|
||||
$authors = array_unique( $authors );
|
||||
|
||||
return $authors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs open graph tag data.
|
||||
*
|
||||
* @param array $tags Array of tag data.
|
||||
* @return array of tag data.
|
||||
*/
|
||||
public function set_custom_og_tags( $tags ) {
|
||||
$custom_title = Jetpack_SEO_Titles::get_custom_title();
|
||||
|
||||
if ( ! empty( $custom_title ) ) {
|
||||
$tags['og:title'] = $custom_title;
|
||||
}
|
||||
|
||||
$post_custom_description = Jetpack_SEO_Posts::get_post_custom_description( get_post() );
|
||||
$front_page_meta = Jetpack_SEO_Utils::get_front_page_meta_description();
|
||||
|
||||
if ( class_exists( 'woocommerce' ) && is_shop() ) {
|
||||
$shop_page_id = get_option( 'woocommerce_shop_page_id' );
|
||||
if ( $shop_page_id ) {
|
||||
$post_custom_description = Jetpack_SEO_Posts::get_post_custom_description( get_post( $shop_page_id ) );
|
||||
}
|
||||
}
|
||||
|
||||
if ( is_front_page() && ! empty( $front_page_meta ) ) {
|
||||
$tags['og:description'] = $front_page_meta;
|
||||
} elseif ( ! empty( $post_custom_description ) ) {
|
||||
$tags['og:description'] = $post_custom_description;
|
||||
}
|
||||
|
||||
return $tags;
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs Jetpack's SEO <meta> tags.
|
||||
*/
|
||||
public function meta_tags() {
|
||||
global $wp_query;
|
||||
|
||||
$post_count = is_countable( $wp_query->posts ) ? count( $wp_query->posts ) : 0;
|
||||
$period = '';
|
||||
$template = '';
|
||||
$meta = array();
|
||||
|
||||
/**
|
||||
* Can be used to specify a list of themes that set their own meta tags.
|
||||
*
|
||||
* If current site is using one of the themes listed as conflicting, inserting Jetpack SEO
|
||||
* meta tags will be prevented.
|
||||
*
|
||||
* @module seo-tools
|
||||
*
|
||||
* @since 4.4.0
|
||||
*
|
||||
* @param array List of conflicted theme names. Defaults to empty array.
|
||||
*/
|
||||
$conflicted_themes = apply_filters( 'jetpack_seo_meta_tags_conflicted_themes', array() );
|
||||
|
||||
if ( isset( $conflicted_themes[ get_option( 'template' ) ] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$front_page_meta = Jetpack_SEO_Utils::get_front_page_meta_description();
|
||||
$description = $front_page_meta ? $front_page_meta : get_bloginfo( 'description' );
|
||||
$meta['description'] = trim( $description );
|
||||
|
||||
// Try to target things if we're on a "specific" page of any kind.
|
||||
if ( is_singular() ) {
|
||||
if ( ! ( is_front_page() && Jetpack_SEO_Utils::get_front_page_meta_description() ) ) {
|
||||
$description = Jetpack_SEO_Posts::get_post_description( get_post() );
|
||||
|
||||
if ( $description ) {
|
||||
$description = wp_trim_words(
|
||||
strip_shortcodes(
|
||||
wp_strip_all_tags( $description, true )
|
||||
)
|
||||
);
|
||||
$meta['description'] = $description;
|
||||
}
|
||||
}
|
||||
} elseif ( is_author() ) {
|
||||
$obj = get_queried_object();
|
||||
|
||||
$meta['description'] = sprintf(
|
||||
/* translators: first property is an user's display name, the second is the site's title. */
|
||||
_x( 'Read all of the posts by %1$s on %2$s', 'Read all of the posts by Author Name on Blog Title', 'jetpack' ),
|
||||
$obj->display_name ?? __( 'the author', 'jetpack' ),
|
||||
get_bloginfo( 'title' )
|
||||
);
|
||||
} elseif ( is_tag() || is_category() || is_tax() ) {
|
||||
$obj = get_queried_object();
|
||||
$description = '';
|
||||
|
||||
if ( isset( $obj->term_id ) && isset( $obj->taxonomy ) ) {
|
||||
$description = get_term_field( 'description', $obj->term_id, $obj->taxonomy, 'raw' );
|
||||
}
|
||||
|
||||
if ( ! is_wp_error( $description ) && $description ) {
|
||||
$meta['description'] = wp_trim_words( $description );
|
||||
} else {
|
||||
$authors = $this->get_authors();
|
||||
|
||||
$meta['description'] = wp_sprintf(
|
||||
/* translators: %1$s: A post category. %2$l: Post authors. */
|
||||
_x( 'Posts about %1$s written by %2$l', 'Posts about Category written by John and Bob', 'jetpack' ),
|
||||
single_term_title( '', false ),
|
||||
$authors
|
||||
);
|
||||
}
|
||||
} elseif ( is_date() ) {
|
||||
if ( is_year() ) {
|
||||
$period = get_query_var( 'year' );
|
||||
|
||||
/* translators: %1$s: Number of posts published. %2$l: Post author. %3$s: A year date. */
|
||||
$template = _nx(
|
||||
'%1$s post published by %2$l in the year %3$s', // Singular.
|
||||
'%1$s posts published by %2$l in the year %3$s', // Plural.
|
||||
$post_count, // Number.
|
||||
'10 posts published by John in the year 2012', // Context.
|
||||
'jetpack'
|
||||
);
|
||||
} elseif ( is_month() ) {
|
||||
$period = gmdate( 'F Y', mktime( 0, 0, 0, get_query_var( 'monthnum' ), 1, get_query_var( 'year' ) ) );
|
||||
|
||||
/* translators: %1$s: Number of posts published. %2$l: Post author. %3$s: A month/year date. */
|
||||
$template = _nx(
|
||||
'%1$s post published by %2$l during %3$s', // Singular.
|
||||
'%1$s posts published by %2$l during %3$s', // Plural.
|
||||
$post_count, // Number.
|
||||
'10 posts publishes by John during May 2012', // Context.
|
||||
'jetpack'
|
||||
);
|
||||
} elseif ( is_day() ) {
|
||||
$period = gmdate(
|
||||
'F j, Y',
|
||||
mktime( 0, 0, 0, get_query_var( 'monthnum' ), get_query_var( 'day' ), get_query_var( 'year' ) )
|
||||
);
|
||||
|
||||
/* translators: %1$s: Number of posts published. %2$l: Post author. %3$s: A month/day/year date. */
|
||||
$template = _nx(
|
||||
'%1$s post published by %2$l on %3$s', // Singular.
|
||||
'%1$s posts published by %2$l on %3$s', // Plural.
|
||||
$post_count, // Number.
|
||||
'10 posts published by John on May 30, 2012', // Context.
|
||||
'jetpack'
|
||||
);
|
||||
}
|
||||
|
||||
$authors = $this->get_authors();
|
||||
$meta['description'] = wp_sprintf( $template, $post_count, $authors, $period );
|
||||
}
|
||||
|
||||
$mark_as_noindex = Jetpack_SEO_Posts::get_post_noindex_setting( get_post() );
|
||||
if ( $mark_as_noindex ) {
|
||||
$meta['robots'] = 'noindex';
|
||||
}
|
||||
|
||||
/**
|
||||
* Can be used to edit the default SEO tools meta tags.
|
||||
*
|
||||
* @module seo-tools
|
||||
*
|
||||
* @since 4.4.0
|
||||
*
|
||||
* @param array Array that consists of meta name and meta content pairs.
|
||||
*/
|
||||
$meta = apply_filters( 'jetpack_seo_meta_tags', $meta );
|
||||
|
||||
if ( ! is_array( $meta ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Output them.
|
||||
foreach ( $meta as $name => $content ) {
|
||||
if ( ! empty( $content ) ) {
|
||||
echo '<meta name="' . esc_attr( $name ) . '" content="' . esc_attr( $content ) . '" />' . "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user