337 lines
9.2 KiB
PHP
337 lines
9.2 KiB
PHP
<?php
|
|
/**
|
|
* WP-CLI Commands Class
|
|
*
|
|
* Provides WP-CLI commands for managing OG image fallbacks.
|
|
*
|
|
* @package ROBOTSTXT_OG
|
|
* @since 1.0.0
|
|
*/
|
|
|
|
if ( ! defined( 'ABSPATH' ) ) {
|
|
exit; // Exit if accessed directly.
|
|
}
|
|
|
|
/**
|
|
* Class Robotstxt_OG_CLI
|
|
*
|
|
* WP-CLI commands for OpenGraph (by ROBOTSTXT) plugin.
|
|
*
|
|
* ## EXAMPLES
|
|
*
|
|
* # Resolve fallback images for all posts
|
|
* $ wp og-fallback resolve --all
|
|
* Success: Resolved 45 posts. Failed: 2.
|
|
*
|
|
* # Resolve a single post
|
|
* $ wp og-fallback resolve 42
|
|
* Success: Post 42 resolved to https://example.com/image.jpg
|
|
*
|
|
* # Clear all cached fallback URLs
|
|
* $ wp og-fallback clear-cache --all
|
|
* Success: Cleared 45 cached fallback URLs.
|
|
*
|
|
* # Preview without making changes
|
|
* $ wp og-fallback resolve --all --dry-run
|
|
* Found 47 posts with featured images. (dry-run, no changes made)
|
|
*
|
|
* @since 1.0.0
|
|
*/
|
|
class Robotstxt_OG_CLI extends WP_CLI_Command {
|
|
|
|
/**
|
|
* Image resolver instance.
|
|
*
|
|
* @since 1.0.0
|
|
* @var Robotstxt_OG_Image_Resolver
|
|
*/
|
|
private Robotstxt_OG_Image_Resolver $resolver;
|
|
|
|
/**
|
|
* Constructor.
|
|
*
|
|
* @since 1.0.0
|
|
*
|
|
* @param Robotstxt_OG_Image_Resolver $resolver Image resolver instance.
|
|
*/
|
|
public function __construct( Robotstxt_OG_Image_Resolver $resolver ) {
|
|
$this->resolver = $resolver;
|
|
}
|
|
|
|
/**
|
|
* Resolve fallback images for posts.
|
|
*
|
|
* Clears cached fallback URL and re-resolves it for the specified post(s).
|
|
* Use --all to process all posts with featured images.
|
|
*
|
|
* ## OPTIONS
|
|
*
|
|
* [<post_id>]
|
|
* : The ID of a single post to resolve.
|
|
*
|
|
* [--all]
|
|
* : Resolve fallback images for all posts with featured images.
|
|
*
|
|
* [--dry-run]
|
|
* : Preview what would be done without making changes.
|
|
*
|
|
* [--post-type=<type>]
|
|
* : Limit resolution to a specific post type. Default: any.
|
|
*
|
|
* ## EXAMPLES
|
|
*
|
|
* wp og-fallback resolve --all
|
|
* wp og-fallback resolve --all --dry-run
|
|
* wp og-fallback resolve 42
|
|
* wp og-fallback resolve --all --post-type=post
|
|
*
|
|
* @since 1.0.0
|
|
*
|
|
* @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'] ) && is_string( $assoc_args['post-type'] ) ) ? $assoc_args['post-type'] : 'any';
|
|
|
|
if ( $all ) {
|
|
$this->resolve_all( $dry_run, $post_type );
|
|
return;
|
|
}
|
|
|
|
if ( ! empty( $args[0] ) ) {
|
|
$post_id = absint( $args[0] );
|
|
$this->resolve_single( $post_id, $dry_run );
|
|
return;
|
|
}
|
|
|
|
WP_CLI::error( 'Please specify a post ID or use --all flag.' );
|
|
}
|
|
|
|
/**
|
|
* Clear cached fallback URLs.
|
|
*
|
|
* ## OPTIONS
|
|
*
|
|
* [<post_id>]
|
|
* : The ID of a single post to clear cache for.
|
|
*
|
|
* [--all]
|
|
* : Clear all cached fallback URLs.
|
|
*
|
|
* [--dry-run]
|
|
* : Preview what would be done without making changes.
|
|
*
|
|
* ## EXAMPLES
|
|
*
|
|
* wp og-fallback clear-cache --all
|
|
* wp og-fallback clear-cache 42
|
|
* wp og-fallback clear-cache --all --dry-run
|
|
*
|
|
* @since 1.0.0
|
|
*
|
|
* @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 {
|
|
$all = isset( $assoc_args['all'] );
|
|
$dry_run = isset( $assoc_args['dry-run'] );
|
|
|
|
if ( $all ) {
|
|
$this->clear_all_caches( $dry_run );
|
|
return;
|
|
}
|
|
|
|
if ( ! empty( $args[0] ) ) {
|
|
$post_id = absint( $args[0] );
|
|
$this->clear_single_cache( $post_id, $dry_run );
|
|
return;
|
|
}
|
|
|
|
WP_CLI::error( 'Please specify a post ID or use --all flag.' );
|
|
}
|
|
|
|
/**
|
|
* Resolve fallback images for all posts.
|
|
*
|
|
* @since 1.0.0
|
|
*
|
|
* @param bool $dry_run Whether to run without making changes.
|
|
* @param string $post_type Post type to limit to.
|
|
* @return void
|
|
*/
|
|
private function resolve_all( bool $dry_run, string $post_type ): void {
|
|
$posts = get_posts(
|
|
array(
|
|
'post_type' => $post_type,
|
|
'post_status' => 'any',
|
|
'posts_per_page' => -1,
|
|
'meta_query' => array( // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
|
|
array(
|
|
'key' => '_thumbnail_id',
|
|
'compare' => 'EXISTS',
|
|
),
|
|
),
|
|
'fields' => 'ids',
|
|
)
|
|
);
|
|
|
|
$count = count( $posts );
|
|
|
|
if ( $dry_run ) {
|
|
WP_CLI::line(
|
|
/* translators: %d: number of posts found */
|
|
sprintf( __( 'Found %d posts with featured images. (dry-run, no changes made)', 'robotstxt-og' ), $count )
|
|
);
|
|
return;
|
|
}
|
|
|
|
if ( 0 === $count ) {
|
|
WP_CLI::warning( __( 'No posts with featured images found.', 'robotstxt-og' ) );
|
|
return;
|
|
}
|
|
|
|
$success = 0;
|
|
$failed = 0;
|
|
$progress = WP_CLI\Utils\make_progress_bar(
|
|
/* translators: %d: number of posts to process */
|
|
sprintf( __( 'Resolving %d posts', 'robotstxt-og' ), $count ),
|
|
$count
|
|
);
|
|
|
|
foreach ( $posts as $post_id ) {
|
|
$this->resolver->clear_cache( $post_id );
|
|
$url = $this->resolver->resolve_image( $post_id );
|
|
|
|
if ( ! empty( $url ) ) {
|
|
++$success;
|
|
} else {
|
|
++$failed;
|
|
}
|
|
|
|
$progress->tick();
|
|
}
|
|
|
|
$progress->finish();
|
|
|
|
WP_CLI::success(
|
|
/* translators: 1: successful count, 2: failed count */
|
|
sprintf( __( 'Resolved %1$d posts. Failed: %2$d.', 'robotstxt-og' ), $success, $failed )
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Resolve fallback image for a single post.
|
|
*
|
|
* @since 1.0.0
|
|
*
|
|
* @param int $post_id Post ID.
|
|
* @param bool $dry_run Whether to run without making changes.
|
|
* @return void
|
|
*/
|
|
private function resolve_single( int $post_id, bool $dry_run ): void {
|
|
$post = get_post( $post_id );
|
|
|
|
if ( ! $post ) {
|
|
/* translators: %d: post ID */
|
|
WP_CLI::error( sprintf( __( 'Post %d not found.', 'robotstxt-og' ), $post_id ) );
|
|
return;
|
|
}
|
|
|
|
if ( $dry_run ) {
|
|
$thumb_id = get_post_thumbnail_id( $post_id );
|
|
$thumb = $thumb_id ? wp_get_attachment_url( $thumb_id ) : '';
|
|
/* translators: 1: post ID, 2: image URL */
|
|
WP_CLI::line( sprintf( __( 'Post %1$d has featured image: %2$s (dry-run, no changes made)', 'robotstxt-og' ), $post_id, ! empty( $thumb ) ? $thumb : 'none' ) );
|
|
return;
|
|
}
|
|
|
|
$this->resolver->clear_cache( $post_id );
|
|
$url = $this->resolver->resolve_image( $post_id );
|
|
|
|
if ( ! empty( $url ) ) {
|
|
/* translators: 1: post ID, 2: resolved URL */
|
|
WP_CLI::success( sprintf( __( 'Post %1$d resolved to %2$s', 'robotstxt-og' ), $post_id, $url ) );
|
|
} else {
|
|
/* translators: %d: post ID */
|
|
WP_CLI::warning( sprintf( __( 'Post %d: no compatible image found.', 'robotstxt-og' ), $post_id ) );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Clear all cached fallback URLs.
|
|
*
|
|
* @since 1.0.0
|
|
*
|
|
* @param bool $dry_run Whether to run without making changes.
|
|
* @return void
|
|
*/
|
|
private function clear_all_caches( bool $dry_run ): void {
|
|
global $wpdb;
|
|
|
|
// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
|
|
// WP-CLI single-invocation context; no persistent cache layer is appropriate here.
|
|
$count = (int) $wpdb->get_var(
|
|
$wpdb->prepare(
|
|
"SELECT COUNT(*) FROM {$wpdb->postmeta} WHERE meta_key = %s",
|
|
'_og_image_fallback_url'
|
|
)
|
|
);
|
|
// phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
|
|
|
|
if ( $dry_run ) {
|
|
/* translators: %d: number of cache entries found */
|
|
WP_CLI::line( sprintf( __( 'Found %d cached fallback URLs. (dry-run, no changes made)', 'robotstxt-og' ), $count ) );
|
|
return;
|
|
}
|
|
|
|
if ( 0 === $count ) {
|
|
WP_CLI::warning( __( 'No cached fallback URLs found.', 'robotstxt-og' ) );
|
|
return;
|
|
}
|
|
|
|
// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
|
|
// Bulk delete of all cached postmeta rows — no WP API equivalent for this operation.
|
|
$deleted = $wpdb->delete(
|
|
$wpdb->postmeta,
|
|
array( 'meta_key' => '_og_image_fallback_url' ), // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
|
|
array( '%s' )
|
|
);
|
|
// phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
|
|
|
|
/* translators: %d: number of cleared entries */
|
|
WP_CLI::success( sprintf( __( 'Cleared %d cached fallback URLs.', 'robotstxt-og' ), (int) $deleted ) );
|
|
}
|
|
|
|
/**
|
|
* Clear cached fallback URL for a single post.
|
|
*
|
|
* @since 1.0.0
|
|
*
|
|
* @param int $post_id Post ID.
|
|
* @param bool $dry_run Whether to run without making changes.
|
|
* @return void
|
|
*/
|
|
private function clear_single_cache( int $post_id, bool $dry_run ): void {
|
|
$cached = get_post_meta( $post_id, '_og_image_fallback_url', true );
|
|
|
|
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, ( is_string( $cached ) && ! empty( $cached ) ) ? $cached : 'none' ) );
|
|
return;
|
|
}
|
|
|
|
if ( empty( $cached ) ) {
|
|
/* translators: %d: post ID */
|
|
WP_CLI::warning( sprintf( __( 'Post %d has no cached fallback URL.', 'robotstxt-og' ), $post_id ) );
|
|
return;
|
|
}
|
|
|
|
$this->resolver->clear_cache( $post_id );
|
|
/* translators: %d: post ID */
|
|
WP_CLI::success( sprintf( __( 'Cleared cached fallback URL for post %d.', 'robotstxt-og' ), $post_id ) );
|
|
}
|
|
}
|