287 lines
7.7 KiB
PHP
287 lines
7.7 KiB
PHP
<?php
|
|
// Copyright 2026 Umbra Tools (umbratools.com)
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
/**
|
|
* External Book Data API Helpers
|
|
*
|
|
* @package BD_Murphy
|
|
*/
|
|
|
|
if (!defined('ABSPATH')) {
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* Get book data from Goodreads
|
|
*
|
|
* @param string $author_id Goodreads author ID.
|
|
* @param int $timeout Response timeout in seconds.
|
|
* @return array|WP_Error
|
|
*/
|
|
function bdm_get_goodreads_books($author_id, $timeout = 30) {
|
|
if (!get_theme_mod('enable_goodreads_fetch', false)) {
|
|
return new WP_Error('consent_required', __('Goodreads integration is not enabled.', 'bd-bookshelf'));
|
|
}
|
|
|
|
if (empty($author_id)) {
|
|
return new WP_Error('missing_author_id', __('Goodreads author ID is required.', 'bd-bookshelf'));
|
|
}
|
|
|
|
$url = 'https://www.goodreads.com/author/show/' . urlencode($author_id);
|
|
$response = wp_safe_remote_get($url, array(
|
|
'timeout' => $timeout,
|
|
'headers' => array(
|
|
'User-Agent' => 'BDMurphy-BookShelf/2.0',
|
|
),
|
|
));
|
|
|
|
if (is_wp_error($response)) {
|
|
return $response;
|
|
}
|
|
|
|
$body = wp_remote_retrieve_body($response);
|
|
if (empty($body)) {
|
|
return new WP_Error('empty_response', __('Empty response from Goodreads.', 'bd-bookshelf'));
|
|
}
|
|
|
|
$books = array();
|
|
|
|
libxml_use_internal_errors(true);
|
|
$doc = new DOMDocument();
|
|
$doc->loadHTML($body);
|
|
|
|
$xpath = new DOMXPath($doc);
|
|
|
|
$book_rows = $xpath->query("//table[@class='tableList']//tr[contains(@class, 'bookalike')]");
|
|
|
|
foreach ($book_rows as $row) {
|
|
$title_node = $xpath->query(".//a[contains(@class, 'bookTitle')]", $row)->item(0);
|
|
$asin_node = $xpath->query(".//input[@class='asin']", $row)->item(0);
|
|
$rating_node = $xpath->query(".//span[@class='minirating']", $row)->item(0);
|
|
$cover_node = $xpath->query(".//img[@class='bookCover']", $row)->item(0);
|
|
|
|
$title = $title_node ? trim($title_node->textContent) : '';
|
|
$url = $title_node ? $title_node->getAttribute('href') : '';
|
|
$asin = $asin_node ? $asin_node->getAttribute('value') : '';
|
|
|
|
$rating = '';
|
|
if ($rating_node) {
|
|
$rating_text = trim($rating_node->textContent);
|
|
if (preg_match('/([\d.]+) avg rating/i', $rating_text, $matches)) {
|
|
$rating = $matches[1];
|
|
}
|
|
}
|
|
|
|
$cover = $cover_node ? $cover_node->getAttribute('src') : '';
|
|
|
|
if ($title) {
|
|
$books[] = array(
|
|
'title' => $title,
|
|
'url' => $url,
|
|
'asin' => $asin,
|
|
'rating' => $rating,
|
|
'cover' => $cover,
|
|
);
|
|
}
|
|
}
|
|
|
|
return $books;
|
|
}
|
|
|
|
/**
|
|
* Get book data from Amazon Author Central
|
|
*
|
|
* @param string $author_id Amazon Author Central author slug/page ID.
|
|
* @param int $timeout Response timeout in seconds.
|
|
* @return array|WP_Error
|
|
*/
|
|
function bdm_get_amazon_books($author_id, $timeout = 30) {
|
|
if (!get_theme_mod('enable_goodreads_fetch', false)) {
|
|
return new WP_Error('consent_required', __('External book data fetching is not enabled.', 'bd-bookshelf'));
|
|
}
|
|
|
|
if (empty($author_id)) {
|
|
return new WP_Error('missing_author_id', __('Amazon author ID is required.', 'bd-bookshelf'));
|
|
}
|
|
|
|
$url = 'https://www.amazon.com/author/' . urlencode($author_id) . '/books';
|
|
$response = wp_safe_remote_get($url, array(
|
|
'timeout' => $timeout,
|
|
'headers' => array(
|
|
'User-Agent' => 'BDMurphy-BookShelf/2.0',
|
|
),
|
|
));
|
|
|
|
if (is_wp_error($response)) {
|
|
return $response;
|
|
}
|
|
|
|
$body = wp_remote_retrieve_body($response);
|
|
if (empty($body)) {
|
|
return new WP_Error('empty_response', __('Empty response from Amazon.', 'bd-bookshelf'));
|
|
}
|
|
|
|
$books = array();
|
|
|
|
libxml_use_internal_errors(true);
|
|
$doc = new DOMDocument();
|
|
$doc->loadHTML($body);
|
|
|
|
$xpath = new DOMXPath($doc);
|
|
|
|
$product_nodes = $xpath->query("//div[contains(@class, 'product')]//a[contains(@class, 'product-title')]");
|
|
|
|
foreach ($product_nodes as $node) {
|
|
$title = trim($node->textContent);
|
|
$url = $node->getAttribute('href');
|
|
|
|
if ($title && $url) {
|
|
$books[] = array(
|
|
'title' => $title,
|
|
'url' => $url,
|
|
);
|
|
}
|
|
}
|
|
|
|
return $books;
|
|
}
|
|
|
|
/**
|
|
* Get ASIN from Amazon book URL
|
|
*
|
|
* @param string $url Amazon product URL.
|
|
* @return string|null
|
|
*/
|
|
function bdm_extract_asin_from_url($url) {
|
|
if (empty($url)) {
|
|
return null;
|
|
}
|
|
|
|
if (preg_match('/\/dp\/([A-Z0-9]{10})/i', $url, $matches)) {
|
|
return $matches[1];
|
|
}
|
|
|
|
if (preg_match('/\/gp\/product\/([A-Z0-9]{10})/i', $url, $matches)) {
|
|
return $matches[1];
|
|
}
|
|
|
|
if (preg_match('/(?:amazon\.com.*?)([A-Z0-9]{10})/i', $url, $matches)) {
|
|
return $matches[1];
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Get product data from Amazon API (via ISBN/ASIN)
|
|
*
|
|
* @param string $isbn_or_asin ISBN-10, ISBN-13, or ASIN.
|
|
* @param string $region Amazon region (com, co.uk, de, etc).
|
|
* @param int $timeout Response timeout in seconds.
|
|
* @return array|WP_Error
|
|
*/
|
|
function bdm_get_amazon_product_data($isbn_or_asin, $region = 'com', $timeout = 30) {
|
|
if (!get_theme_mod('enable_goodreads_fetch', false)) {
|
|
return new WP_Error('consent_required', __('External book data fetching is not enabled.', 'bd-bookshelf'));
|
|
}
|
|
|
|
$asin = strtoupper($isbn_or_asin);
|
|
|
|
if (strlen($asin) === 13 && is_numeric($asin)) {
|
|
$asin = bdm_isbn13_to_asin($asin);
|
|
if (!$asin) {
|
|
return new WP_Error('invalid_isbn', __('Could not convert ISBN-13 to ASIN.', 'bd-bookshelf'));
|
|
}
|
|
}
|
|
|
|
if (strlen($asin) !== 10) {
|
|
$asin = strtoupper($isbn_or_asin);
|
|
}
|
|
|
|
$domain = 'amazon.' . $region;
|
|
$url = 'https://' . $domain . '/gp/product/' . $asin;
|
|
|
|
$response = wp_safe_remote_get($url, array(
|
|
'timeout' => $timeout,
|
|
'headers' => array(
|
|
'User-Agent' => 'BDMurphy-BookShelf/2.0',
|
|
),
|
|
));
|
|
|
|
if (is_wp_error($response)) {
|
|
return $response;
|
|
}
|
|
|
|
$body = wp_remote_retrieve_body($response);
|
|
if (empty($body)) {
|
|
return new WP_Error('empty_response', __('Empty response from Amazon.', 'bd-bookshelf'));
|
|
}
|
|
|
|
libxml_use_internal_errors(true);
|
|
$doc = new DOMDocument();
|
|
$doc->loadHTML($body);
|
|
|
|
$xpath = new DOMXPath($doc);
|
|
|
|
$title = '';
|
|
$title_node = $xpath->query("//span[@id='productTitle']")->item(0);
|
|
if ($title_node) {
|
|
$title = trim($title_node->textContent);
|
|
}
|
|
|
|
$price = '';
|
|
$price_nodes = $xpath->query("//span[contains(@class, 'a-price-whole')]");
|
|
if ($price_nodes->length > 0) {
|
|
$price = trim($price_nodes->item(0)->textContent);
|
|
}
|
|
|
|
$rating = '';
|
|
$rating_nodes = $xpath->query("//span[contains(@class, 'a-icon-alt')]");
|
|
if ($rating_nodes->length > 0) {
|
|
$rating_text = trim($rating_nodes->item(0)->textContent);
|
|
if (preg_match('/^([\d.]+)/', $rating_text, $matches)) {
|
|
$rating = $matches[1];
|
|
}
|
|
}
|
|
|
|
return array(
|
|
'asin' => $asin,
|
|
'title' => $title,
|
|
'price' => $price,
|
|
'rating' => $rating,
|
|
'url' => $url,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Convert ISBN-13 to ISBN-10/ASIN
|
|
*
|
|
* @param string $isbn13 ISBN-13.
|
|
* @return string|false
|
|
*/
|
|
function bdm_isbn13_to_asin($isbn13) {
|
|
$isbn13 = preg_replace('/[^0-9X]/i', '', $isbn13);
|
|
|
|
if (strlen($isbn13) !== 13) {
|
|
return false;
|
|
}
|
|
|
|
$isbn10 = substr($isbn13, 3, 9);
|
|
|
|
$sum = 0;
|
|
for ($i = 0; $i < 9; $i++) {
|
|
$sum += ($i + 1) * intval($isbn10[$i]);
|
|
}
|
|
|
|
$remainder = $sum % 11;
|
|
$check_digit = (11 - $remainder) % 11;
|
|
if ($check_digit === 10) {
|
|
$isbn10 .= 'X';
|
|
} else {
|
|
$isbn10 .= $check_digit;
|
|
}
|
|
|
|
return $isbn10;
|
|
}
|