diff --git a/includes/abilities/comic/class-comic-read-operations.php b/includes/abilities/comic/class-comic-read-operations.php index 3575426..7628e22 100644 --- a/includes/abilities/comic/class-comic-read-operations.php +++ b/includes/abilities/comic/class-comic-read-operations.php @@ -39,20 +39,15 @@ class EMCP_Tools_Comic_Read_Operations { } /** - * Get full details of a single comic post. + * Resolves the featured image for a comic post. * - * @param array $args Arguments: { id?: int, slug?: string } - * @return array|\WP_Error + * Returns the full-resolution image URL, attachment ID, dimensions, and alt text. + * If no post thumbnail is explicitly set, falls back to the comic's first image attachment. + * + * @param int $post_id Comic post ID. + * @return array { id: int, url: string, width: ?int, height: ?int, alt: string } */ - public static function get_comic( array $args ) { - $post = self::resolve_comic_post( $args ); - if ( is_wp_error( $post ) ) { - return $post; - } - - $post_id = $post->ID; - - // Featured media (Page 1) + public static function resolve_featured_image( int $post_id ): array { $featured_id = (int) get_post_thumbnail_id( $post_id ); $featured_url = ''; $featured_w = null; @@ -69,6 +64,64 @@ class EMCP_Tools_Comic_Read_Operations { $featured_alt = (string) get_post_meta( $featured_id, '_wp_attachment_image_alt', true ); } + // Fallback: If no post thumbnail is set, search for the first attached image + if ( empty( $featured_url ) && function_exists( 'get_posts' ) ) { + $attachments = get_posts( + array( + 'post_type' => 'attachment', + 'post_parent' => $post_id, + 'post_mime_type' => 'image', + 'posts_per_page' => 1, + 'order' => 'ASC', + 'orderby' => 'menu_order ID', + ) + ); + if ( ! empty( $attachments ) ) { + $att = reset( $attachments ); + $featured_id = (int) ( $att->ID ?? 0 ); + if ( $featured_id > 0 && function_exists( 'wp_get_attachment_image_src' ) ) { + $src = wp_get_attachment_image_src( $featured_id, 'full' ); + if ( is_array( $src ) && ! empty( $src[0] ) ) { + $featured_url = $src[0]; + $featured_w = (int) $src[1]; + $featured_h = (int) $src[2]; + } + $featured_alt = (string) get_post_meta( $featured_id, '_wp_attachment_image_alt', true ); + } + } + } + + return array( + 'id' => $featured_id, + 'url' => $featured_url, + 'width' => $featured_w, + 'height' => $featured_h, + 'alt' => $featured_alt, + ); + } + + /** + * Get full details of a single comic post. + * + * @param array $args Arguments: { id?: int, slug?: string } + * @return array|\WP_Error + */ + public static function get_comic( array $args ) { + $post = self::resolve_comic_post( $args ); + if ( is_wp_error( $post ) ) { + return $post; + } + + $post_id = $post->ID; + + // Featured media (Page 1) with fallback to attachments + $featured_media = self::resolve_featured_image( $post_id ); + $featured_id = $featured_media['id']; + $featured_url = $featured_media['url']; + $featured_w = $featured_media['width']; + $featured_h = $featured_media['height']; + $featured_alt = $featured_media['alt']; + // Multi-image field (Pages 2..N) $html_below = (string) get_post_meta( $post_id, 'comic-html-below', true ); if ( '' === $html_below ) { @@ -133,13 +186,21 @@ class EMCP_Tools_Comic_Read_Operations { ), 'content' => $post->post_content, 'excerpt' => $post->post_excerpt, - 'featured_media' => array( + 'featured_media' => array( 'id' => $featured_id, 'url' => $featured_url, 'width' => $featured_w, 'height' => $featured_h, 'alt' => $featured_alt, ), + 'featured_image' => array( + 'id' => $featured_id, + 'url' => $featured_url, + 'width' => $featured_w, + 'height' => $featured_h, + 'alt' => $featured_alt, + ), + 'featured_image_url' => $featured_url, 'comic_html_below' => $html_below, 'additional_images' => $additional_images, 'all_images' => $all_images, @@ -234,31 +295,39 @@ class EMCP_Tools_Comic_Read_Operations { foreach ( $query->posts as $p ) { $p_id = $p->ID; - $thumb_id = (int) get_post_thumbnail_id( $p_id ); - $thumb_url = ''; + $featured = self::resolve_featured_image( $p_id ); + $featured_url = $featured['url']; + + $thumb_id = $featured['id']; + $thumb_url = ''; if ( $thumb_id > 0 && function_exists( 'wp_get_attachment_image_src' ) ) { $src = wp_get_attachment_image_src( $thumb_id, 'medium' ); if ( is_array( $src ) && ! empty( $src[0] ) ) { $thumb_url = $src[0]; } } + if ( empty( $thumb_url ) ) { + $thumb_url = $featured_url; + } $html_below = (string) get_post_meta( $p_id, 'comic-html-below', true ); $add_count = count( EMCP_Tools_Comic_Html_Helper::parse_html_below( $html_below ) ); - $total_pgs = ( ! empty( $thumb_url ) ? 1 : 0 ) + $add_count; + $total_pgs = ( ! empty( $featured_url ) ? 1 : 0 ) + $add_count; $comics[] = array( - 'id' => $p_id, - 'slug' => $p->post_name, - 'title' => get_the_title( $p_id ), - 'url' => get_permalink( $p_id ), - 'date' => $p->post_date, - 'status' => $p->post_status, - 'thumbnail_url' => $thumb_url, - 'total_pages' => $total_pgs, - 'source_tweet_id' => (string) get_post_meta( $p_id, 'source_tweet_id', true ), - 'source_url' => (string) get_post_meta( $p_id, 'source_url', true ), - 'chapters' => self::get_term_summaries( $p_id, 'chapters' ), + 'id' => $p_id, + 'slug' => $p->post_name, + 'title' => get_the_title( $p_id ), + 'url' => get_permalink( $p_id ), + 'date' => $p->post_date, + 'status' => $p->post_status, + 'thumbnail_url' => $thumb_url, + 'featured_image' => $featured, + 'featured_image_url' => $featured_url, + 'total_pages' => $total_pgs, + 'source_tweet_id' => (string) get_post_meta( $p_id, 'source_tweet_id', true ), + 'source_url' => (string) get_post_meta( $p_id, 'source_url', true ), + 'chapters' => self::get_term_summaries( $p_id, 'chapters' ), ); } diff --git a/tests/test-comic-easel-mcp.php b/tests/test-comic-easel-mcp.php index 651c999..2f7eaf9 100644 --- a/tests/test-comic-easel-mcp.php +++ b/tests/test-comic-easel-mcp.php @@ -49,6 +49,8 @@ class WP_Post { public $post_date_gmt; public $post_modified; public $post_author = 1; + public $post_parent = 0; + public $post_mime_type = ''; } class WP_Term { @@ -76,6 +78,20 @@ function get_post( $id ) { return $GLOBALS['wp_posts'][ $id ] ?? null; } +function get_posts( $args = array() ) { + $results = array(); + $parent = $args['post_parent'] ?? 0; + $type = $args['post_type'] ?? 'post'; + $mime = $args['post_mime_type'] ?? ''; + foreach ( $GLOBALS['wp_posts'] as $p ) { + if ( $type && $p->post_type !== $type ) continue; + if ( $parent && $p->post_parent != $parent ) continue; + if ( $mime && 0 !== strpos( $p->post_mime_type, $mime ) ) continue; + $results[] = $p; + } + return $results; +} + function wp_insert_post( $args, $wp_error = false ) { $id = ++$GLOBALS['wp_post_counter']; $p = new WP_Post(); @@ -351,10 +367,31 @@ assert( $find_res2['found'] === true ); assert( $find_res2['comic']['id'] === $created_id ); echo 'Found by source_url: ID ' . $find_res2['comic']['id'] . "\n"; -// Find nonexistent source -$find_empty = EMCP_Tools_Comic_Read_Operations::find_by_source( array( 'source_tweet_id' => '999999999999' ) ); -assert( $find_empty['found'] === false ); -echo "PASS: find_by_source verified for scrapers and idempotency!\n\n"; +// Verify featured_image and featured_image_url on get_comic +assert( isset( $read_res['featured_image']['url'] ) && ! empty( $read_res['featured_image']['url'] ) ); +assert( isset( $read_res['featured_image_url'] ) && $read_res['featured_image_url'] === $read_res['featured_image']['url'] ); + +// Verify list_comics returns featured_image and featured_image_url +$list_res = EMCP_Tools_Comic_Read_Operations::list_comics( array( 'status' => 'publish' ) ); +assert( ! empty( $list_res['comics'] ) ); +assert( isset( $list_res['comics'][0]['featured_image']['url'] ) ); +assert( isset( $list_res['comics'][0]['featured_image_url'] ) ); + +// Verify fallback to attached image when _thumbnail_id is missing +$no_thumb_id = wp_insert_post( array( 'post_title' => 'Legacy Attached Comic', 'post_type' => 'comic', 'post_status' => 'publish' ) ); +$att_p = new WP_Post(); +$att_id = ++$GLOBALS['wp_post_counter']; +$att_p->ID = $att_id; +$att_p->post_type = 'attachment'; +$att_p->post_mime_type = 'image/jpeg'; +$att_p->post_parent = $no_thumb_id; +$GLOBALS['wp_posts'][ $att_id ] = $att_p; + +$legacy_res = EMCP_Tools_Comic_Read_Operations::get_comic( array( 'id' => $no_thumb_id ) ); +assert( ! is_wp_error( $legacy_res ) ); +assert( $legacy_res['featured_image']['id'] === $att_id ); +assert( ! empty( $legacy_res['featured_image_url'] ) ); +echo "PASS: featured_image, featured_image_url, and attachment fallback verified!\n"; echo "=== TEST 4: Update Comic (Append Images & Set Source) ===\n"; $update_res = EMCP_Tools_Comic_Write_Operations::update_comic(