383 lines
11 KiB
PHP
383 lines
11 KiB
PHP
<?php
|
|
/**
|
|
* Meta Box Class
|
|
*
|
|
* Provides per-post Open Graph and social media customization fields
|
|
* in the WordPress post editor.
|
|
*
|
|
* @package ROBOTSTXT_OG
|
|
* @since 1.2.0
|
|
*/
|
|
|
|
if ( ! defined( 'ABSPATH' ) ) {
|
|
exit; // Exit if accessed directly.
|
|
}
|
|
|
|
/**
|
|
* Class Robotstxt_OG_Meta_Box
|
|
*
|
|
* Registers and handles the Open Graph meta box in the post editor,
|
|
* and registers the underlying post meta for REST API access.
|
|
*
|
|
* @since 1.2.0
|
|
*/
|
|
class Robotstxt_OG_Meta_Box {
|
|
|
|
/**
|
|
* Allowed og:type override values (the auto-derived default is stored as empty).
|
|
*
|
|
* @since 1.2.0
|
|
* @var string[]
|
|
*/
|
|
public const ALLOWED_OG_TYPES = array( 'website', 'article' );
|
|
|
|
/**
|
|
* Sanitize an og:type override value to a safe value or empty string.
|
|
*
|
|
* @since 1.2.0
|
|
*
|
|
* @param mixed $value Raw value.
|
|
* @return string
|
|
*/
|
|
public static function sanitize_og_type( $value ): string {
|
|
$value = is_string( $value ) ? $value : '';
|
|
|
|
return in_array( $value, self::ALLOWED_OG_TYPES, true ) ? $value : '';
|
|
}
|
|
|
|
|
|
/**
|
|
* Initialize hooks.
|
|
*
|
|
* @since 1.2.0
|
|
*
|
|
* @return void
|
|
*/
|
|
public function init(): void {
|
|
// Register post meta for REST API access (block editor).
|
|
add_action( 'init', array( $this, 'register_post_meta' ) );
|
|
|
|
// Meta box UI is admin-only.
|
|
if ( is_admin() ) {
|
|
add_action( 'add_meta_boxes', array( $this, 'register_meta_box' ) );
|
|
add_action( 'save_post', array( $this, 'save_meta_box' ), 10, 2 );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Register custom post meta for all public post types.
|
|
*
|
|
* Exposes _og_title and _og_description via the REST API so the
|
|
* block editor can read and write them via useEntityProp.
|
|
*
|
|
* @since 1.2.0
|
|
*
|
|
* @return void
|
|
*/
|
|
public function register_post_meta(): void {
|
|
$post_types = get_post_types( array( 'public' => true ), 'names' );
|
|
|
|
foreach ( $post_types as $post_type ) {
|
|
register_post_meta(
|
|
$post_type,
|
|
'_og_title',
|
|
array(
|
|
'type' => 'string',
|
|
'single' => true,
|
|
'sanitize_callback' => 'sanitize_text_field',
|
|
'auth_callback' => static function () {
|
|
return current_user_can( 'edit_posts' );
|
|
},
|
|
'show_in_rest' => true,
|
|
)
|
|
);
|
|
|
|
register_post_meta(
|
|
$post_type,
|
|
'_og_description',
|
|
array(
|
|
'type' => 'string',
|
|
'single' => true,
|
|
'sanitize_callback' => 'sanitize_textarea_field',
|
|
'auth_callback' => static function () {
|
|
return current_user_can( 'edit_posts' );
|
|
},
|
|
'show_in_rest' => true,
|
|
)
|
|
);
|
|
|
|
register_post_meta(
|
|
$post_type,
|
|
'_og_image_alt',
|
|
array(
|
|
'type' => 'string',
|
|
'single' => true,
|
|
'sanitize_callback' => 'sanitize_text_field',
|
|
'auth_callback' => static function () {
|
|
return current_user_can( 'edit_posts' );
|
|
},
|
|
'show_in_rest' => true,
|
|
)
|
|
);
|
|
|
|
register_post_meta(
|
|
$post_type,
|
|
'_og_type',
|
|
array(
|
|
'type' => 'string',
|
|
'single' => true,
|
|
'sanitize_callback' => array( __CLASS__, 'sanitize_og_type' ),
|
|
'auth_callback' => static function () {
|
|
return current_user_can( 'edit_posts' );
|
|
},
|
|
'show_in_rest' => true,
|
|
)
|
|
);
|
|
|
|
register_post_meta(
|
|
$post_type,
|
|
'_twitter_creator',
|
|
array(
|
|
'type' => 'string',
|
|
'single' => true,
|
|
'sanitize_callback' => 'sanitize_text_field',
|
|
'auth_callback' => static function () {
|
|
return current_user_can( 'edit_posts' );
|
|
},
|
|
'show_in_rest' => true,
|
|
)
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Register the meta box for all public post types.
|
|
*
|
|
* @since 1.2.0
|
|
*
|
|
* @return void
|
|
*/
|
|
public function register_meta_box(): void {
|
|
$post_types = get_post_types( array( 'public' => true ), 'names' );
|
|
|
|
foreach ( $post_types as $post_type ) {
|
|
add_meta_box(
|
|
'robotstxt-og-meta-box',
|
|
__( 'Open Graph / Social Media', 'robotstxt-og' ),
|
|
array( $this, 'render_meta_box' ),
|
|
$post_type,
|
|
'normal',
|
|
'default'
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Render the meta box HTML.
|
|
*
|
|
* @since 1.2.0
|
|
*
|
|
* @param WP_Post $post The current post object.
|
|
* @return void
|
|
*/
|
|
public function render_meta_box( WP_Post $post ): void {
|
|
wp_nonce_field( 'robotstxt_og_meta_box', 'robotstxt_og_meta_box_nonce' );
|
|
|
|
$raw_title = get_post_meta( $post->ID, '_og_title', true );
|
|
$og_title = is_string( $raw_title ) ? $raw_title : '';
|
|
$raw_desc = get_post_meta( $post->ID, '_og_description', true );
|
|
$og_description = is_string( $raw_desc ) ? $raw_desc : '';
|
|
$raw_alt = get_post_meta( $post->ID, '_og_image_alt', true );
|
|
$og_image_alt = is_string( $raw_alt ) ? $raw_alt : '';
|
|
$raw_type = get_post_meta( $post->ID, '_og_type', true );
|
|
$og_type = self::sanitize_og_type( $raw_type );
|
|
$raw_creator = get_post_meta( $post->ID, '_twitter_creator', true );
|
|
$twitter_creator = is_string( $raw_creator ) ? $raw_creator : '';
|
|
?>
|
|
<table class="form-table" role="presentation">
|
|
<tbody>
|
|
<tr>
|
|
<th scope="row">
|
|
<label for="robotstxt_og_type">
|
|
<?php esc_html_e( 'Open Graph Type', 'robotstxt-og' ); ?>
|
|
</label>
|
|
</th>
|
|
<td>
|
|
<select id="robotstxt_og_type" name="robotstxt_og_type">
|
|
<option value="" <?php selected( $og_type, '' ); ?>>
|
|
<?php esc_html_e( 'Default (auto: article for posts, website otherwise)', 'robotstxt-og' ); ?>
|
|
</option>
|
|
<option value="website" <?php selected( $og_type, 'website' ); ?>>
|
|
<?php esc_html_e( 'website', 'robotstxt-og' ); ?>
|
|
</option>
|
|
<option value="article" <?php selected( $og_type, 'article' ); ?>>
|
|
<?php esc_html_e( 'article', 'robotstxt-og' ); ?>
|
|
</option>
|
|
</select>
|
|
<p class="description">
|
|
<?php esc_html_e( 'Overrides og:type for this post. Selecting "article" also emits the article:* meta tags (published/modified time, section, tags).', 'robotstxt-og' ); ?>
|
|
</p>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<th scope="row">
|
|
<label for="robotstxt_og_twitter_creator">
|
|
<?php esc_html_e( 'Twitter Author Handle', 'robotstxt-og' ); ?>
|
|
</label>
|
|
</th>
|
|
<td>
|
|
<input
|
|
type="text"
|
|
id="robotstxt_og_twitter_creator"
|
|
name="robotstxt_og_twitter_creator"
|
|
value="<?php echo esc_attr( $twitter_creator ); ?>"
|
|
class="large-text"
|
|
placeholder="@username"
|
|
/>
|
|
<p class="description">
|
|
<?php esc_html_e( 'Twitter/X handle of the post author, emitted as twitter:creator. Leave blank to omit.', 'robotstxt-og' ); ?>
|
|
</p>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<th scope="row">
|
|
<label for="robotstxt_og_title">
|
|
<?php esc_html_e( 'Custom Title', 'robotstxt-og' ); ?>
|
|
</label>
|
|
</th>
|
|
<td>
|
|
<input
|
|
type="text"
|
|
id="robotstxt_og_title"
|
|
name="robotstxt_og_title"
|
|
value="<?php echo esc_attr( $og_title ); ?>"
|
|
class="large-text"
|
|
/>
|
|
<p class="description">
|
|
<?php esc_html_e( 'Overrides the default og:title for this post. Leave blank to use the post title automatically.', 'robotstxt-og' ); ?>
|
|
</p>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<th scope="row">
|
|
<label for="robotstxt_og_description">
|
|
<?php esc_html_e( 'Custom Description', 'robotstxt-og' ); ?>
|
|
</label>
|
|
</th>
|
|
<td>
|
|
<textarea
|
|
id="robotstxt_og_description"
|
|
name="robotstxt_og_description"
|
|
class="large-text"
|
|
rows="3"
|
|
><?php echo esc_textarea( $og_description ); ?></textarea>
|
|
<p class="description">
|
|
<?php esc_html_e( 'Overrides the default og:description for this post. Leave blank to use the excerpt automatically.', 'robotstxt-og' ); ?>
|
|
</p>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<th scope="row">
|
|
<label for="robotstxt_og_image_alt">
|
|
<?php esc_html_e( 'Custom OG Image Alt', 'robotstxt-og' ); ?>
|
|
</label>
|
|
</th>
|
|
<td>
|
|
<input
|
|
type="text"
|
|
id="robotstxt_og_image_alt"
|
|
name="robotstxt_og_image_alt"
|
|
value="<?php echo esc_attr( $og_image_alt ); ?>"
|
|
class="large-text"
|
|
/>
|
|
<p class="description">
|
|
<?php esc_html_e( 'Alt text for the og:image of this post. Leave blank to use the featured image alt text from the media library.', 'robotstxt-og' ); ?>
|
|
</p>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
<?php
|
|
}
|
|
|
|
/**
|
|
* Save meta box values on post save.
|
|
*
|
|
* @since 1.2.0
|
|
*
|
|
* @param int $post_id The post ID.
|
|
* @param WP_Post $post The post object.
|
|
* @return void
|
|
*/
|
|
public function save_meta_box( int $post_id, WP_Post $post ): void {
|
|
// Skip autosaves and revisions.
|
|
if ( wp_is_post_autosave( $post_id ) || wp_is_post_revision( $post_id ) ) {
|
|
return;
|
|
}
|
|
|
|
// Verify nonce.
|
|
$nonce_raw = filter_input( INPUT_POST, 'robotstxt_og_meta_box_nonce', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
|
|
$nonce = $nonce_raw ? sanitize_text_field( wp_unslash( $nonce_raw ) ) : '';
|
|
|
|
if ( ! wp_verify_nonce( $nonce, 'robotstxt_og_meta_box' ) ) {
|
|
return;
|
|
}
|
|
|
|
// Check permissions.
|
|
$post_type_obj = get_post_type_object( $post->post_type );
|
|
|
|
if ( ! $post_type_obj || ! current_user_can( $post_type_obj->cap->edit_post, $post_id ) ) {
|
|
return;
|
|
}
|
|
|
|
// Save og:title.
|
|
$og_title_raw = filter_input( INPUT_POST, 'robotstxt_og_title', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
|
|
$og_title = $og_title_raw ? sanitize_text_field( wp_unslash( $og_title_raw ) ) : '';
|
|
|
|
if ( empty( $og_title ) ) {
|
|
delete_post_meta( $post_id, '_og_title' );
|
|
} else {
|
|
update_post_meta( $post_id, '_og_title', $og_title );
|
|
}
|
|
|
|
// Save og:description.
|
|
$og_desc_raw = filter_input( INPUT_POST, 'robotstxt_og_description', FILTER_UNSAFE_RAW );
|
|
$og_desc = $og_desc_raw ? sanitize_textarea_field( wp_unslash( $og_desc_raw ) ) : '';
|
|
|
|
if ( empty( $og_desc ) ) {
|
|
delete_post_meta( $post_id, '_og_description' );
|
|
} else {
|
|
update_post_meta( $post_id, '_og_description', $og_desc );
|
|
}
|
|
|
|
// Save og:image:alt.
|
|
$og_alt_raw = filter_input( INPUT_POST, 'robotstxt_og_image_alt', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
|
|
$og_alt = $og_alt_raw ? sanitize_text_field( wp_unslash( $og_alt_raw ) ) : '';
|
|
|
|
if ( empty( $og_alt ) ) {
|
|
delete_post_meta( $post_id, '_og_image_alt' );
|
|
} else {
|
|
update_post_meta( $post_id, '_og_image_alt', $og_alt );
|
|
}
|
|
|
|
// Save og:type override (whitelisted).
|
|
$og_type_raw = filter_input( INPUT_POST, 'robotstxt_og_type', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
|
|
$og_type = self::sanitize_og_type( $og_type_raw ? wp_unslash( $og_type_raw ) : '' );
|
|
|
|
if ( '' === $og_type ) {
|
|
delete_post_meta( $post_id, '_og_type' );
|
|
} else {
|
|
update_post_meta( $post_id, '_og_type', $og_type );
|
|
}
|
|
|
|
// Save twitter:creator handle.
|
|
$creator_raw = filter_input( INPUT_POST, 'robotstxt_og_twitter_creator', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
|
|
$creator = $creator_raw ? sanitize_text_field( wp_unslash( $creator_raw ) ) : '';
|
|
|
|
if ( '' === $creator ) {
|
|
delete_post_meta( $post_id, '_twitter_creator' );
|
|
} else {
|
|
update_post_meta( $post_id, '_twitter_creator', $creator );
|
|
}
|
|
}
|
|
}
|