esc_html__('Display author books from Goodreads with ratings', 'bd-bookshelf'), 'classname' => 'widget-goodreads-books', ) ); } public function widget($args, $instance) { echo $args['before_widget']; $author_name = get_theme_mod('author_name', 'B.D. Murphy'); $title = sprintf(esc_html__('GoodReads for %s', 'bd-bookshelf'), $author_name); if (!empty($instance['title'])) { $title = $instance['title']; } if (!empty($title)) { echo $args['before_title'] . esc_html($title) . $args['after_title']; } // Try to get books from BD_BookShelfHelper first $books = array(); if (class_exists('BD_BookShelfHelper')) { $query = new WP_Query(array( 'post_type' => 'book', 'posts_per_page' => -1, 'post_status' => 'publish', 'orderby' => 'date', 'order' => 'DESC', )); $books = $query->posts; } if (!empty($books)) { echo '
'; foreach ($books as $book) { $this->render_book_item($book); } echo '
'; } else { // Fallback: fetch from Goodreads author page $this->render_goodreads_fallback(); } echo $args['after_widget']; } /** * Render books by fetching from Goodreads author widget */ private function render_goodreads_fallback() { if (!get_theme_mod('enable_goodreads_fetch', false)) { echo '

' . esc_html__('Goodreads integration is not enabled. Enable it in Customizer > Display Options.', 'bd-bookshelf') . '

'; return; } $goodreads_id = get_theme_mod('goodreads_author_id', '30469651'); if (empty($goodreads_id)) { echo '

' . esc_html__('Configure your Goodreads Author ID in Theme Settings.', 'bd-bookshelf') . '

'; return; } $books = array(); // Check transient cache first (24 hours) $cache_key = 'bdm_goodreads_books_' . sanitize_title($goodreads_id); $cached_books = get_transient($cache_key); if ($cached_books !== false) { $books = $cached_books; } else { $widget_url = sprintf( 'https://www.goodreads.com/author/list/%s', esc_attr($goodreads_id) ); $response = wp_remote_get($widget_url, array( 'timeout' => 10, 'headers' => array( 'User-Agent' => 'Mozilla/5.0 (compatible; BDBookshelf/1.0)', ), )); if (!is_wp_error($response)) { $body = wp_remote_retrieve_body($response); $books = $this->parse_goodreads_html($body); if (!empty($books)) { set_transient($cache_key, $books, 24 * HOUR_IN_SECONDS); } } } if (empty($books)) { echo '

' . esc_html__('No books found on Goodreads.', 'bd-bookshelf') . '

'; return; } echo '
'; foreach ($books as $book) { echo '
'; if (!empty($book['cover'])) { printf( '
%s
', esc_url($book['cover']), esc_attr($book['title']) ); } echo '
'; if (!empty($book['url'])) { printf( '%s', esc_url($book['url']), esc_html($book['title']) ); } else { printf( '%s', esc_html($book['title']) ); } if (!empty($book['rating'])) { printf( '
Goodreads: %s/5
', esc_html($book['rating']) ); } echo '
'; } echo '
'; } /** * Parse Goodreads author page HTML for books * Version 2.2.1 - Top 6 books by rating */ private function parse_goodreads_html($html) { $books = array(); // Use DOMDocument to parse $dom = new DOMDocument(); libxml_use_internal_errors(true); $dom->loadHTML('' . $html); libxml_clear_errors(); $xpath = new DOMXPath($dom); // Find book rows - Goodreads uses table rows for book lists $rows = $xpath->query('//tr[itemtype="http://schema.org/Book"]'); if ($rows->length === 0) { // Try alternative selectors $rows = $xpath->query('//tr'); } foreach ($rows as $row) { $title = ''; $cover = ''; $url = ''; $rating = ''; // Get title $titleNodes = $xpath->query('.//a[@class="bookTitle"]', $row); if ($titleNodes->length > 0) { $title = trim($titleNodes->item(0)->textContent); $url = $titleNodes->item(0)->getAttribute('href'); if ($url && strpos($url, 'http') !== 0) { $url = 'https://www.goodreads.com' . $url; } } if (empty($title)) continue; // Get cover image $imgNodes = $xpath->query('.//img', $row); if ($imgNodes->length > 0) { $cover = $imgNodes->item(0)->getAttribute('src'); } // Get rating and review count $ratingNodes = $xpath->query('.//span[@class="minirating"]', $row); if ($ratingNodes->length > 0) { $rating_text = $ratingNodes->item(0)->textContent; if (preg_match('/([\d.]+)\s*avg/', $rating_text, $matches)) { $rating = $matches[1]; } if (preg_match('/([\d,]+)\s*rating/', $rating_text, $matches)) { $review_count = intval(str_replace(',', '', $matches[1])); } } if (!empty($title)) { $books[] = array( 'title' => $title, 'cover' => $cover, 'url' => $url, 'rating' => $rating, 'review_count' => $review_count, ); } } // Sort by review count descending (most reviews first) usort($books, function($a, $b) { $countA = intval($a['review_count']); $countB = intval($b['review_count']); if ($countA == $countB) return 0; return ($countA > $countB) ? -1 : 1; }); // Return top 6 return array_slice($books, 0, 6); } private function render_book_item($book) { $cover_url = ''; if (has_post_thumbnail($book->ID)) { $cover_url = get_the_post_thumbnail_url($book->ID, 'shelf-thumb'); } $title = $book->post_title; $slug = get_post_meta($book->ID, 'short_link_slug', true); $url = $slug ? home_url('/' . $slug) : get_permalink($book->ID); $ratings = array(); $amazon = get_post_meta($book->ID, 'amazon_rating', true); $goodreads = get_post_meta($book->ID, 'goodreads_rating', true); if ($amazon) $ratings['amazon'] = $amazon; if ($goodreads) $ratings['goodreads'] = $goodreads; echo '
'; if ($cover_url) { printf( '
%s
', esc_url($url), esc_url($cover_url), esc_attr($title) ); } echo '
'; printf( '%s', esc_url($url), esc_html($title) ); if (!empty($ratings)) { echo '
'; if (!empty($ratings['amazon'])) { printf('Amazon: %s/5 ', esc_html($ratings['amazon'])); } if (!empty($ratings['goodreads'])) { printf('Goodreads: %s/5', esc_html($ratings['goodreads'])); } echo '
'; } echo '
'; } public function form($instance) { $title = !empty($instance['title']) ? $instance['title'] : ''; $author_name = get_theme_mod('author_name', 'B.D. Murphy'); $goodreads_id = get_theme_mod('goodreads_author_id', '30469651'); ?>