v1.1.0
This commit is contained in:
parent
9b856d1c3d
commit
ddaaff71ad
23 changed files with 879 additions and 798 deletions
|
|
@ -87,14 +87,14 @@ class Robotstxt_OG_CLI extends WP_CLI_Command {
|
|||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @param array $args Positional arguments.
|
||||
* @param array $assoc_args Associative arguments.
|
||||
* @param array<int, string> $args Positional arguments.
|
||||
* @param array<string, string|bool> $assoc_args Associative arguments.
|
||||
* @return void
|
||||
*/
|
||||
public function resolve( array $args, array $assoc_args ): void {
|
||||
$all = isset( $assoc_args['all'] );
|
||||
$dry_run = isset( $assoc_args['dry-run'] );
|
||||
$post_type = isset( $assoc_args['post-type'] ) ? $assoc_args['post-type'] : 'any';
|
||||
$post_type = ( isset( $assoc_args['post-type'] ) && is_string( $assoc_args['post-type'] ) ) ? $assoc_args['post-type'] : 'any';
|
||||
|
||||
if ( $all ) {
|
||||
$this->resolve_all( $dry_run, $post_type );
|
||||
|
|
@ -132,8 +132,8 @@ class Robotstxt_OG_CLI extends WP_CLI_Command {
|
|||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @param array $args Positional arguments.
|
||||
* @param array $assoc_args Associative arguments.
|
||||
* @param array<int, string> $args Positional arguments.
|
||||
* @param array<string, string|bool> $assoc_args Associative arguments.
|
||||
* @return void
|
||||
*/
|
||||
public function clear_cache( array $args, array $assoc_args ): void {
|
||||
|
|
@ -320,7 +320,7 @@ class Robotstxt_OG_CLI extends WP_CLI_Command {
|
|||
|
||||
if ( $dry_run ) {
|
||||
/* translators: 1: post ID, 2: cached URL or 'none' */
|
||||
WP_CLI::line( sprintf( __( 'Post %1$d cached URL: %2$s (dry-run, no changes made)', 'robotstxt-og' ), $post_id, ! empty( $cached ) ? $cached : 'none' ) );
|
||||
WP_CLI::line( sprintf( __( 'Post %1$d cached URL: %2$s (dry-run, no changes made)', 'robotstxt-og' ), $post_id, ( is_string( $cached ) && ! empty( $cached ) ) ? $cached : 'none' ) );
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -136,6 +136,10 @@ class Robotstxt_OG_Image_Fallback {
|
|||
add_action( 'updated_post_meta', array( $this, 'handle_thumbnail_change' ), 10, 4 );
|
||||
add_action( 'deleted_post_meta', array( $this, 'handle_thumbnail_change' ), 10, 4 );
|
||||
|
||||
// Register GDPR privacy data handlers.
|
||||
add_filter( 'wp_privacy_personal_data_exporters', array( $this, 'register_privacy_exporter' ) );
|
||||
add_filter( 'wp_privacy_personal_data_erasers', array( $this, 'register_privacy_eraser' ) );
|
||||
|
||||
// Register activation and deactivation hooks.
|
||||
register_activation_hook( ROBOTSTXT_OG_PATH . 'robotstxt-og.php', array( $this, 'activate' ) );
|
||||
register_deactivation_hook( ROBOTSTXT_OG_PATH . 'robotstxt-og.php', array( $this, 'deactivate' ) );
|
||||
|
|
@ -209,13 +213,13 @@ class Robotstxt_OG_Image_Fallback {
|
|||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @param int $meta_id ID of the meta data entry.
|
||||
* @param int $post_id Post ID.
|
||||
* @param string $meta_key Meta key being updated.
|
||||
* @param mixed $meta_value New meta value (unused).
|
||||
* @param int|int[] $meta_id ID or array of IDs of the meta data entry.
|
||||
* @param int $post_id Post ID.
|
||||
* @param string $meta_key Meta key being updated.
|
||||
* @param mixed $meta_value New meta value (unused).
|
||||
* @return void
|
||||
*/
|
||||
public function handle_thumbnail_change( int $meta_id, int $post_id, string $meta_key, $meta_value = null ): void { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
|
||||
public function handle_thumbnail_change( int|array $meta_id, int $post_id, string $meta_key, $meta_value = null ): void { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
|
||||
if ( '_thumbnail_id' !== $meta_key ) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -223,6 +227,171 @@ class Robotstxt_OG_Image_Fallback {
|
|||
$this->resolver->clear_cache( $post_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the personal data exporter.
|
||||
*
|
||||
* @since 1.0.3
|
||||
*
|
||||
* @param array<int, array<string, mixed>> $exporters List of exporters.
|
||||
* @return array<int, array<string, mixed>> Updated exporters list.
|
||||
*/
|
||||
public function register_privacy_exporter( array $exporters ): array {
|
||||
$exporters[] = array(
|
||||
'exporter_friendly_name' => __( 'OpenGraph Custom Post Data', 'robotstxt-og' ),
|
||||
'callback' => array( $this, 'export_privacy_data' ),
|
||||
);
|
||||
return $exporters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the personal data eraser.
|
||||
*
|
||||
* @since 1.0.3
|
||||
*
|
||||
* @param array<int, array<string, mixed>> $erasers List of erasers.
|
||||
* @return array<int, array<string, mixed>> Updated erasers list.
|
||||
*/
|
||||
public function register_privacy_eraser( array $erasers ): array {
|
||||
$erasers[] = array(
|
||||
'eraser_friendly_name' => __( 'OpenGraph Custom Post Data', 'robotstxt-og' ),
|
||||
'callback' => array( $this, 'erase_privacy_data' ),
|
||||
);
|
||||
return $erasers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Export personal data stored by this plugin for a given email address.
|
||||
*
|
||||
* Exports the custom og:title and og:description stored in post meta for
|
||||
* all posts authored by the user with the given email address.
|
||||
*
|
||||
* @since 1.0.3
|
||||
*
|
||||
* @param string $email_address User email address.
|
||||
* @param int $page Pagination page (1-based).
|
||||
* @return array{data: array<int, array<string, mixed>>, done: bool} Export result.
|
||||
*/
|
||||
public function export_privacy_data( string $email_address, int $page = 1 ): array {
|
||||
$user = get_user_by( 'email', $email_address );
|
||||
|
||||
if ( ! $user ) {
|
||||
return array(
|
||||
'data' => array(),
|
||||
'done' => true,
|
||||
);
|
||||
}
|
||||
|
||||
$posts = get_posts(
|
||||
array(
|
||||
'author' => $user->ID,
|
||||
'post_type' => 'any',
|
||||
'post_status' => 'any',
|
||||
'posts_per_page' => 100,
|
||||
'paged' => $page,
|
||||
'fields' => 'ids',
|
||||
)
|
||||
);
|
||||
|
||||
$data = array();
|
||||
|
||||
foreach ( $posts as $post_id ) {
|
||||
$og_title = get_post_meta( $post_id, '_og_title', true );
|
||||
$og_desc = get_post_meta( $post_id, '_og_description', true );
|
||||
|
||||
if ( empty( $og_title ) && empty( $og_desc ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$item_data = array();
|
||||
|
||||
if ( ! empty( $og_title ) && is_string( $og_title ) ) {
|
||||
$item_data[] = array(
|
||||
'name' => __( 'Custom OG Title', 'robotstxt-og' ),
|
||||
'value' => $og_title,
|
||||
);
|
||||
}
|
||||
|
||||
if ( ! empty( $og_desc ) && is_string( $og_desc ) ) {
|
||||
$item_data[] = array(
|
||||
'name' => __( 'Custom OG Description', 'robotstxt-og' ),
|
||||
'value' => $og_desc,
|
||||
);
|
||||
}
|
||||
|
||||
if ( ! empty( $item_data ) ) {
|
||||
$data[] = array(
|
||||
'group_id' => 'robotstxt-og-post-meta',
|
||||
'group_label' => __( 'OpenGraph Post Meta', 'robotstxt-og' ),
|
||||
'item_id' => 'post-' . $post_id,
|
||||
'data' => $item_data,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$done = count( $posts ) < 100;
|
||||
|
||||
return array(
|
||||
'data' => $data,
|
||||
'done' => $done,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Erase personal data stored by this plugin for a given email address.
|
||||
*
|
||||
* Removes custom og:title and og:description from all posts authored by
|
||||
* the user with the given email address.
|
||||
*
|
||||
* @since 1.0.3
|
||||
*
|
||||
* @param string $email_address User email address.
|
||||
* @param int $page Pagination page (1-based).
|
||||
* @return array{items_removed: int, items_retained: int, messages: string[], done: bool} Erase result.
|
||||
*/
|
||||
public function erase_privacy_data( string $email_address, int $page = 1 ): array {
|
||||
$user = get_user_by( 'email', $email_address );
|
||||
|
||||
if ( ! $user ) {
|
||||
return array(
|
||||
'items_removed' => 0,
|
||||
'items_retained' => 0,
|
||||
'messages' => array(),
|
||||
'done' => true,
|
||||
);
|
||||
}
|
||||
|
||||
$posts = get_posts(
|
||||
array(
|
||||
'author' => $user->ID,
|
||||
'post_type' => 'any',
|
||||
'post_status' => 'any',
|
||||
'posts_per_page' => 100,
|
||||
'paged' => $page,
|
||||
'fields' => 'ids',
|
||||
)
|
||||
);
|
||||
|
||||
$items_removed = 0;
|
||||
|
||||
foreach ( $posts as $post_id ) {
|
||||
$deleted_title = delete_post_meta( $post_id, '_og_title' );
|
||||
$deleted_desc = delete_post_meta( $post_id, '_og_description' );
|
||||
|
||||
if ( $deleted_title || $deleted_desc ) {
|
||||
++$items_removed;
|
||||
}
|
||||
}
|
||||
|
||||
$done = count( $posts ) < 100;
|
||||
|
||||
return array(
|
||||
'items_removed' => $items_removed,
|
||||
'items_retained' => 0,
|
||||
'messages' => array(),
|
||||
'done' => $done,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get image resolver instance.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ class Robotstxt_OG_Image_Resolver {
|
|||
// Check postmeta cache first.
|
||||
$cached_url = get_post_meta( $post_id, '_og_image_fallback_url', true );
|
||||
|
||||
if ( ! empty( $cached_url ) && $this->is_valid_url( $cached_url ) ) {
|
||||
if ( is_string( $cached_url ) && ! empty( $cached_url ) && $this->is_valid_url( $cached_url ) ) {
|
||||
$this->log(
|
||||
'cache_hit',
|
||||
array(
|
||||
|
|
@ -103,7 +103,8 @@ class Robotstxt_OG_Image_Resolver {
|
|||
}
|
||||
|
||||
// Detect file extension.
|
||||
$path_info = pathinfo( wp_parse_url( $image_url, PHP_URL_PATH ) );
|
||||
$url_path = wp_parse_url( $image_url, PHP_URL_PATH );
|
||||
$path_info = pathinfo( is_string( $url_path ) ? $url_path : '' );
|
||||
$extension = isset( $path_info['extension'] ) ? strtolower( $path_info['extension'] ) : '';
|
||||
|
||||
// If already a compatible format, save and return.
|
||||
|
|
@ -191,9 +192,14 @@ class Robotstxt_OG_Image_Resolver {
|
|||
// Strip query string for URL manipulation.
|
||||
$clean_url = strtok( $image_url, '?' );
|
||||
|
||||
if ( ! is_string( $clean_url ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Parse URL and get base path without extension.
|
||||
$path_info = pathinfo( wp_parse_url( $clean_url, PHP_URL_PATH ) );
|
||||
$filename = $path_info['filename'] ?? '';
|
||||
$url_path = wp_parse_url( $clean_url, PHP_URL_PATH );
|
||||
$path_info = pathinfo( is_string( $url_path ) ? $url_path : '' );
|
||||
$filename = $path_info['filename'];
|
||||
$dir = $path_info['dirname'] ?? '';
|
||||
|
||||
if ( empty( $filename ) ) {
|
||||
|
|
@ -224,10 +230,52 @@ class Robotstxt_OG_Image_Resolver {
|
|||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a URL is safe to make server-side HTTP requests to.
|
||||
*
|
||||
* Rejects non-http(s) schemes and URLs that resolve to private or
|
||||
* reserved IP ranges to prevent Server-Side Request Forgery (SSRF).
|
||||
*
|
||||
* @since 1.0.3
|
||||
*
|
||||
* @param string $url URL to validate.
|
||||
* @return bool True if the URL is safe to request.
|
||||
*/
|
||||
private function is_safe_url( string $url ): bool {
|
||||
$scheme = wp_parse_url( $url, PHP_URL_SCHEME );
|
||||
if ( ! in_array( $scheme, array( 'http', 'https' ), true ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$host = wp_parse_url( $url, PHP_URL_HOST );
|
||||
if ( ! is_string( $host ) || '' === $host ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Strip IPv6 brackets for validation.
|
||||
$host_bare = trim( $host, '[]' );
|
||||
|
||||
// If the host is already an IP address, validate it directly.
|
||||
if ( filter_var( $host_bare, FILTER_VALIDATE_IP ) !== false ) {
|
||||
return (bool) filter_var( $host_bare, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE );
|
||||
}
|
||||
|
||||
// Resolve hostname to IP and validate it is not private/reserved.
|
||||
$resolved = gethostbyname( $host_bare );
|
||||
|
||||
// gethostbyname() returns the original string on failure.
|
||||
if ( $resolved === $host_bare ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (bool) filter_var( $resolved, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a URL exists using HTTP HEAD request.
|
||||
*
|
||||
* Uses negative caching to avoid repeated requests for non-existent URLs.
|
||||
* URLs resolving to private or reserved IP ranges are rejected (SSRF protection).
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
|
|
@ -235,6 +283,12 @@ class Robotstxt_OG_Image_Resolver {
|
|||
* @return bool True if URL returns 200-299 status code.
|
||||
*/
|
||||
private function url_exists( string $url ): bool {
|
||||
// Reject URLs that resolve to private/reserved IP ranges (SSRF protection).
|
||||
if ( ! $this->is_safe_url( $url ) ) {
|
||||
$this->log( 'ssrf_blocked', array( 'url' => $url ) );
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check negative cache to avoid repeated failed requests.
|
||||
$cache_key = self::NEGATIVE_CACHE_PREFIX . md5( $url );
|
||||
|
||||
|
|
@ -333,7 +387,8 @@ class Robotstxt_OG_Image_Resolver {
|
|||
* @return string Compatible image URL or empty string.
|
||||
*/
|
||||
private function get_global_fallback(): string {
|
||||
$fallback_url = (string) get_option( 'robotstxt_og_fallback_image', '' );
|
||||
$option_value = get_option( 'robotstxt_og_fallback_image', '' );
|
||||
$fallback_url = is_string( $option_value ) ? $option_value : '';
|
||||
|
||||
if ( empty( $fallback_url ) || ! $this->is_valid_url( $fallback_url ) ) {
|
||||
return '';
|
||||
|
|
@ -354,7 +409,8 @@ class Robotstxt_OG_Image_Resolver {
|
|||
* @return string Compatible image URL or empty string.
|
||||
*/
|
||||
public function get_homepage_image(): string {
|
||||
$image_url = (string) get_option( 'robotstxt_og_homepage_image', '' );
|
||||
$option_value = get_option( 'robotstxt_og_homepage_image', '' );
|
||||
$image_url = is_string( $option_value ) ? $option_value : '';
|
||||
|
||||
if ( ! empty( $image_url ) && $this->is_valid_url( $image_url ) ) {
|
||||
return esc_url_raw( $image_url );
|
||||
|
|
@ -378,7 +434,8 @@ class Robotstxt_OG_Image_Resolver {
|
|||
* @return string Compatible image URL or empty string.
|
||||
*/
|
||||
public function ensure_compatible_format( string $image_url ): string {
|
||||
$path_info = pathinfo( wp_parse_url( $image_url, PHP_URL_PATH ) );
|
||||
$url_path = wp_parse_url( $image_url, PHP_URL_PATH );
|
||||
$path_info = pathinfo( is_string( $url_path ) ? $url_path : '' );
|
||||
$extension = isset( $path_info['extension'] ) ? strtolower( $path_info['extension'] ) : '';
|
||||
|
||||
// Already a compatible format.
|
||||
|
|
@ -459,7 +516,7 @@ class Robotstxt_OG_Image_Resolver {
|
|||
// Check term meta cache first.
|
||||
$cached_url = get_term_meta( $term_id, '_og_image_fallback_url', true );
|
||||
|
||||
if ( ! empty( $cached_url ) && $this->is_valid_url( $cached_url ) ) {
|
||||
if ( is_string( $cached_url ) && ! empty( $cached_url ) && $this->is_valid_url( $cached_url ) ) {
|
||||
$this->log(
|
||||
'taxonomy_cache_hit',
|
||||
array(
|
||||
|
|
@ -504,7 +561,8 @@ class Robotstxt_OG_Image_Resolver {
|
|||
}
|
||||
|
||||
// Detect file extension and resolve if needed.
|
||||
$path_info = pathinfo( wp_parse_url( $image_url, PHP_URL_PATH ) );
|
||||
$url_path = wp_parse_url( $image_url, PHP_URL_PATH );
|
||||
$path_info = pathinfo( is_string( $url_path ) ? $url_path : '' );
|
||||
$extension = isset( $path_info['extension'] ) ? strtolower( $path_info['extension'] ) : '';
|
||||
|
||||
if ( in_array( $extension, array( 'jpg', 'jpeg', 'png' ), true ) ) {
|
||||
|
|
@ -555,8 +613,8 @@ class Robotstxt_OG_Image_Resolver {
|
|||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @param string $event Event name (e.g., 'cache_hit', 'head_request_error').
|
||||
* @param array $context Additional context data.
|
||||
* @param string $event Event name (e.g., 'cache_hit', 'head_request_error').
|
||||
* @param array<string, mixed> $context Additional context data.
|
||||
* @return void
|
||||
*/
|
||||
private function log( string $event, array $context = array() ): void {
|
||||
|
|
|
|||
|
|
@ -118,8 +118,10 @@ class Robotstxt_OG_Meta_Box {
|
|||
public function render_meta_box( WP_Post $post ): void {
|
||||
wp_nonce_field( 'robotstxt_og_meta_box', 'robotstxt_og_meta_box_nonce' );
|
||||
|
||||
$og_title = (string) get_post_meta( $post->ID, '_og_title', true );
|
||||
$og_description = (string) get_post_meta( $post->ID, '_og_description', true );
|
||||
$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">
|
||||
|
|
@ -182,7 +184,7 @@ class Robotstxt_OG_Meta_Box {
|
|||
}
|
||||
|
||||
// Verify nonce.
|
||||
$nonce_raw = filter_input( INPUT_POST, 'robotstxt_og_meta_box_nonce', FILTER_SANITIZE_SPECIAL_CHARS );
|
||||
$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' ) ) {
|
||||
|
|
@ -197,7 +199,7 @@ class Robotstxt_OG_Meta_Box {
|
|||
}
|
||||
|
||||
// Save og:title.
|
||||
$og_title_raw = filter_input( INPUT_POST, 'robotstxt_og_title', FILTER_SANITIZE_SPECIAL_CHARS );
|
||||
$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 ) ) {
|
||||
|
|
|
|||
|
|
@ -121,7 +121,8 @@ class Robotstxt_OG_REST_API {
|
|||
* @return WP_REST_Response|WP_Error Response object.
|
||||
*/
|
||||
public function handle_resolve( WP_REST_Request $request ) {
|
||||
$post_id = (int) $request->get_param( 'post_id' );
|
||||
$param = $request->get_param( 'post_id' );
|
||||
$post_id = is_numeric( $param ) ? (int) $param : 0;
|
||||
|
||||
// Verify post exists.
|
||||
$post = get_post( $post_id );
|
||||
|
|
@ -159,7 +160,8 @@ class Robotstxt_OG_REST_API {
|
|||
* @return WP_REST_Response|WP_Error Response object.
|
||||
*/
|
||||
public function handle_status( WP_REST_Request $request ) {
|
||||
$post_id = (int) $request->get_param( 'post_id' );
|
||||
$param = $request->get_param( 'post_id' );
|
||||
$post_id = is_numeric( $param ) ? (int) $param : 0;
|
||||
|
||||
// Verify post exists.
|
||||
$post = get_post( $post_id );
|
||||
|
|
@ -189,14 +191,15 @@ class Robotstxt_OG_REST_API {
|
|||
/**
|
||||
* Check REST API permission.
|
||||
*
|
||||
* Requires manage_options capability.
|
||||
* Requires edit_others_posts capability (editors and above).
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @param WP_REST_Request $request REST request object.
|
||||
* @return bool|WP_Error True if authorized, WP_Error otherwise.
|
||||
*/
|
||||
public function check_permission() {
|
||||
if ( ! current_user_can( 'manage_options' ) ) {
|
||||
public function check_permission( WP_REST_Request $request ) {
|
||||
if ( ! current_user_can( 'edit_others_posts' ) ) {
|
||||
return new WP_Error(
|
||||
'rest_forbidden',
|
||||
__( 'You do not have permission to access this endpoint.', 'robotstxt-og' ),
|
||||
|
|
@ -204,6 +207,18 @@ class Robotstxt_OG_REST_API {
|
|||
);
|
||||
}
|
||||
|
||||
// For post-specific endpoints, also verify the user can edit that post.
|
||||
$param = $request->get_param( 'post_id' );
|
||||
$post_id = is_numeric( $param ) ? (int) $param : 0;
|
||||
|
||||
if ( $post_id > 0 && ! current_user_can( 'edit_post', $post_id ) ) {
|
||||
return new WP_Error(
|
||||
'rest_forbidden',
|
||||
__( 'You do not have permission to edit this post.', 'robotstxt-og' ),
|
||||
array( 'status' => 403 )
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -225,7 +225,8 @@ class Robotstxt_OG_Tags {
|
|||
private function get_og_title(): string {
|
||||
if ( is_singular() ) {
|
||||
$post_id = get_queried_object_id();
|
||||
$custom_title = (string) get_post_meta( $post_id, '_og_title', true );
|
||||
$raw_title = get_post_meta( $post_id, '_og_title', true );
|
||||
$custom_title = is_string( $raw_title ) ? $raw_title : '';
|
||||
|
||||
if ( ! empty( $custom_title ) ) {
|
||||
return $custom_title;
|
||||
|
|
@ -262,13 +263,15 @@ class Robotstxt_OG_Tags {
|
|||
private function get_og_description(): string {
|
||||
if ( is_singular() ) {
|
||||
$post_id = get_queried_object_id();
|
||||
$custom_desc = (string) get_post_meta( $post_id, '_og_description', true );
|
||||
$raw_desc = get_post_meta( $post_id, '_og_description', true );
|
||||
$custom_desc = is_string( $raw_desc ) ? $raw_desc : '';
|
||||
|
||||
if ( ! empty( $custom_desc ) ) {
|
||||
return $custom_desc;
|
||||
}
|
||||
|
||||
$excerpt = (string) get_post_field( 'post_excerpt', $post_id );
|
||||
$raw_excerpt = get_post_field( 'post_excerpt', $post_id );
|
||||
$excerpt = is_string( $raw_excerpt ) ? $raw_excerpt : '';
|
||||
|
||||
if ( ! empty( $excerpt ) ) {
|
||||
return wp_strip_all_tags( $excerpt );
|
||||
|
|
@ -455,7 +458,8 @@ class Robotstxt_OG_Tags {
|
|||
}
|
||||
|
||||
// Alt text from the media library (set when uploading/editing the image).
|
||||
$alt_text = (string) get_post_meta( $image_id, '_wp_attachment_image_alt', true );
|
||||
$raw_alt = get_post_meta( $image_id, '_wp_attachment_image_alt', true );
|
||||
$alt_text = is_string( $raw_alt ) ? $raw_alt : '';
|
||||
|
||||
if ( ! empty( $alt_text ) ) {
|
||||
printf(
|
||||
|
|
@ -489,7 +493,7 @@ class Robotstxt_OG_Tags {
|
|||
|
||||
$published = get_the_date( 'c', $post );
|
||||
|
||||
if ( ! empty( $published ) ) {
|
||||
if ( is_string( $published ) && ! empty( $published ) ) {
|
||||
printf(
|
||||
'<meta property="article:published_time" content="%s" />' . "\n",
|
||||
esc_attr( $published )
|
||||
|
|
@ -498,7 +502,7 @@ class Robotstxt_OG_Tags {
|
|||
|
||||
$modified = get_the_modified_date( 'c', $post );
|
||||
|
||||
if ( ! empty( $modified ) ) {
|
||||
if ( is_string( $modified ) && ! empty( $modified ) ) {
|
||||
printf(
|
||||
'<meta property="article:modified_time" content="%s" />' . "\n",
|
||||
esc_attr( $modified )
|
||||
|
|
@ -541,7 +545,8 @@ class Robotstxt_OG_Tags {
|
|||
* @return void
|
||||
*/
|
||||
private function output_twitter_card_tags( string $image_url ): void {
|
||||
$card_type = (string) get_option( 'robotstxt_og_twitter_card_type', 'summary_large_image' );
|
||||
$card_option = get_option( 'robotstxt_og_twitter_card_type', 'summary_large_image' );
|
||||
$card_type = is_string( $card_option ) ? $card_option : 'summary_large_image';
|
||||
|
||||
if ( ! in_array( $card_type, array( 'summary', 'summary_large_image' ), true ) ) {
|
||||
$card_type = 'summary_large_image';
|
||||
|
|
@ -554,7 +559,8 @@ class Robotstxt_OG_Tags {
|
|||
);
|
||||
|
||||
// Site handle (e.g. @example).
|
||||
$twitter_site = sanitize_text_field( (string) get_option( 'robotstxt_og_twitter_site', '' ) );
|
||||
$twitter_option = get_option( 'robotstxt_og_twitter_site', '' );
|
||||
$twitter_site = sanitize_text_field( is_string( $twitter_option ) ? $twitter_option : '' );
|
||||
|
||||
if ( ! empty( $twitter_site ) ) {
|
||||
// Ensure the handle includes the @ prefix.
|
||||
|
|
|
|||
Loading…
Reference in a new issue