Files
Bookshelf/bd-bookshelf-plugin/includes/class-bdm-rating-sync.php
2026-10-18 19:01:01 -07:00

217 lines
6.3 KiB
PHP

<?php
// Copyright 2026 Umbra Tools (umbratools.com)
// SPDX-License-Identifier: Apache-2.0
/**
* Rating Sync Engine class
*
* @package BD_Bookshelf
*/
if (!defined('ABSPATH')) {
exit;
}
class BDM_Rating_Sync {
/**
* Constructor
*/
public function __construct() {
// Schedule cron job
add_action('bd_bookshelf_sync_ratings', [$this, 'sync_all_ratings']);
// Register cron interval
add_filter('cron_schedules', [$this, 'add_cron_interval']);
// Schedule on plugin activation
if (!wp_next_scheduled('bd_bookshelf_sync_ratings')) {
wp_schedule_event(time(), 'bd_bookshelf_twelve_hours', 'bd_bookshelf_sync_ratings');
}
}
/**
* Add custom cron interval
*/
public function add_cron_interval($schedules) {
$schedules['bd_bookshelf_twelve_hours'] = [
'interval' => 43200, // 12 hours in seconds
'display' => esc_html__('Every 12 Hours', 'bd-bookshelf'),
];
return $schedules;
}
/**
* Sync all book ratings
*/
public function sync_all_ratings() {
$books = BDM_Book_Manager::get_books(['number' => 999]);
$synced = 0;
$failed = 0;
foreach ($books as $book) {
$result = $this->sync_book_rating($book->id);
if ($result) {
$synced++;
} else {
$failed++;
}
// Delay between requests to avoid rate limiting
sleep(2);
}
// Log results
update_option('bd_bookshelf_last_sync', [
'time' => current_time('mysql'),
'synced' => $synced,
'failed' => $failed,
'total' => count($books),
]);
return ['synced' => $synced, 'failed' => $failed];
}
/**
* Sync rating for a single book
*/
public function sync_book_rating($book_id) {
global $wpdb;
$book = BDM_Book_Manager::get_book($book_id);
if (!$book) {
return false;
}
$amazon_rating = 0;
$goodreads_rating = 0;
// Sync Amazon rating
if (!empty($book->amazon_asin) || !empty($book->isbn_ebook)) {
$isbn = $book->amazon_asin ?: $book->isbn_ebook;
$amazon_rating = $this->fetch_amazon_rating($isbn);
// Keep last rating if fetch fails
if ($amazon_rating === false) {
$amazon_rating = $book->rating_amazon;
}
}
// Sync Goodreads rating
if (!empty($book->goodreads_id)) {
$goodreads_rating = $this->fetch_goodreads_rating($book->goodreads_id);
// Keep last rating if fetch fails
if ($goodreads_rating === false) {
$goodreads_rating = $book->rating_goodreads;
}
}
// Update book ratings
$table = BDM_Database::table('books');
$wpdb->update(
$table,
[
'rating_amazon' => $amazon_rating,
'rating_goodreads' => $goodreads_rating,
'last_sync' => current_time('mysql'),
],
['id' => $book_id],
['%f', '%f', '%s'],
['%d']
);
return true;
}
/**
* Fetch Amazon rating via web scraping
*/
private function fetch_amazon_rating($isbn) {
$url = "https://www.amazon.com/dp/{$isbn}";
$response = wp_remote_get($url, [
'timeout' => 15,
'headers' => [
'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
'Accept-Language' => 'en-US,en;q=0.5',
],
]);
if (is_wp_error($response)) {
error_log('BD Bookshelf: Amazon fetch error - ' . $response->get_error_message());
return false;
}
$body = wp_remote_retrieve_body($response);
// Parse rating from HTML
// Amazon uses data-hook="review-star-rating" or similar
if (preg_match('/data-hook="review-star-rating"[^>]*>.*?(\d+\.?\d*)\s*out of\s*5/si', $body, $matches)) {
return (float) $matches[1];
}
// Alternative pattern
if (preg_match('/a-icon-alt[^>]*>(\d+\.?\d*)\s*out of\s*\d+ stars/si', $body, $matches)) {
return (float) $matches[1];
}
return false;
}
/**
* Fetch Goodreads rating via web scraping
*/
private function fetch_goodreads_rating($goodreads_id) {
$url = "https://www.goodreads.com/book/show/{$goodreads_id}";
$response = wp_remote_get($url, [
'timeout' => 15,
'headers' => [
'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
'Accept-Language' => 'en-US,en;q=0.5',
],
]);
if (is_wp_error($response)) {
error_log('BD Bookshelf: Goodreads fetch error - ' . $response->get_error_message());
return false;
}
$body = wp_remote_retrieve_body($response);
// Parse rating from HTML
// Goodreads uses ratingValue in meta or specific class
if (preg_match('/ratingValue[^>]*>(\d+\.?\d*)</si', $body, $matches)) {
return (float) $matches[1];
}
// Alternative pattern
if (preg_match('/class="RatingStatistics__rating"[^>]*>(\d+\.?\d*)</si', $body, $matches)) {
return (float) $matches[1];
}
return false;
}
/**
* Manual sync trigger
*/
public static function manual_sync() {
$sync = new self();
return $sync->sync_all_ratings();
}
/**
* Get last sync info
*/
public static function get_last_sync_info() {
return get_option('bd_bookshelf_last_sync', []);
}
}