1219 lines
54 KiB
PHP
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')));
|
|
}
|
|
}
|