Files
Bookshelf/bd-bookshelfhelper/inc/class-admin.php
2026-10-18 19:01:01 -07:00

1219 lines
54 KiB
PHP

<?php
// Copyright 2026 Umbra Tools (umbratools.com)
// SPDX-License-Identifier: Apache-2.0
/**
* Admin Class
*
* @package BD_BookShelfHelper
*/
if (!defined('ABSPATH')) {
exit;
}
class BD_BookShelfHelper_Admin {
private static $instance = null;
/**
* Get instance
*
* @return BD_BookShelfHelper_Admin
*/
public static function instance() {
if (null === self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Constructor
*/
public function __construct() {
add_action('admin_menu', array($this, 'add_menu_pages'));
add_action('admin_enqueue_scripts', array($this, 'enqueue_assets'));
add_action('add_meta_boxes_book', array($this, 'add_meta_boxes'));
add_action('save_post_book', array($this, 'save_meta_boxes'));
add_action('wp_ajax_bdm_update_links', array($this, 'ajax_update_links'));
add_action('wp_ajax_bdm_autosave', array($this, 'ajax_autosave'));
}
/**
* Add admin menu pages
*/
public function add_menu_pages() {
add_menu_page(
esc_html__('BD BookHelper', 'bd-bookshelfhelper'),
esc_html__('BD BookHelper', 'bd-bookshelfhelper'),
'edit_posts',
'bd-bookshelf',
'',
'dashicons-book-alt',
30
);
add_submenu_page(
'bd-bookshelf',
esc_html__('Books', 'bd-bookshelfhelper'),
esc_html__('All Books', 'bd-bookshelfhelper'),
'edit_posts',
'edit.php?post_type=book'
);
add_submenu_page(
'bd-bookshelf',
esc_html__('Add Book', 'bd-bookshelfhelper'),
esc_html__('Add Book', 'bd-bookshelfhelper'),
'edit_posts',
'post-new.php?post_type=book'
);
add_submenu_page(
'bd-bookshelf',
esc_html__('Book Data Entry', 'bd-bookshelfhelper'),
esc_html__('Data Entry', 'bd-bookshelfhelper'),
'edit_posts',
'bd-bookshelf-data-entry',
array($this, 'data_entry_page')
);
add_submenu_page(
'bd-bookshelf',
esc_html__('Series', 'bd-bookshelfhelper'),
esc_html__('Series', 'bd-bookshelfhelper'),
'manage_categories',
'edit-tags.php?taxonomy=series&post_type=book'
);
add_submenu_page(
'bd-bookshelf',
esc_html__('Pen Names', 'bd-bookshelfhelper'),
esc_html__('Pen Names', 'bd-bookshelfhelper'),
'manage_options',
'bd-bookshelf-pen-names',
array($this, 'pen_names_page')
);
add_submenu_page(
'bd-bookshelf',
esc_html__('Settings', 'bd-bookshelfhelper'),
esc_html__('Settings', 'bd-bookshelfhelper'),
'manage_options',
'bd-bookshelf-settings',
array($this, 'settings_page')
);
add_submenu_page(
'bd-bookshelf',
esc_html__('Migrate BD Books', 'bd-bookshelfhelper'),
esc_html__('Migrate', 'bd-bookshelfhelper'),
'manage_options',
'bd-bookshelf-migrate',
array($this, 'migrate_page')
);
}
/**
* Enqueue admin assets
*
* @param string $hook Current admin page.
*/
public function enqueue_assets($hook) {
$screen = get_current_screen();
if ($screen && 'book' === $screen->post_type) {
wp_enqueue_media();
}
if (strpos($hook, 'bd-bookshelf') !== false) {
wp_enqueue_style(
'bd-bookshelf-admin',
BD_BHS_URI . 'assets/css/admin.css',
array(),
BD_BHS_VERSION
);
wp_enqueue_script(
'bd-bookshelf-admin',
BD_BHS_URI . 'assets/js/admin.js',
array('jquery'),
BD_BHS_VERSION,
true
);
wp_localize_script('bd-bookshelf-admin', 'bdmAdmin', array(
'ajaxUrl' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('bd_bookshelf_admin'),
));
}
}
/**
* Add meta boxes
*/
public function add_meta_boxes() {
add_meta_box(
'bd_book_details',
esc_html__('Book Details', 'bd-bookshelfhelper'),
array($this, 'book_details_meta_box'),
'book',
'normal',
'high'
);
add_meta_box(
'bd_book_seo',
esc_html__('SEO Settings', 'bd-bookshelfhelper'),
array($this, 'book_seo_meta_box'),
'book',
'side',
'low'
);
}
/**
* Book details meta box content
*
* @param WP_Post $post Post object.
*/
public function book_details_meta_box($post) {
$languages = BD_BookShelfHelper::get_supported_languages();
wp_nonce_field('bd_book_meta', 'bd_book_nonce');
echo '<div class="bd-book-meta-box">';
// Core Fields
echo '<h3>' . esc_html__('Core Fields', 'bd-bookshelfhelper') . '</h3>';
// Pen Name
echo '<p><label>' . esc_html__('Pen Name', 'bd-bookshelfhelper') . '</label>';
echo '<select name="pen_name_id">';
echo '<option value="">' . esc_html__('B.D. Murphy (Default)', 'bd-bookshelfhelper') . '</option>';
global $wpdb;
$table_name = $wpdb->prefix . 'bd_pen_names';
$pen_names = $wpdb->get_results("SELECT id, pen_name FROM {$table_name} WHERE active = 1 ORDER BY pen_name");
$pen_name_id = get_post_meta($post->ID, 'pen_name_id', true);
if ($pen_names) {
foreach ($pen_names as $pn) {
echo '<option value="' . esc_attr($pn->id) . '"' . selected($pen_name_id, $pn->id, false) . '>' . esc_html($pn->pen_name) . '</option>';
}
}
echo '</select></p>';
// ASIN and ISBN
echo '<p><label>' . esc_html__('ASIN (Amazon)', 'bd-bookshelfhelper') . '</label>';
echo '<input type="text" name="asin" value="' . esc_attr(get_post_meta($post->ID, 'asin', true)) . '" class="regular-text"></p>';
echo '<p><label>' . esc_html__('ISBN-13 (Print)', 'bd-bookshelfhelper') . '</label>';
echo '<input type="text" name="isbn" value="' . esc_attr(get_post_meta($post->ID, 'isbn', true)) . '" class="regular-text"></p>';
// Short Link
echo '<p><label>' . esc_html__('Short Link Slug', 'bd-bookshelfhelper') . '</label>';
echo '<input type="text" name="short_link_slug" value="' . esc_attr(get_post_meta($post->ID, 'short_link_slug', true)) . '" class="regular-text">';
echo '<p class="description">' . esc_html__('Auto-generated if empty. E.g., "ph" for "Pandemic Hacker"', 'bd-bookshelfhelper') . '</p></p>';
// Series
$series_terms = get_terms(array('taxonomy' => 'series', 'hide_empty' => false));
if ($series_terms) {
echo '<p><label>' . esc_html__('Series', 'bd-bookshelfhelper') . '</label>';
echo '<select name="series_id">';
echo '<option value="">' . esc_html__('None / Standalone', 'bd-bookshelfhelper') . '</option>';
$current_series = get_the_terms($post->ID, 'series');
$current_series_id = ($current_series && !is_wp_error($current_series)) ? $current_series[0]->term_id : 0;
foreach ($series_terms as $term) {
printf('<option value="%s"%s>%s</option>', esc_attr($term->term_id), selected($current_series_id, $term->term_id, false), esc_html($term->name));
}
echo '</select></p>';
if ($current_series_id) {
echo '<p><label>' . esc_html__('Volume Number', 'bd-bookshelfhelper') . '</label>';
echo '<input type="number" name="volume_number" value="' . esc_attr(get_post_meta($post->ID, 'volume_number', true)) . '" min="1"></p>';
}
}
// Language Content
echo '<h3>' . esc_html__('Language Content', 'bd-bookshelfhelper') . '</h3>';
foreach ($languages as $code => $name) {
echo '<fieldset class="bd-lang-fieldset">';
echo '<legend>' . esc_html($name) . ' (' . strtoupper($code) . ')</legend>';
echo '<p><label>' . esc_html__('Title', 'bd-bookshelfhelper') . '</label>';
echo '<input type="text" name="title_' . esc_attr($code) . '" value="' . esc_attr(get_post_meta($post->ID, 'title_' . $code, true)) . '" class="large-text"></p>';
echo '<p><label>' . esc_html__('Back Cover Blurb', 'bd-bookshelfhelper') . '</label>';
echo '<textarea name="blurb_' . esc_attr($code) . '" rows="4" class="large-text">' . esc_textarea(get_post_meta($post->ID, 'blurb_' . $code, true)) . '</textarea></p>';
echo '<p><label>' . esc_html__('Preview Excerpt', 'bd-bookshelfhelper') . '</label>';
echo '<textarea name="preview_excerpt_' . esc_attr($code) . '" rows="3" class="large-text">' . esc_textarea(get_post_meta($post->ID, 'preview_excerpt_' . $code, true)) . '</textarea></p>';
echo '<p><label>' . esc_html__('Pull Quotes (1-5)', 'bd-bookshelfhelper') . '</label>';
for ($i = 1; $i <= 5; $i++) {
printf('<input type="text" name="pull_quote_%s_%d" value="%s" class="large-text" placeholder="%s">',
esc_attr($code), $i,
esc_attr(get_post_meta($post->ID, 'pull_quote_' . $code . '_' . $i, true)),
esc_attr__('Quote ' . $i, 'bd-bookshelfhelper')
);
}
echo '</p>';
echo '</fieldset>';
}
// Retailer Links
echo '<h3>' . esc_html__('Retailer Links', 'bd-bookshelfhelper') . '</h3>';
$retailer_fields = array(
'ebook_url_amazon' => 'Amazon (Ebook)',
'ebook_url_apple' => 'Apple Books',
'ebook_url_google' => 'Google Play',
'ebook_url_barnes' => 'Barnes & Noble (Ebook)',
'ebook_url_kobo' => 'Kobo',
'paperback_url_barnes' => 'Barnes & Noble (Paperback)',
'hardback_url_barnes' => 'Barnes & Noble (Hardback)',
);
foreach ($retailer_fields as $field => $label) {
printf('<p><label>%s</label><input type="url" name="%s" value="%s" class="large-text"></p>',
esc_html($label), esc_attr($field), esc_url(get_post_meta($post->ID, $field, true))
);
}
// Ratings
echo '<h3>' . esc_html__('Ratings', 'bd-bookshelfhelper') . '</h3>';
echo '<p><label>' . esc_html__('Amazon Rating', 'bd-bookshelfhelper') . '</label>';
echo '<input type="text" name="amazon_rating" value="' . esc_attr(get_post_meta($post->ID, 'amazon_rating', true)) . '" placeholder="4.5"></p>';
echo '<p><label>' . esc_html__('Goodreads Rating', 'bd-bookshelfhelper') . '</label>';
echo '<input type="text" name="goodreads_rating" value="' . esc_attr(get_post_meta($post->ID, 'goodreads_rating', true)) . '" placeholder="4.2"></p>';
echo '</div>';
}
/**
* SEO meta box content
*
* @param WP_Post $post Post object.
*/
public function book_seo_meta_box($post) {
echo '<p><label>' . esc_html__('SEO Title', 'bd-bookshelfhelper') . '</label>';
echo '<input type="text" name="seo_title" value="' . esc_attr(get_post_meta($post->ID, 'seo_title', true)) . '" class="large-text"></p>';
echo '<p><label>' . esc_html__('SEO Description', 'bd-bookshelfhelper') . '</label>';
echo '<textarea name="seo_description" rows="4" class="large-text">' . esc_textarea(get_post_meta($post->ID, 'seo_description', true)) . '</textarea></p>';
}
/**
* Data Entry page - Enhanced interface per spec 5.2.1
*/
public function data_entry_page() {
$post_id = isset($_GET['post_id']) ? absint($_GET['post_id']) : 0;
$languages = BD_BookShelfHelper::get_supported_languages();
// Get all books for selection
global $wpdb;
$books = $wpdb->get_results(
"SELECT ID, post_title FROM {$wpdb->posts} WHERE post_type = 'book' AND post_status IN ('publish', 'draft', 'pending') ORDER BY post_title"
);
// If no post_id provided and no books exist, show selection
if (!$post_id && empty($books)) {
echo '<div class="wrap">';
echo '<h1>' . esc_html__('Book Data Entry', 'bd-bookshelfhelper') . '</h1>';
echo '<div class="bd-status-message warning">' . esc_html__('No books found. Please create a book first.', 'bd-bookshelfhelper') . '</div>';
echo '<p><a href="' . esc_url(admin_url('post-new.php?post_type=book')) . '" class="button button-primary">' . esc_html__('Create New Book', 'bd-bookshelfhelper') . '</a></p>';
echo '</div>';
return;
}
// Handle post selection
if (isset($_POST['bd_select_book']) && isset($_POST['selected_book_id'])) {
$post_id = absint($_POST['selected_book_id']);
}
// If still no post_id, show selection
if (!$post_id) {
echo '<div class="wrap">';
echo '<h1>' . esc_html__('Book Data Entry', 'bd-bookshelfhelper') . '</h1>';
echo '<form method="post">';
echo '<table class="form-table">';
echo '<tr><th>' . esc_html__('Select Book', 'bd-bookshelfhelper') . '</th>';
echo '<td><select name="selected_book_id">';
echo '<option value="">' . esc_html__('Select a book...', 'bd-bookshelfhelper') . '</option>';
foreach ($books as $book) {
echo '<option value="' . esc_attr($book->ID) . '">' . esc_html($book->post_title) . '</option>';
}
echo '</select></td></tr>';
echo '</table>';
submit_button(esc_html__('Select Book', 'bd-bookshelfhelper'), 'primary', 'bd_select_book');
echo '</form>';
echo '</div>';
return;
}
$post = get_post($post_id);
if (!$post || 'book' !== $post->post_type) {
echo '<div class="wrap">';
echo '<div class="bd-status-message error">' . esc_html__('Invalid book selected.', 'bd-bookshelfhelper') . '</div>';
echo '</div>';
return;
}
wp_nonce_field('bd_data_entry', 'bd_data_entry_nonce');
echo '<div class="wrap">';
echo '<h1>' . sprintf(esc_html__('Data Entry: %s', 'bd-bookshelfhelper'), esc_html($post->post_title)) . '</h1>';
// Language tabs
echo '<div class="bd-language-tabs">';
foreach ($languages as $code => $name) {
printf('<button type="button" class="bd-lang-tab%s" data-lang="%s">%s (%s)</button>',
'en' === $code ? ' active' : '',
esc_attr($code),
esc_html($name),
strtoupper($code)
);
}
echo '</div>';
echo '<form id="bd-data-entry-form" method="post">';
echo '<input type="hidden" name="post_id" value="' . esc_attr($post_id) . '">';
wp_nonce_field('bd_data_entry_save', 'bd_data_entry_save_nonce');
// Section 1: Basic Information
$this->render_section_header('basic', __('Basic Information', 'bd-bookshelfhelper'), true);
echo '<div class="bd-section-content" id="bd-section-basic">';
echo '<div class="bd-field-row">';
echo '<div class="bd-field required">';
echo '<label for="post_title">' . esc_html__('Book Title', 'bd-bookshelfhelper') . '</label>';
echo '<input type="text" id="post_title" name="post_title" value="' . esc_attr($post->post_title) . '" class="large-text" required>';
echo '<span class="bd-hint">' . esc_html__('The full title of the book as it appears on the cover.', 'bd-bookshelfhelper') . '</span>';
echo '</div>';
echo '<div class="bd-field">';
echo '<label for="pen_name_id">' . esc_html__('Pen Name', 'bd-bookshelfhelper') . '</label>';
echo '<select id="pen_name_id" name="pen_name_id">';
echo '<option value="">' . esc_html__('B.D. Murphy (Default)', 'bd-bookshelfhelper') . '</option>';
$pen_names = $wpdb->get_results("SELECT id, pen_name FROM {$wpdb->prefix}bd_pen_names WHERE active = 1 ORDER BY pen_name");
$pen_name_id = get_post_meta($post_id, 'pen_name_id', true);
if ($pen_names) {
foreach ($pen_names as $pn) {
echo '<option value="' . esc_attr($pn->id) . '"' . selected($pen_name_id, $pn->id, false) . '>' . esc_html($pn->pen_name) . '</option>';
}
}
echo '</select>';
echo '<span class="bd-hint">' . esc_html__('The author name to display with this book.', 'bd-bookshelfhelper') . '</span>';
echo '</div>';
echo '</div>';
echo '<div class="bd-field-row">';
echo '<div class="bd-field">';
echo '<label for="publish_date">' . esc_html__('Publication Date', 'bd-bookshelfhelper') . '</label>';
echo '<input type="date" id="publish_date" name="publish_date" value="' . esc_attr(get_post_meta($post_id, 'publish_date', true)) . '">';
echo '<span class="bd-hint">' . esc_html__('Format: YYYY-MM-DD (e.g., 2024-01-15)', 'bd-bookshelfhelper') . '</span>';
echo '</div>';
echo '<div class="bd-field">';
echo '<label for="sort_order">' . esc_html__('Sort Order', 'bd-bookshelfhelper') . '</label>';
echo '<input type="number" id="sort_order" name="sort_order" value="' . esc_attr(get_post_meta($post_id, 'sort_order', true)) . '" min="0">';
echo '<span class="bd-hint">' . esc_html__('Manual sort position (lower numbers appear first).', 'bd-bookshelfhelper') . '</span>';
echo '</div>';
echo '</div>';
echo '</div>'; // End Basic Information
// Section 2: Identifiers
$this->render_section_header('identifiers', __('Identifiers', 'bd-bookshelfhelper'), true);
echo '<div class="bd-section-content" id="bd-section-identifiers">';
echo '<div class="bd-field-row">';
echo '<div class="bd-field">';
echo '<label for="isbn">' . esc_html__('ISBN-13 (Print)', 'bd-bookshelfhelper') . '</label>';
echo '<input type="text" id="isbn" name="isbn" value="' . esc_attr(get_post_meta($post_id, 'isbn', true)) . '" placeholder="9781234567890">';
echo '<span class="bd-hint">' . esc_html__('13 digits, no dashes (e.g., 9781234567890)', 'bd-bookshelfhelper') . '</span>';
echo '</div>';
echo '<div class="bd-field">';
echo '<label for="asin">' . esc_html__('ASIN (Amazon)', 'bd-bookshelfhelper') . '</label>';
echo '<input type="text" id="asin" name="asin" value="' . esc_attr(get_post_meta($post_id, 'asin', true)) . '" placeholder="B001234567">';
echo '<span class="bd-hint">' . esc_html__('10 alphanumeric characters (e.g., B001234567)', 'bd-bookshelfhelper') . '</span>';
echo '</div>';
echo '</div>';
echo '</div>'; // End Identifiers
// Section 3: Series & Order
$this->render_section_header('series', __('Series & Order', 'bd-bookshelfhelper'), true);
echo '<div class="bd-section-content" id="bd-section-series">';
$series_terms = get_terms(array('taxonomy' => 'series', 'hide_empty' => false));
echo '<div class="bd-field">';
echo '<label for="series_id">' . esc_html__('Series', 'bd-bookshelfhelper') . '</label>';
echo '<select id="series_id" name="series_id">';
echo '<option value="">' . esc_html__('None / Standalone', 'bd-bookshelfhelper') . '</option>';
$current_series = get_the_terms($post_id, 'series');
$current_series_id = ($current_series && !is_wp_error($current_series)) ? $current_series[0]->term_id : 0;
if ($series_terms) {
foreach ($series_terms as $term) {
printf('<option value="%s"%s>%s</option>', esc_attr($term->term_id), selected($current_series_id, $term->term_id, false), esc_html($term->name));
}
}
echo '</select>';
echo '<span class="bd-hint">' . esc_html__('Select the series this book belongs to, or leave blank for standalone.', 'bd-bookshelfhelper') . '</span>';
echo '</div>';
echo '<div class="bd-field">';
echo '<label for="volume_number">' . esc_html__('Volume Number', 'bd-bookshelfhelper') . '</label>';
echo '<input type="number" id="volume_number" name="volume_number" value="' . esc_attr(get_post_meta($post_id, 'volume_number', true)) . '" min="1">';
echo '<span class="bd-hint">' . esc_html__('Order within the series (e.g., 1 for Book 1).', 'bd-bookshelfhelper') . '</span>';
echo '</div>';
echo '</div>'; // End Series
// Section 4: Short Link
$this->render_section_header('shortlink', __('Short Link', 'bd-bookshelfhelper'), false);
echo '<div class="bd-section-content" id="bd-section-shortlink">';
echo '<div class="bd-field">';
echo '<label for="short_link_slug">' . esc_html__('Short Link Slug', 'bd-bookshelfhelper') . '</label>';
$current_slug = get_post_meta($post_id, 'short_link_slug', true);
if (empty($current_slug)) {
$current_slug = $this->generate_short_slug($post->post_title);
}
echo '<code>' . esc_url(home_url('/')) . '</code>';
echo '<input type="text" id="short_link_slug" name="short_link_slug" value="' . esc_attr($current_slug) . '">';
echo '<span class="bd-hint">' . esc_html__('Auto-generated from title if empty. E.g., "ph" for "Pandemic Hacker"', 'bd-bookshelfhelper') . '</span>';
echo '</div>';
echo '</div>'; // End Short Link
// Section 5: Language Content (tabs for each language)
$this->render_section_header('content', __('Content (by Language)', 'bd-bookshelfhelper'), true);
echo '<div class="bd-section-content" id="bd-section-content">';
foreach ($languages as $code => $name) {
echo '<div class="bd-language-content' . ('en' === $code ? ' active' : '') . '" id="bd-lang-' . esc_attr($code) . '">';
echo '<h3>' . esc_html($name) . ' (' . strtoupper($code) . ')</h3>';
echo '<div class="bd-field">';
echo '<label for="title_' . esc_attr($code) . '">' . esc_html__('Title', 'bd-bookshelfhelper') . '</label>';
echo '<input type="text" id="title_' . esc_attr($code) . '" name="title_' . esc_attr($code) . '" value="' . esc_attr(get_post_meta($post_id, 'title_' . $code, true)) . '" class="large-text">';
echo '<span class="bd-hint">' . esc_html__('Title in this language (if different from default).', 'bd-bookshelfhelper') . '</span>';
echo '</div>';
echo '<div class="bd-field">';
echo '<label for="blurb_' . esc_attr($code) . '">' . esc_html__('Back Cover Blurb', 'bd-bookshelfhelper') . '</label>';
echo '<textarea id="blurb_' . esc_attr($code) . '" name="blurb_' . esc_attr($code) . '" rows="6" class="large-text">' . esc_textarea(get_post_meta($post_id, 'blurb_' . $code, true)) . '</textarea>';
echo '<span class="bd-hint">' . esc_html__('The book description shown on the back cover and product pages.', 'bd-bookshelfhelper') . '</span>';
echo '</div>';
echo '<div class="bd-field">';
echo '<label for="preview_excerpt_' . esc_attr($code) . '">' . esc_html__('Preview Excerpt', 'bd-bookshelfhelper') . '</label>';
echo '<textarea id="preview_excerpt_' . esc_attr($code) . '" name="preview_excerpt_' . esc_attr($code) . '" rows="4" class="large-text">' . esc_textarea(get_post_meta($post_id, 'preview_excerpt_' . $code, true)) . '</textarea>';
echo '<span class="bd-hint">' . esc_html__('A short excerpt (50-100 words) for previews and "Look Inside" feature.', 'bd-bookshelfhelper') . '</span>';
echo '</div>';
echo '<div class="bd-field">';
echo '<label>' . esc_html__('Pull Quotes (up to 5)', 'bd-bookshelfhelper') . '</label>';
echo '<div class="bd-pull-quotes">';
for ($i = 1; $i <= 5; $i++) {
printf('<input type="text" name="pull_quote_%s_%d" value="%s" placeholder="%s">',
esc_attr($code), $i,
esc_attr(get_post_meta($post_id, 'pull_quote_' . $code . '_' . $i, true)),
esc_attr(sprintf(__('Quote %d', 'bd-bookshelfhelper'), $i))
);
}
echo '</div>';
echo '<span class="bd-hint">' . esc_html__('Notable quotes or reviews to display on the book page.', 'bd-bookshelfhelper') . '</span>';
echo '</div>';
echo '<div class="bd-field-row">';
echo '<div class="bd-field">';
echo '<label for="genre_tags_' . esc_attr($code) . '">' . esc_html__('Genre Tags', 'bd-bookshelfhelper') . '</label>';
echo '<input type="text" id="genre_tags_' . esc_attr($code) . '" name="genre_tags_' . esc_attr($code) . '" value="' . esc_attr(get_post_meta($post_id, 'genre_tags_' . $code, true)) . '">';
echo '<span class="bd-hint">' . esc_html__('Comma-separated (e.g., Thriller, Sci-Fi, Mystery) - minimum 3 recommended', 'bd-bookshelfhelper') . '</span>';
echo '</div>';
echo '<div class="bd-field">';
echo '<label for="amazon_search_terms_' . esc_attr($code) . '">' . esc_html__('Amazon Search Terms', 'bd-bookshelfhelper') . '</label>';
echo '<input type="text" id="amazon_search_terms_' . esc_attr($code) . '" name="amazon_search_terms_' . esc_attr($code) . '" value="' . esc_attr(get_post_meta($post_id, 'amazon_search_terms_' . $code, true)) . '">';
echo '<span class="bd-hint">' . esc_html__('Keywords for Amazon search optimization, comma-separated.', 'bd-bookshelfhelper') . '</span>';
echo '</div>';
echo '</div>';
echo '</div>'; // End language content
}
echo '</div>'; // End Content
// Section 6: Retailer Links
$this->render_section_header('retailers', __('Retailer Links', 'bd-bookshelfhelper'), true);
echo '<div class="bd-section-content" id="bd-section-retailers">';
echo '<div class="bd-retailer-grid">';
$retailers = array(
'ebook_url_amazon' => array('label' => 'Amazon (Ebook)', 'hint' => 'Direct link to ebook product page'),
'ebook_url_apple' => array('label' => 'Apple Books', 'hint' => 'Link with your affiliate code'),
'ebook_url_google' => array('label' => 'Google Play', 'hint' => 'Link with your partner code'),
'ebook_url_barnes' => array('label' => 'Barnes & Noble (Ebook)', 'hint' => 'Link with affiliate parameter'),
'ebook_url_kobo' => array('label' => 'Kobo', 'hint' => 'Link with affiliate parameter'),
'paperback_url_amazon' => array('label' => 'Amazon (Paperback)', 'hint' => 'Direct link to paperback'),
'paperback_url_barnes' => array('label' => 'Barnes & Noble (Paperback)', 'hint' => 'Link with affiliate parameter'),
'hardback_url_barnes' => array('label' => 'Barnes & Noble (Hardback)', 'hint' => 'Link with affiliate parameter'),
);
foreach ($retailers as $field => $info) {
echo '<div class="bd-field">';
echo '<label for="' . esc_attr($field) . '">' . esc_html($info['label'], 'bd-bookshelfhelper') . '</label>';
echo '<input type="url" id="' . esc_attr($field) . '" name="' . esc_attr($field) . '" value="' . esc_url(get_post_meta($post_id, $field, true)) . '" placeholder="https://">';
echo '<span class="bd-hint">' . esc_html($info['hint'], 'bd-bookshelfhelper') . '</span>';
echo '</div>';
}
echo '</div>';
echo '</div>'; // End Retailer Links
// Section 7: Reviews
$this->render_section_header('reviews', __('Reviews', 'bd-bookshelfhelper'), false);
echo '<div class="bd-section-content" id="bd-section-reviews">';
$reviews_table = $wpdb->prefix . 'bd_reviews';
$reviews = $wpdb->get_results($wpdb->prepare("SELECT * FROM {$reviews_table} WHERE book_id = %d ORDER BY display_order", $post_id));
echo '<p>' . esc_html__('Reviews are managed via the Reviews tab on the book editing screen.', 'bd-bookshelfhelper') . '</p>';
if ($reviews) {
echo '<table class="widefat">';
echo '<thead><tr><th>Source</th><th>Comment</th><th>Order</th></tr></thead>';
echo '<tbody>';
foreach ($reviews as $review) {
echo '<tr>';
echo '<td>' . esc_html($review->source_name) . '</td>';
echo '<td>' . esc_html(wp_trim_words($review->comment_text, 20)) . '</td>';
echo '<td>' . esc_html($review->display_order) . '</td>';
echo '</tr>';
}
echo '</tbody></table>';
}
echo '</div>'; // End Reviews
// Section 8: Ratings
$this->render_section_header('ratings', __('Ratings', 'bd-bookshelfhelper'), false);
echo '<div class="bd-section-content" id="bd-section-ratings">';
echo '<div class="bd-field-row">';
echo '<div class="bd-field">';
echo '<label for="amazon_rating">' . esc_html__('Amazon Rating', 'bd-bookshelfhelper') . '</label>';
echo '<input type="text" id="amazon_rating" name="amazon_rating" value="' . esc_attr(get_post_meta($post_id, 'amazon_rating', true)) . '" placeholder="4.5">';
echo '<span class="bd-hint">' . esc_html__('Average rating out of 5 (e.g., 4.5)', 'bd-bookshelfhelper') . '</span>';
echo '</div>';
echo '<div class="bd-field">';
echo '<label for="goodreads_rating">' . esc_html__('Goodreads Rating', 'bd-bookshelfhelper') . '</label>';
echo '<input type="text" id="goodreads_rating" name="goodreads_rating" value="' . esc_attr(get_post_meta($post_id, 'goodreads_rating', true)) . '" placeholder="4.2">';
echo '<span class="bd-hint">' . esc_html__('Average rating out of 5 from Goodreads.', 'bd-bookshelfhelper') . '</span>';
echo '</div>';
echo '</div>';
echo '</div>'; // End Ratings
// Section 9: SEO
$this->render_section_header('seo', __('SEO Settings', 'bd-bookshelfhelper'), false);
echo '<div class="bd-section-content" id="bd-section-seo">';
echo '<div class="bd-field">';
echo '<label for="seo_title">' . esc_html__('SEO Title', 'bd-bookshelfhelper') . '</label>';
echo '<input type="text" id="seo_title" name="seo_title" value="' . esc_attr(get_post_meta($post_id, 'seo_title', true)) . '" class="large-text">';
echo '<span class="bd-hint">' . esc_html__('Custom title for search engines (defaults to book title if empty).', 'bd-bookshelfhelper') . '</span>';
echo '</div>';
echo '<div class="bd-field">';
echo '<label for="seo_description">' . esc_html__('SEO Description', 'bd-bookshelfhelper') . '</label>';
echo '<textarea id="seo_description" name="seo_description" rows="4" class="large-text">' . esc_textarea(get_post_meta($post_id, 'seo_description', true)) . '</textarea>';
echo '<span class="bd-hint">' . esc_html__('Meta description for search results (150-160 characters recommended).', 'bd-bookshelfhelper') . '</span>';
echo '</div>';
echo '</div>'; // End SEO
// Action buttons
echo '<div class="bd-data-entry-actions">';
echo '<a href="' . esc_url(admin_url('post.php?post=' . $post_id . '&action=edit')) . '" class="button">' . esc_html__('Edit in Standard Editor', 'bd-bookshelfhelper') . '</a>';
submit_button(esc_html__('Save Book Data', 'bd-bookshelfhelper'), 'primary large', 'bd_save_data_entry', false);
echo '<button type="button" id="bd-preview-btn" class="button button-large">' . esc_html__('Preview', 'bd-bookshelfhelper') . '</button>';
echo '</div>';
echo '</form>';
// Auto-save indicator
echo '<div class="bd-autosave-indicator"></div>';
echo '</div>'; // End wrap
// Handle form submission
if (isset($_POST['bd_save_data_entry']) && isset($_POST['bd_data_entry_save_nonce'])) {
$this->save_data_entry($post_id);
}
}
/**
* Render section header with collapse toggle
*/
private function render_section_header($id, $title, $default_open = true) {
printf('<h2 class="bd-section-header%s" data-section="%s">', $default_open ? '' : ' collapsed', esc_attr($id));
echo '<span>' . esc_html($title) . '</span>';
echo '<span class="dashicons dashicons-arrow-down-alt2"></span>';
echo '</h2>';
}
/**
* Save data entry form
*/
private function save_data_entry($post_id) {
if (!isset($_POST['bd_data_entry_save_nonce']) || !wp_verify_nonce($_POST['bd_data_entry_save_nonce'], 'bd_data_entry_save')) {
echo '<div class="bd-status-message error">' . esc_html__('Security check failed.', 'bd-bookshelfhelper') . '</div>';
return;
}
if (!current_user_can('edit_post', $post_id)) {
echo '<div class="bd-status-message error">' . esc_html__('Permission denied.', 'bd-bookshelfhelper') . '</div>';
return;
}
// Update post title
if (isset($_POST['post_title'])) {
wp_update_post(array(
'ID' => $post_id,
'post_title' => sanitize_text_field($_POST['post_title']),
));
}
// Core fields
$fields = array(
'pen_name_id', 'isbn', 'asin', 'short_link_slug', 'volume_number',
'publish_date', 'sort_order', 'amazon_rating', 'goodreads_rating',
'seo_title', 'seo_description'
);
foreach ($fields as $field) {
if (isset($_POST[$field])) {
$value = $_POST[$field];
if (in_array($field, array('isbn', 'asin'))) {
$value = sanitize_text_field($value);
} elseif (in_array($field, array('seo_title', 'seo_description'))) {
$value = sanitize_text_field($value);
} else {
$value = sanitize_text_field($value);
}
update_post_meta($post_id, $field, $value);
}
}
// Language content
$languages = BD_BookShelfHelper::get_supported_languages();
foreach ($languages as $code => $name) {
$title_key = 'title_' . $code;
$blurb_key = 'blurb_' . $code;
$preview_key = 'preview_excerpt_' . $code;
$genre_key = 'genre_tags_' . $code;
$search_key = 'amazon_search_terms_' . $code;
foreach (array($title_key, $blurb_key, $preview_key, $genre_key, $search_key) as $key) {
if (isset($_POST[$key])) {
update_post_meta($post_id, $key, sanitize_text_field($_POST[$key]));
}
}
for ($i = 1; $i <= 5; $i++) {
$quote_key = 'pull_quote_' . $code . '_' . $i;
if (isset($_POST[$quote_key])) {
update_post_meta($post_id, $quote_key, sanitize_text_field($_POST[$quote_key]));
}
}
}
// Retailer links
$retailer_fields = array(
'ebook_url_amazon', 'ebook_url_apple', 'ebook_url_google',
'ebook_url_barnes', 'ebook_url_kobo', 'paperback_url_amazon',
'paperback_url_barnes', 'hardback_url_barnes'
);
foreach ($retailer_fields as $field) {
if (isset($_POST[$field])) {
update_post_meta($post_id, $field, esc_url_raw($_POST[$field]));
}
}
// Series
if (isset($_POST['series_id'])) {
$series_id = absint($_POST['series_id']);
if ($series_id > 0) {
wp_set_post_terms($post_id, array($series_id), 'series');
} else {
wp_set_post_terms($post_id, array(), 'series');
}
}
echo '<div class="bd-status-message success">' . esc_html__('Book data saved successfully!', 'bd-bookshelfhelper') . '</div>';
}
/**
* AJAX handler for auto-save
*/
public function ajax_autosave() {
check_ajax_referer('bd_bookshelf_admin', 'nonce');
$post_id = absint($_POST['post_id']);
if (!$post_id || !current_user_can('edit_post', $post_id)) {
wp_send_json_error(array('message' => 'Permission denied'));
}
// Save basic fields
$text_fields = array(
'pen_name_id', 'isbn', 'asin', 'short_link_slug', 'volume_number',
'publish_date', 'sort_order', 'amazon_rating', 'goodreads_rating',
'seo_title', 'seo_description'
);
foreach ($text_fields as $field) {
if (isset($_POST[$field]) && $_POST[$field] !== '') {
update_post_meta($post_id, $field, sanitize_text_field($_POST[$field]));
}
}
// Save post title
if (isset($_POST['post_title']) && $_POST['post_title'] !== '') {
wp_update_post(array(
'ID' => $post_id,
'post_title' => sanitize_text_field($_POST['post_title']),
));
}
// Save series
if (isset($_POST['series_id'])) {
$series_id = absint($_POST['series_id']);
if ($series_id > 0) {
wp_set_post_terms($post_id, array($series_id), 'series');
} else {
wp_set_post_terms($post_id, array(), 'series');
}
}
// Save language content
$languages = BD_BookShelfHelper::get_supported_languages();
foreach ($languages as $code => $name) {
$title_key = 'title_' . $code;
$blurb_key = 'blurb_' . $code;
$preview_key = 'preview_excerpt_' . $code;
$genre_key = 'genre_tags_' . $code;
$search_key = 'amazon_search_terms_' . $code;
foreach (array($title_key, $blurb_key, $preview_key, $genre_key, $search_key) as $key) {
if (isset($_POST[$key]) && $_POST[$key] !== '') {
update_post_meta($post_id, $key, sanitize_text_field($_POST[$key]));
}
}
for ($i = 1; $i <= 5; $i++) {
$quote_key = 'pull_quote_' . $code . '_' . $i;
if (isset($_POST[$quote_key]) && $_POST[$quote_key] !== '') {
update_post_meta($post_id, $quote_key, sanitize_text_field($_POST[$quote_key]));
}
}
}
// Save retailer links
$retailer_fields = array(
'ebook_url_amazon', 'ebook_url_apple', 'ebook_url_google',
'ebook_url_barnes', 'ebook_url_kobo', 'paperback_url_amazon',
'paperback_url_barnes', 'hardback_url_barnes'
);
foreach ($retailer_fields as $field) {
if (isset($_POST[$field]) && $_POST[$field] !== '') {
update_post_meta($post_id, $field, esc_url_raw($_POST[$field]));
}
}
wp_send_json_success(array('message' => 'Auto-saved'));
}
/**
* Save meta boxes
*
* @param int $post_id Post ID.
*/
public function save_meta_boxes($post_id) {
if (!isset($_POST['bd_book_nonce']) || !wp_verify_nonce($_POST['bd_book_nonce'], 'bd_book_meta')) {
return $post_id;
}
if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
return $post_id;
}
if (!current_user_can('edit_post', $post_id)) {
return $post_id;
}
$fields = array(
'pen_name_id', 'asin', 'isbn', 'short_link_slug', 'volume_number',
'amazon_rating', 'goodreads_rating', 'seo_title', 'seo_description'
);
foreach ($fields as $field) {
if (isset($_POST[$field])) {
update_post_meta($post_id, $field, sanitize_text_field($_POST[$field]));
}
}
$languages = BD_BookShelfHelper::get_supported_languages();
foreach ($languages as $code => $name) {
$title_key = 'title_' . $code;
$blurb_key = 'blurb_' . $code;
$preview_key = 'preview_excerpt_' . $code;
if (isset($_POST[$title_key])) {
update_post_meta($post_id, $title_key, sanitize_text_field($_POST[$title_key]));
}
if (isset($_POST[$blurb_key])) {
update_post_meta($post_id, $blurb_key, sanitize_textarea_field($_POST[$blurb_key]));
}
if (isset($_POST[$preview_key])) {
update_post_meta($post_id, $preview_key, sanitize_textarea_field($_POST[$preview_key]));
}
for ($i = 1; $i <= 5; $i++) {
$quote_key = 'pull_quote_' . $code . '_' . $i;
if (isset($_POST[$quote_key])) {
update_post_meta($post_id, $quote_key, sanitize_text_field($_POST[$quote_key]));
}
}
}
$retailer_fields = array(
'ebook_url_amazon', 'ebook_url_apple', 'ebook_url_google',
'ebook_url_barnes', 'ebook_url_kobo', 'paperback_url_barnes', 'hardback_url_barnes'
);
foreach ($retailer_fields as $field) {
if (isset($_POST[$field])) {
update_post_meta($post_id, $field, esc_url_raw($_POST[$field]));
}
}
if (isset($_POST['series_id'])) {
$series_id = absint($_POST['series_id']);
if ($series_id > 0) {
wp_set_post_terms($post_id, array($series_id), 'series');
} else {
wp_set_post_terms($post_id, array(), 'series');
}
}
// Auto-generate short slug if empty
if (empty($_POST['short_link_slug'])) {
$title = get_the_title($post_id);
$slug = $this->generate_short_slug($title);
update_post_meta($post_id, 'short_link_slug', $slug);
}
}
/**
* Generate short slug from title
*
* @param string $title Book title.
* @return string
*/
private function generate_short_slug($title) {
$title = preg_replace('/[^a-zA-Z0-9\s]/', '', $title);
$words = explode(' ', $title);
$stop_words = array('the', 'a', 'an', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'for');
$significant_words = array_filter($words, function($word) use ($stop_words) {
return !in_array(strtolower($word), $stop_words) && strlen($word) > 2;
});
$slug = '';
foreach ($significant_words as $word) {
$slug .= strtolower(substr($word, 0, 1));
}
if (strlen($slug) < 2) {
$slug = substr(implode('', $significant_words), 0, 4);
}
global $wpdb;
$original_slug = $slug;
$counter = 0;
while (true) {
$check_slug = $counter > 0 ? $original_slug . $counter : $slug;
$exists = $wpdb->get_var($wpdb->prepare(
"SELECT post_id FROM {$wpdb->postmeta} WHERE meta_key = 'short_link_slug' AND meta_value = %s",
$check_slug
));
if (!$exists) {
return $check_slug;
}
$counter++;
}
}
/**
* Pen names admin page
*/
public function pen_names_page() {
global $wpdb;
$table_name = $wpdb->prefix . 'bd_pen_names';
if (isset($_POST['bd_add_pen_name']) && check_admin_referer('bd_add_pen_name')) {
$pen_name = sanitize_text_field($_POST['new_pen_name']);
$notes = sanitize_textarea_field($_POST['new_notes']);
if ($pen_name) {
$wpdb->insert($table_name, array(
'pen_name' => $pen_name,
'notes' => $notes,
'active' => 1,
'created_at' => current_time('mysql'),
));
echo '<div class="updated"><p>' . esc_html__('Pen name added.', 'bd-bookshelfhelper') . '</p></div>';
}
}
if (isset($_POST['bd_update_pen_status'])) {
$id = absint($_POST['pen_name_id']);
$active = isset($_POST['pen_active']) ? 1 : 0;
$wpdb->update($table_name, array('active' => $active), array('id' => $id));
}
if (isset($_POST['bd_delete_pen_name'])) {
$id = absint($_POST['pen_name_id']);
$wpdb->delete($table_name, array('id' => $id));
}
$pen_names = $wpdb->get_results("SELECT * FROM {$table_name} ORDER BY pen_name");
echo '<div class="wrap">';
echo '<h1>' . esc_html__('Pen Names', 'bd-bookshelfhelper') . '</h1>';
// Add new form
echo '<form method="post">';
wp_nonce_field('bd_add_pen_name');
echo '<h2>' . esc_html__('Add New Pen Name', 'bd-bookshelfhelper') . '</h2>';
echo '<table class="form-table">';
echo '<tr><th>' . esc_html__('Pen Name', 'bd-bookshelfhelper') . '</th>';
echo '<td><input type="text" name="new_pen_name" class="regular-text" required>';
echo '<p class="description">' . esc_html__('e.g., J.K. Murphy', 'bd-bookshelfhelper') . '</p></td></tr>';
echo '<tr><th>' . esc_html__('Notes', 'bd-bookshelfhelper') . '</th>';
echo '<td><textarea name="new_notes" rows="3" class="regular-text"></textarea></td></tr>';
echo '</table>';
submit_button(esc_html__('Add Pen Name', 'bd-bookshelfhelper'), 'primary', 'bd_add_pen_name');
echo '</form>';
// Existing list
echo '<h2>' . esc_html__('Existing Pen Names', 'bd-bookshelfhelper') . '</h2>';
echo '<table class="widefat">';
echo '<thead><tr><th>ID</th><th>Pen Name</th><th>Notes</th><th>Status</th><th>Actions</th></tr></thead>';
echo '<tbody>';
if ($pen_names) {
foreach ($pen_names as $pn) {
echo '<tr>';
printf('<td>%d</td>', esc_html($pn->id));
printf('<td>%s</td>', esc_html($pn->pen_name));
printf('<td>%s</td>', esc_html($pn->notes));
printf('<td>%s</td>', $pn->active ? esc_html__('Active', 'bd-bookshelfhelper') : esc_html__('Inactive', 'bd-bookshelfhelper'));
echo '<td>';
printf('<form method="post" style="display:inline;">%s', wp_nonce_field('bd_update_pen_status', '_wpnonce', true, false));
printf('<input type="hidden" name="pen_name_id" value="%d">', esc_attr($pn->id));
printf('<input type="checkbox" name="pen_active" value="1"%s>', checked($pn->active, 1, false));
submit_button(esc_html__('Update', 'bd-bookshelfhelper'), 'secondary', 'bd_update_pen_status', false);
echo '</form> ';
printf('<form method="post" style="display:inline;">%s', wp_nonce_field('bd_delete_pen_name', '_wpnonce', true, false));
printf('<input type="hidden" name="pen_name_id" value="%d">', esc_attr($pn->id));
submit_button(esc_html__('Delete', 'bd-bookshelfhelper'), 'delete', 'bd_delete_pen_name', false, array('onclick' => 'return confirm("Delete this pen name?");'));
echo '</form>';
echo '</td></tr>';
}
} else {
echo '<tr><td colspan="5">' . esc_html__('No pen names found.', 'bd-bookshelfhelper') . '</td></tr>';
}
echo '</tbody></table></div>';
}
/**
* Settings admin page
*/
public function settings_page() {
if (isset($_POST['bd_save_settings'])) {
check_admin_referer('bd_settings');
$affiliates = array(
'amazon' => sanitize_text_field($_POST['affiliate_amazon'] ?? ''),
'apple' => sanitize_text_field($_POST['affiliate_apple'] ?? ''),
'barnes' => sanitize_text_field($_POST['affiliate_barnes'] ?? ''),
'kobo' => sanitize_text_field($_POST['affiliate_kobo'] ?? ''),
'google' => sanitize_text_field($_POST['affiliate_google'] ?? ''),
);
update_option('bdm_affiliate_codes', $affiliates);
$goodreads_id = sanitize_text_field($_POST['goodreads_author_id'] ?? '');
update_option('bdm_goodreads_author_id', $goodreads_id);
echo '<div class="updated"><p>' . esc_html__('Settings saved.', 'bd-bookshelfhelper') . '</p></div>';
}
$affiliates = get_option('bdm_affiliate_codes', array());
$goodreads_id = get_option('bdm_goodreads_author_id', '30469651');
echo '<div class="wrap">';
echo '<h1>' . esc_html__('BD BookHelper Settings', 'bd-bookshelfhelper') . '</h1>';
echo '<form method="post">';
wp_nonce_field('bd_settings');
echo '<h2>' . esc_html__('Affiliate Codes', 'bd-bookshelfhelper') . '</h2>';
echo '<p>' . esc_html__('Enter your affiliate IDs for each retailer. Links include these IDs when available.', 'bd-bookshelfhelper') . '</p>';
echo '<table class="form-table">';
$affiliate_fields = array(
'amazon' => 'Amazon',
'apple' => 'Apple Books',
'barnes' => 'Barnes & Noble',
'kobo' => 'Kobo',
'google' => 'Google Play',
);
foreach ($affiliate_fields as $key => $label) {
printf('<tr><th>%s</th>', esc_html($label));
printf('<td><input type="text" name="affiliate_%s" value="%s" class="regular-text"></td></tr>',
esc_attr($key), esc_attr($affiliates[$key] ?? '')
);
}
echo '</table>';
echo '<h2>' . esc_html__('Goodreads Integration', 'bd-bookshelfhelper') . '</h2>';
echo '<table class="form-table">';
printf('<tr><th>' . esc_html__('Goodreads Author ID', 'bd-bookshelfhelper') . '</th>');
printf('<td><input type="text" name="goodreads_author_id" value="%s" class="regular-text">', esc_attr($goodreads_id));
echo '<p class="description">' . esc_html__('Default: 30469651 (B_D_Murphy)', 'bd-bookshelfhelper') . '</p></td></tr>';
echo '</table>';
echo '<h2>' . esc_html__('Amazon Ratings Warning', 'bd-bookshelfhelper') . '</h2>';
echo '<p style="color:#d63638;">' . esc_html__('Warning: Scraping Amazon product pages may conflict with their Terms of Service. Use at your own risk.', 'bd-bookshelfhelper') . '</p>';
submit_button(esc_html__('Save Settings', 'bd-bookshelfhelper'), 'primary', 'bd_save_settings');
echo '</form></div>';
}
/**
* Migration page - Import from BD Books plugin
*/
public function migrate_page() {
global $wpdb;
$old_table = $wpdb->prefix . 'bd_books';
$table_exists = $wpdb->get_var("SHOW TABLES LIKE '$old_table'") === $old_table;
if (isset($_POST['bd_run_migration']) && check_admin_referer('bd_migrate_nonce')) {
$result = BD_BookShelfHelper::migrate_from_bd_books();
if ($result['success']) {
echo '<div class="updated"><p>' . esc_html($result['message']) . '</p></div>';
} else {
echo '<div class="error"><p>' . esc_html($result['message']) . '</p></div>';
}
}
echo '<div class="wrap">';
echo '<h1>' . esc_html__('Migrate from BD Books', 'bd-bookshelfhelper') . '</h1>';
if (!$table_exists) {
echo '<div class="notice notice-warning">';
echo '<p>' . esc_html__('The old BD Books table was not found. No migration needed - the plugin data does not exist.', 'bd-bookshelfhelper') . '</p>';
echo '</div>';
echo '<p>' . esc_html__('This tool migrates books from the original "BD Books" (BD Book Listing) plugin to the new BookHelper system using custom post types.', 'bd-bookshelfhelper') . '</p>';
echo '</div>';
return;
}
$old_books = $wpdb->get_results("SELECT * FROM {$old_table}");
$old_count = count($old_books);
$new_books = get_posts(array(
'post_type' => 'book',
'post_status' => 'any',
'numberposts' => -1,
));
echo '<div class="notice notice-info">';
echo '<p>' . sprintf(esc_html__('Found %d books in the old BD Books table.', 'bd-bookshelfhelper'), $old_count) . '</p>';
echo '<p>' . sprintf(esc_html__('Current BookHelper has %d books.', 'bd-bookshelfhelper'), count($new_books)) . '</p>';
echo '</div>';
if ($old_count > 0) {
echo '<h2>' . esc_html__('Books to Migrate', 'bd-bookshelfhelper') . '</h2>';
echo '<table class="widefat">';
echo '<thead><tr><th>Title</th><th>Author</th><th>Cover</th></tr></thead>';
echo '<tbody>';
foreach ($old_books as $book) {
$has_cover = !empty($book->cover_image_id) || !empty($book->cover_image_url);
echo '<tr>';
echo '<td>' . esc_html($book->title) . '</td>';
echo '<td>' . esc_html($book->author) . '</td>';
echo '<td>' . ($has_cover ? esc_html__('Yes', 'bd-bookshelfhelper') : esc_html__('No', 'bd-bookshelfhelper')) . '</td>';
echo '</tr>';
}
echo '</tbody></table>';
echo '<p style="margin-top: 20px;"><strong>' . esc_html__('What gets migrated:', 'bd-bookshelfhelper') . '</strong></p>';
echo '<ul>';
echo '<li>' . esc_html__('Book titles and authors (as pen names)', 'bd-bookshelfhelper') . '</li>';
echo '<li>' . esc_html__('Descriptions become blurbs in English', 'bd-bookshelfhelper') . '</li>';
echo '<li>' . esc_html__('Cover images (WordPress media or external URLs)', 'bd-bookshelfhelper') . '</li>';
echo '<li>' . esc_html__('Purchase links to retailer fields', 'bd-bookshelfhelper') . '</li>';
echo '</ul>';
echo '<form method="post" style="margin-top: 20px;">';
wp_nonce_field('bd_migrate_nonce');
echo '<input type="submit" name="bd_run_migration" class="button button-primary" value="' . esc_attr__('Run Migration', 'bd-bookshelfhelper') . '" onclick="return confirm(\'' . esc_js(__('This will create new book entries for any books not already migrated. Continue?', 'bd-bookshelfhelper')) . '\');">';
echo '</form>';
} else {
echo '<p>' . esc_html__('No books found in the old table to migrate.', 'bd-bookshelfhelper') . '</p>';
}
echo '</div>';
}
/**
* AJAX handler for updating links
*/
public function ajax_update_links() {
check_ajax_referer('bd_update_links');
$book_id = absint($_POST['book_id']);
wp_send_json_success(array('message' => esc_html__('Links updated.', 'bd-bookshelfhelper')));
}
}