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); ?>

cover_image)) : ?>

authors)) { foreach ($book->authors as $ba) { $book_authors[] = $ba->author_id; } } for ($i = 0; $i < 3; $i++) : $selected = $book_authors[$i] ?? 0; ?>

[ 'label' => 'eBook', 'sites' => ['Amazon', 'Apple Books', 'Google Play', 'Barnes & Noble', 'Kobo'], ], 'paperback' => [ 'label' => 'Paperback', 'sites' => ['Amazon', 'Barnes & Noble', 'Kobo'], ], 'hardcover' => [ 'label' => 'Hardcover', 'sites' => ['Amazon', 'Barnes & Noble', 'Kobo'], ], 'audio' => [ 'label' => 'Audio', 'sites' => ['Amazon', 'Apple Books', 'Barnes & Noble', 'Kobo'], ], ]; ?> $format_config) : ?>

100]); ?>


display_name); ?> email); ?> website ? '' . esc_html($author->website) . '' : '-'; ?> goodreads_author_id); ?> |
100]); ?>


name); ?> description, 15)); ?> |

name); ?> url_pattern); ?> affiliate_param); ?>

get_results("SELECT * FROM {$table} ORDER BY created_at DESC"); ?>


[slug); ?>] target_url); ?> redirect_type); ?> click_count); ?> |

'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, ]); } }