output built from them.
*
* @package ACSSEO
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Title, meta description, canonical URL and robots directives.
*/
class ACSSEO_Meta {
/**
* Singleton instance.
*
* @var ACSSEO_Meta|null
*/
private static $instance = null;
/**
* Return the shared instance.
*
* @return ACSSEO_Meta
*/
public static function instance() {
if ( null === self::$instance ) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Hook everything up.
*/
private function __construct() {
add_action( 'add_meta_boxes', array( $this, 'add_meta_box' ) );
add_action( 'save_post', array( $this, 'save' ), 10, 2 );
add_filter( 'pre_get_document_title', array( $this, 'filter_document_title' ), 20 );
add_action( 'wp_head', array( $this, 'output_head' ), 2 );
}
/**
* Post types that get the SEO meta box.
*
* @return string[]
*/
public static function supported_post_types() {
$types = get_post_types(
array(
'public' => true,
),
'names'
);
unset( $types['attachment'] );
/**
* Filter the post types that receive the ACS SEO Plus meta box.
*
* @param string[] $types Post type names.
*/
return (array) apply_filters( 'acsseo_supported_post_types', array_values( $types ) );
}
/**
* Register the meta box on every supported post type.
*
* @return void
*/
public function add_meta_box() {
foreach ( self::supported_post_types() as $post_type ) {
add_meta_box(
'acsseo_meta_box',
__( 'SEO', 'acs-seo-plus' ),
array( $this, 'render_meta_box' ),
$post_type,
'normal',
'high'
);
}
}
/**
* Render the meta box.
*
* @param WP_Post $post Current post.
* @return void
*/
public function render_meta_box( $post ) {
wp_nonce_field( 'acsseo_save_meta', 'acsseo_meta_nonce' );
$title = (string) get_post_meta( $post->ID, '_acsseo_title', true );
$description = (string) get_post_meta( $post->ID, '_acsseo_description', true );
$canonical = (string) get_post_meta( $post->ID, '_acsseo_canonical', true );
$noindex = (string) get_post_meta( $post->ID, '_acsseo_noindex', true );
$nofollow = (string) get_post_meta( $post->ID, '_acsseo_nofollow', true );
$schema = (string) get_post_meta( $post->ID, '_acsseo_schema', true );
$social = (string) get_post_meta( $post->ID, '_acsseo_social_image', true );
?>
post_type ) {
return;
}
$title = isset( $_POST['acsseo_title'] )
? sanitize_text_field( wp_unslash( $_POST['acsseo_title'] ) )
: '';
$this->update_or_delete( $post_id, '_acsseo_title', $title );
$description = isset( $_POST['acsseo_description'] )
? sanitize_textarea_field( wp_unslash( $_POST['acsseo_description'] ) )
: '';
$this->update_or_delete( $post_id, '_acsseo_description', $description );
$canonical = isset( $_POST['acsseo_canonical'] )
? esc_url_raw( wp_unslash( $_POST['acsseo_canonical'] ) )
: '';
$this->update_or_delete( $post_id, '_acsseo_canonical', $canonical );
$social = isset( $_POST['acsseo_social_image'] )
? esc_url_raw( wp_unslash( $_POST['acsseo_social_image'] ) )
: '';
$this->update_or_delete( $post_id, '_acsseo_social_image', $social );
foreach ( array( 'noindex', 'nofollow', 'schema' ) as $flag ) {
$value = ( isset( $_POST[ 'acsseo_' . $flag ] ) && '1' === $_POST[ 'acsseo_' . $flag ] ) ? '1' : '';
$this->update_or_delete( $post_id, '_acsseo_' . $flag, $value );
}
}
/**
* Store a value, or remove the meta row when the value is empty.
*
* @param int $post_id Post ID.
* @param string $key Meta key.
* @param string $value Sanitized value.
* @return void
*/
private function update_or_delete( $post_id, $key, $value ) {
if ( '' === $value ) {
delete_post_meta( $post_id, $key );
return;
}
update_post_meta( $post_id, $key, $value );
}
/**
* Replace the document title with the stored SEO title.
*
* Returning a non empty string here short circuits wp_get_document_title(),
* which is why the plugin never prints a second element.
*
* @param string $title Title supplied so far.
* @return string
*/
public function filter_document_title( $title ) {
if ( ! ACSSEO_Settings::get( 'output_title' ) ) {
return $title;
}
if ( is_front_page() ) {
$home_title = (string) ACSSEO_Settings::get( 'home_title', '' );
return '' !== $home_title ? $home_title : $title;
}
if ( ! is_singular() ) {
return $title;
}
$post_id = get_queried_object_id();
if ( ! $post_id ) {
return $title;
}
$custom = (string) get_post_meta( $post_id, '_acsseo_title', true );
return '' !== $custom ? $custom : $title;
}
/**
* Resolve the description used for meta description and social tags.
*
* @param int $post_id Post ID.
* @return string
*/
public static function resolve_description( $post_id ) {
$description = (string) get_post_meta( $post_id, '_acsseo_description', true );
if ( '' !== $description ) {
return $description;
}
$post = get_post( $post_id );
if ( ! $post instanceof WP_Post ) {
return '';
}
$excerpt = has_excerpt( $post ) ? $post->post_excerpt : $post->post_content;
$excerpt = wp_strip_all_tags( strip_shortcodes( $excerpt ), true );
if ( '' === trim( $excerpt ) ) {
return '';
}
if ( mb_strlen( $excerpt ) > 160 ) {
$excerpt = mb_substr( $excerpt, 0, 157 ) . '...';
}
return $excerpt;
}
/**
* Print meta description, robots and canonical tags.
*
* @return void
*/
public function output_head() {
if ( is_front_page() && ACSSEO_Settings::get( 'output_description' ) ) {
$home_description = (string) ACSSEO_Settings::get( 'home_description', '' );
if ( '' !== $home_description ) {
echo '' . "\n";
if ( ! is_singular() ) {
return;
}
$this->output_robots_and_canonical( get_queried_object_id() );
return;
}
}
if ( ! is_singular() ) {
return;
}
$post_id = get_queried_object_id();
if ( ! $post_id ) {
return;
}
if ( ACSSEO_Settings::get( 'output_description' ) ) {
$description = self::resolve_description( $post_id );
if ( '' !== $description ) {
echo '' . "\n";
}
}
$this->output_robots_and_canonical( $post_id );
}
/**
* Print the robots and canonical tags of one entry.
*
* @param int $post_id Post ID.
* @return void
*/
private function output_robots_and_canonical( $post_id ) {
if ( ! $post_id ) {
return;
}
if ( ACSSEO_Settings::get( 'output_robots' ) ) {
$directives = array();
if ( '1' === (string) get_post_meta( $post_id, '_acsseo_noindex', true ) ) {
$directives[] = 'noindex';
}
if ( '1' === (string) get_post_meta( $post_id, '_acsseo_nofollow', true ) ) {
$directives[] = 'nofollow';
}
if ( ! empty( $directives ) ) {
echo '' . "\n";
}
}
if ( ACSSEO_Settings::get( 'output_canonical' ) ) {
$canonical = (string) get_post_meta( $post_id, '_acsseo_canonical', true );
if ( '' !== $canonical ) {
// Drop the core tag so the page carries exactly one canonical URL.
remove_action( 'wp_head', 'rel_canonical' );
echo '' . "\n";
}
}
}
}