admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('bd_bookshelf_admin_nonce'),
'mediaTitle' => __('Select Cover Image', 'bd-bookshelf'),
'mediaButton' => __('Use This Image', 'bd-bookshelf'),
]);
}
/**
* Render Books list page
*/
public function render_books_page() {
if (!current_user_can('manage_options')) {
wp_die(__('You do not have sufficient permissions to access this page.', 'bd-bookshelf'));
}
$books = BDM_Book_Manager::get_books(['number' => 50]);
?>
title); ?>
isbn_ebook)) $isbns[] = 'eBook';
if (!empty($book->isbn_paperback)) $isbns[] = 'PB';
if (!empty($book->isbn_hardcover)) $isbns[] = 'HC';
if (!empty($book->isbn_audio)) $isbns[] = 'Audio';
echo esc_html(implode(', ', $isbns));
?>
Amazon: rating_amazon, 1); ?>
Goodreads: rating_goodreads, 1); ?>
last_sync ? date('M j, Y g:i a', strtotime($book->last_sync)) : '-'; ?>
|
100]);
$authors = BDM_Authors::get_authors(['number' => 100]);
// Get existing retail links grouped by format and site
$existing_links = [];
if ($book) {
$raw_links = BDM_Book_Manager::get_book_retail_links($book_id);
foreach ($raw_links as $link) {
$existing_links[$link->format][$link->site_name] = $link->url;
}
}
// Retail sites for URL inputs
$retail_sites = BDM_Retail_Sites::get_sites(true);
?>
100]);
?>
100]);
?>
name); ?>
description, 15)); ?>
|
get_results("SELECT * FROM {$table} ORDER BY created_at DESC");
?>
'Insufficient permissions']);
}
$book_id = absint($_POST['book_id'] ?? 0);
$data = [
'title' => sanitize_text_field($_POST['title'] ?? ''),
'description' => wp_kses_post($_POST['description'] ?? ''),
'cover_image' => esc_url_raw($_POST['cover_image'] ?? ''),
'isbn_ebook' => sanitize_text_field($_POST['isbn_ebook'] ?? ''),
'isbn_paperback' => sanitize_text_field($_POST['isbn_paperback'] ?? ''),
'isbn_hardcover' => sanitize_text_field($_POST['isbn_hardcover'] ?? ''),
'isbn_audio' => sanitize_text_field($_POST['isbn_audio'] ?? ''),
'asin_ebook' => sanitize_text_field($_POST['asin_ebook'] ?? ''),
'asin_paperback' => sanitize_text_field($_POST['asin_paperback'] ?? ''),
'asin_hardcover' => sanitize_text_field($_POST['asin_hardcover'] ?? ''),
'asin_audio' => sanitize_text_field($_POST['asin_audio'] ?? ''),
'amazon_asin' => sanitize_text_field($_POST['asin_ebook'] ?? ''),
'goodreads_id' => sanitize_text_field($_POST['goodreads_id'] ?? ''),
'series_id' => absint($_POST['series_id'] ?? 0),
'author_ids' => array_map('absint', $_POST['authors'] ?? []),
];
if ($book_id) {
$result = BDM_Book_Manager::update_book($book_id, $data);
} else {
$data['slug'] = sanitize_title($data['title']);
$book_id = BDM_Book_Manager::create_book($data);
$result = $book_id !== false;
}
if ($result) {
// Save retail URLs
$retail_urls = $_POST['retail_url'] ?? [];
$this->save_retail_urls($book_id, $retail_urls);
wp_send_json_success(['message' => 'Book saved', 'id' => $book_id]);
} else {
wp_send_json_error(['message' => 'Failed to save book']);
}
}
/**
* Save retail URLs for a book
*/
private function save_retail_urls($book_id, $retail_urls) {
global $wpdb;
$links_table = BDM_Database::table('retail_links');
$sites_table = BDM_Database::table('retail_sites');
// Delete existing non-custom links
$wpdb->delete($links_table, ['book_id' => $book_id, 'is_custom' => 0], ['%d', '%d']);
if (empty($retail_urls) || !is_array($retail_urls)) {
return;
}
foreach ($retail_urls as $format => $sites) {
if (!is_array($sites)) continue;
foreach ($sites as $site_name => $url) {
$url = esc_url_raw(trim($url));
if (empty($url)) continue;
// Find or create site
$site = $wpdb->get_row($wpdb->prepare(
"SELECT id FROM {$sites_table} WHERE name = %s",
$site_name
));
if (!$site) {
$wpdb->insert($sites_table, [
'name' => $site_name,
'url_pattern' => '',
'is_custom' => 1,
'is_active' => 1,
]);
$site_id = $wpdb->insert_id;
} else {
$site_id = $site->id;
}
$wpdb->insert($links_table, [
'book_id' => $book_id,
'site_id' => $site_id,
'format' => $format,
'url' => $url,
'is_custom' => 0,
]);
}
}
}
/**
* AJAX handler for deleting book
*/
public function ajax_delete_book() {
check_ajax_referer('bd_bookshelf_admin_nonce', 'nonce');
if (!current_user_can('manage_options')) {
wp_send_json_error(['message' => 'Insufficient permissions']);
}
$book_id = absint($_POST['id'] ?? 0);
if (!$book_id) {
wp_send_json_error(['message' => 'Invalid book ID']);
}
$result = BDM_Book_Manager::delete_book($book_id);
if ($result) {
wp_send_json_success(['message' => 'Book deleted']);
} else {
wp_send_json_error(['message' => 'Failed to delete book']);
}
}
/**
* AJAX handler for getting author
*/
public function ajax_get_author() {
check_ajax_referer('bd_bookshelf_admin_nonce', 'nonce');
if (!current_user_can('manage_options')) {
wp_send_json_error(['message' => 'Insufficient permissions']);
}
$author_id = absint($_POST['id'] ?? 0);
if (!$author_id) {
wp_send_json_error(['message' => 'Invalid author ID']);
}
$author = BDM_Authors::get_author($author_id);
if (!$author) {
wp_send_json_error(['message' => 'Author not found']);
}
wp_send_json_success($author);
}
/**
* AJAX handler for saving author
*/
public function ajax_save_author() {
check_ajax_referer('bd_bookshelf_admin_nonce', 'author_nonce');
if (!current_user_can('manage_options')) {
wp_send_json_error(['message' => 'Insufficient permissions']);
}
$author_id = absint($_POST['author_id'] ?? 0);
$data = [
'display_name' => sanitize_text_field($_POST['display_name'] ?? ''),
'email' => sanitize_email($_POST['email'] ?? ''),
'website' => esc_url_raw($_POST['website'] ?? ''),
'bio' => wp_kses_post($_POST['bio'] ?? ''),
'goodreads_author_id' => sanitize_text_field($_POST['goodreads_author_id'] ?? ''),
'photo_url' => esc_url_raw($_POST['photo_url'] ?? ''),
];
if ($author_id) {
$result = BDM_Authors::update_author($author_id, $data);
} else {
$author_id = BDM_Authors::create_author($data);
$result = $author_id !== false;
}
if ($result) {
wp_send_json_success(['message' => 'Author saved', 'id' => $author_id]);
} else {
wp_send_json_error(['message' => 'Failed to save author']);
}
}
/**
* AJAX handler for deleting author
*/
public function ajax_delete_author() {
check_ajax_referer('bd_bookshelf_admin_nonce', 'nonce');
if (!current_user_can('manage_options')) {
wp_send_json_error(['message' => 'Insufficient permissions']);
}
$author_id = absint($_POST['id'] ?? 0);
if (!$author_id) {
wp_send_json_error(['message' => 'Invalid author ID']);
}
$result = BDM_Authors::delete_author($author_id);
if ($result) {
wp_send_json_success(['message' => 'Author deleted']);
} else {
wp_send_json_error(['message' => 'Failed to delete author']);
}
}
/**
* AJAX handler for getting series
*/
public function ajax_get_series() {
check_ajax_referer('bd_bookshelf_admin_nonce', 'nonce');
if (!current_user_can('manage_options')) {
wp_send_json_error(['message' => 'Insufficient permissions']);
}
$series_id = absint($_POST['id'] ?? 0);
if (!$series_id) {
wp_send_json_error(['message' => 'Invalid series ID']);
}
$series = BDM_Series::get_series_by_id($series_id);
if (!$series) {
wp_send_json_error(['message' => 'Series not found']);
}
wp_send_json_success($series);
}
/**
* AJAX handler for saving series
*/
public function ajax_save_series() {
check_ajax_referer('bd_bookshelf_admin_nonce', 'series_nonce');
if (!current_user_can('manage_options')) {
wp_send_json_error(['message' => 'Insufficient permissions']);
}
$series_id = absint($_POST['series_id'] ?? 0);
$data = [
'name' => sanitize_text_field($_POST['name'] ?? ''),
'description' => wp_kses_post($_POST['description'] ?? ''),
];
if ($series_id) {
$result = BDM_Series::update_series($series_id, $data);
} else {
$series_id = BDM_Series::create_series($data);
$result = $series_id !== false;
}
if ($result) {
wp_send_json_success(['message' => 'Series saved', 'id' => $series_id]);
} else {
wp_send_json_error(['message' => 'Failed to save series']);
}
}
/**
* AJAX handler for deleting series
*/
public function ajax_delete_series() {
check_ajax_referer('bd_bookshelf_admin_nonce', 'nonce');
if (!current_user_can('manage_options')) {
wp_send_json_error(['message' => 'Insufficient permissions']);
}
$series_id = absint($_POST['id'] ?? 0);
if (!$series_id) {
wp_send_json_error(['message' => 'Invalid series ID']);
}
$result = BDM_Series::delete_series($series_id);
if ($result) {
wp_send_json_success(['message' => 'Series deleted']);
} else {
wp_send_json_error(['message' => 'Failed to delete series']);
}
}
/**
* AJAX handler for getting shortcode
*/
public function ajax_get_shortcode() {
check_ajax_referer('bd_bookshelf_admin_nonce', 'nonce');
if (!current_user_can('manage_options')) {
wp_send_json_error(['message' => 'Insufficient permissions']);
}
$shortcode_id = absint($_POST['id'] ?? 0);
if (!$shortcode_id) {
wp_send_json_error(['message' => 'Invalid shortcode ID']);
}
global $wpdb;
$table = BDM_Database::table('shortcodes');
$shortcode = $wpdb->get_row(
$wpdb->prepare(
"SELECT * FROM {$table} WHERE id = %d",
$shortcode_id
)
);
if (!$shortcode) {
wp_send_json_error(['message' => 'Shortcode not found']);
}
wp_send_json_success($shortcode);
}
/**
* AJAX handler for saving shortcode
*/
public function ajax_save_shortcode() {
check_ajax_referer('bd_bookshelf_admin_nonce', 'shortcode_nonce');
if (!current_user_can('manage_options')) {
wp_send_json_error(['message' => 'Insufficient permissions']);
}
$shortcode_id = absint($_POST['shortcode_id'] ?? 0);
$data = [
'slug' => sanitize_title($_POST['slug'] ?? ''),
'target_url' => esc_url_raw($_POST['target_url'] ?? ''),
'redirect_type' => sanitize_text_field($_POST['redirect_type'] ?? '301'),
'description' => sanitize_text_field($_POST['description'] ?? ''),
];
if (empty($data['slug']) || empty($data['target_url'])) {
wp_send_json_error(['message' => 'Slug and Target URL are required']);
}
if ($shortcode_id) {
$result = BDM_Shortcodes::update_shortcode($shortcode_id, $data);
} else {
$shortcode_id = BDM_Shortcodes::create_shortcode($data['slug'], $data['target_url'], $data['redirect_type'], $data['description']);
$result = $shortcode_id !== false;
}
if ($result) {
wp_send_json_success(['message' => 'Shortcode saved', 'id' => $shortcode_id]);
} else {
wp_send_json_error(['message' => 'Failed to save shortcode']);
}
}
/**
* AJAX handler for deleting shortcode
*/
public function ajax_delete_shortcode() {
check_ajax_referer('bd_bookshelf_admin_nonce', 'nonce');
if (!current_user_can('manage_options')) {
wp_send_json_error(['message' => 'Insufficient permissions']);
}
$shortcode_id = absint($_POST['id'] ?? 0);
if (!$shortcode_id) {
wp_send_json_error(['message' => 'Invalid shortcode ID']);
}
$result = BDM_Shortcodes::delete_shortcode($shortcode_id);
if ($result) {
wp_send_json_success(['message' => 'Shortcode deleted']);
} else {
wp_send_json_error(['message' => 'Failed to delete shortcode']);
}
}
/**
* AJAX handler for saving sites/affiliate codes
*/
public function ajax_save_sites() {
check_ajax_referer('bd_bookshelf_admin_nonce', 'sites_nonce');
if (!current_user_can('manage_options')) {
wp_send_json_error(['message' => 'Insufficient permissions']);
}
if (!empty($_POST['affiliate'])) {
global $wpdb;
$table = BDM_Database::table('retail_sites');
foreach ($_POST['affiliate'] as $site_id => $code) {
$wpdb->update(
$table,
['affiliate_code' => sanitize_text_field($code)],
['id' => absint($site_id)],
['%s'],
['%d']
);
}
}
wp_send_json_success(['message' => 'Affiliate codes saved']);
}
/**
* AJAX handler for saving settings
*/
public function ajax_save_settings() {
check_ajax_referer('bd_bookshelf_save_settings', 'nonce');
if (!current_user_can('manage_options')) {
wp_send_json_error(['message' => 'Insufficient permissions']);
}
BDM_GDPR::update_consent_text($_POST['gdpr_text'] ?? '');
wp_send_json_success(['message' => 'Settings saved']);
}
/**
* AJAX handler for CSV import
*/
public function ajax_import_csv() {
check_ajax_referer('bd_bookshelf_admin_nonce', 'import_nonce');
if (!current_user_can('manage_options')) {
wp_send_json_error(['message' => 'Insufficient permissions']);
}
if (empty($_FILES['csv_file']['tmp_name'])) {
wp_send_json_error(['message' => 'No file uploaded']);
}
$file = $_FILES['csv_file']['tmp_name'];
$handle = fopen($file, 'r');
if (!$handle) {
wp_send_json_error(['message' => 'Unable to open file']);
}
// Get header row
$header = fgetcsv($handle);
$imported = 0;
$updated = 0;
$skipped = 0;
$errors = [];
while (($row = fgetcsv($handle)) !== false) {
$data = array_combine($header, $row);
if (empty($data['title'])) {
$errors[] = 'Row ' . ($imported + $skipped + 1) . ': Missing title';
continue;
}
// Check if already imported - update instead of skip
$existing = BDM_Book_Manager::get_book_by_title($data['title']);
$book_data = [
'title' => $data['title'],
'slug' => sanitize_title($data['title']),
'description' => $data['description'] ?? '',
'isbn_ebook' => $data['isbn_ebook'] ?? '',
'isbn_paperback' => $data['isbn_paperback'] ?? '',
'isbn_hardcover' => $data['isbn_hardcover'] ?? '',
'isbn_audio' => $data['isbn_audio'] ?? '',
'amazon_asin' => $data['amazon_asin'] ?? '',
'goodreads_id' => $data['goodreads_id'] ?? '',
];
if ($existing) {
// Update existing book - fill in empty fields
$update_data = [];
foreach ($book_data as $key => $value) {
if ($key === 'title' || $key === 'slug') continue;
if (!empty($value) && empty($existing->$key)) {
$update_data[$key] = $value;
}
}
if (!empty($update_data)) {
BDM_Book_Manager::update_book($existing->id, $update_data);
$updated++;
} else {
$skipped++;
}
} else {
// Create new book
$book_id = BDM_Book_Manager::create_book($book_data);
if ($book_id) {
$imported++;
} else {
$errors[] = 'Row ' . ($imported + $skipped + 1) . ': Failed to create book';
}
}
}
fclose($handle);
$message = sprintf('Imported %d new books, updated %d existing, skipped %d', $imported, $updated, $skipped);
if (!empty($errors)) {
$message .= '. ' . count($errors) . ' errors.';
}
wp_send_json_success([
'message' => $message,
'imported' => $imported,
'updated' => $updated,
'skipped' => $skipped,
'errors' => $errors,
]);
}
/**
* AJAX handler for posts import
*/
public function ajax_import_posts() {
check_ajax_referer('bd_bookshelf_admin_nonce', 'import_posts_nonce');
if (!current_user_can('manage_options')) {
wp_send_json_error(['message' => 'Insufficient permissions']);
}
$post_type = sanitize_text_field($_POST['post_type'] ?? 'post');
$limit = absint($_POST['limit'] ?? 100);
$posts = get_posts([
'post_type' => $post_type,
'post_status' => 'publish',
'numberposts' => $limit,
]);
$imported = 0;
$skipped = 0;
foreach ($posts as $post) {
// Check if already imported
$existing = BDM_Book_Manager::get_book_by_title($post->post_title);
if ($existing) {
$skipped++;
continue;
}
// Import book
$book_id = BDM_Book_Manager::create_book([
'title' => $post->post_title,
'description' => $post->post_content,
]);
if ($book_id) {
// Link to WordPress post
update_post_meta($post->ID, '_bd_bookshelf_book_id', $book_id);
$imported++;
}
}
wp_send_json_success([
'message' => sprintf('Imported %d books, skipped %d', $imported, $skipped),
'imported' => $imported,
'skipped' => $skipped,
]);
}
/**
* AJAX handler for manual sync
*/
public function ajax_sync_ratings() {
check_ajax_referer('bd_bookshelf_admin_nonce', 'nonce');
if (!current_user_can('manage_options')) {
wp_send_json_error(['message' => 'Insufficient permissions']);
}
$result = BDM_Rating_Sync::manual_sync();
wp_send_json_success([
'message' => sprintf('Synced %d books, failed %d', $result['synced'], $result['failed']),
'result' => $result,
]);
}
}