robotstxt-og/includes/class-robotstxt-og-meta-box.php
2026-03-28 07:59:52 +00:00

221 lines
5.7 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 {
/**
* 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 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 : '';
?>
<table class="form-table" role="presentation">
<tbody>
<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>
</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 );
}
}
}