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 * * [] * : 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=] * : 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 $args Positional arguments. * @param array $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 * * [] * : 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 $args Positional arguments. * @param array $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 ) ); } }