Consolidate three plugins into Umbra Bookshelf
Merge BD Bookshelf (0.1.0), BD BookShelfHelper (2.0.0) and BD SimpleEmail
(2.0.0) into a single plugin released as Umbra Bookshelf 1.0.0, with the
BD Bookshelf theme left as a separate, unrenamed artifact.
Modules
includes/books/ catalogue, ISBN/ASIN, series, retail, ratings, shortcodes,
SEO, GDPR, scrapers, admin, CSV/post import
includes/library/ book post type + series taxonomy, pen names, data entry,
shortlinks, reviews, migrate
includes/email/ subscribers, newsletters, composition, sending
Renamed: plugin headers, text domains (all three -> umbra-bookshelf), classes
(BD_*/BDM_* -> Umbra_*), constants (-> UMBRA_BOOKSHELF_*), asset handles, JS
globals and admin slugs.
Deliberately not renamed -- persisted contract with live installs and with
published post content: table names, option keys, post meta keys, transients,
nonce actions, cron hooks, AJAX actions, shortcodes ([bd-book], [bd-books],
[bdm_signup]), post type `book`, taxonomy `series`, cookies and CSS classes.
BD_BookShelfHelper survives only as a class_alias for the unmodified theme.
Collisions resolved
* class-umbra-theme-compatibility.php declared BD_BookShelfHelper, the same
name as the library module's helper. Latent while the plugins were
separate (only fired when both were active), but an unconditional fatal
once merged. Renamed to Umbra_Theme_Compatibility with a class_alias.
* All three modules called add_menu_page. One top-level menu is now
registered by the bootstrap at admin_menu priority 9.
* BD Bookshelf and BookShelfHelper both claimed bd-bookshelf-settings, leaving
one Settings screen unreachable -> umbra-bookshelf-settings and
umbra-bookshelf-library-settings.
* Catalogue and library both enqueued bd-bookshelf-admin; the second enqueue
was silently discarded -> unique handles per module.
* Umbra_Admin and Umbra_Migration both registered bd_bookshelf_import_csv, so
one handler silently replaced the other. The import page now lives only in
Umbra_Admin (the variant wired to the admin JS); Umbra_Migration keeps only
the preview/run endpoints it uniquely provides.
* Modules were constructed from an init callback, so their own init
registrations raced the hook being dispatched. Now constructed on
plugins_loaded behind a re-entrancy guard.
Also
* Missing assets created: assets/images/default-cover.png (referenced but
never committed, so every cover-less book 404'd) and email/assets/js/
admin.js (enqueued on every email admin page but never committed).
* Library module's strpos($hook, 'bd-bookshelf') asset check matched every
catalogue and email screen once slugs shared the umbra-bookshelf- prefix;
now matched against its own slug list.
* Consolidated uninstall deduplicates the double drop of bd_books/bd_series.
* Specs aligned to the new name; pre-merge specs retained under
spec/archive/ with provenance notes.
* Tests added: tests/verify_structure.py (static) and tests/smoke_runtime.php
(WordPress-stub runtime boot, front-end and admin contexts).
Deferred and recorded in docs/KNOWN-ISSUES.md: the catalogue and library
modules still create bd_books and bd_series with two incompatible schemas
(pre-existing, now unconditional), and Goodreads/Amazon scraping is still
implemented twice on shared cron hooks.
This commit is contained in:
@@ -1,158 +0,0 @@
|
|||||||
<?php
|
|
||||||
// Copyright 2026 Umbra Tools (umbratools.com)
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Plugin Name: BD Bookshelf
|
|
||||||
* Plugin URI: https://authorbdmurphy.com/plugins/bd-bookshelf
|
|
||||||
* Description: Book management plugin for authors with ISBN tracking, retail links, rating synchronization, and series management.
|
|
||||||
* Version: 0.1.0
|
|
||||||
* Author: B.D. Murphy
|
|
||||||
* Author URI: https://authorbdmurphy.com
|
|
||||||
* License: GPLv2 or later
|
|
||||||
* License URI: https://www.gnu.org/licenses/gpl-2.0.html
|
|
||||||
* Text Domain: bd-bookshelf
|
|
||||||
* Domain Path: /languages
|
|
||||||
* Requires at least: 6.0
|
|
||||||
* Requires PHP: 7.4
|
|
||||||
*/
|
|
||||||
|
|
||||||
if (!defined('ABSPATH')) {
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Plugin constants
|
|
||||||
*/
|
|
||||||
define('BD_BOOKSHELF_VERSION', '0.1.0');
|
|
||||||
define('BD_BOOKSHELF_DIR', plugin_dir_path(__FILE__));
|
|
||||||
define('BD_BOOKSHELF_URL', plugin_dir_url(__FILE__));
|
|
||||||
define('BD_BOOKSHELF_BASENAME', plugin_basename(__FILE__));
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Include files
|
|
||||||
*/
|
|
||||||
require_once BD_BOOKSHELF_DIR . 'includes/class-bdm-database.php';
|
|
||||||
require_once BD_BOOKSHELF_DIR . 'includes/class-bdm-book-manager.php';
|
|
||||||
require_once BD_BOOKSHELF_DIR . 'includes/class-bdm-authors.php';
|
|
||||||
require_once BD_BOOKSHELF_DIR . 'includes/class-bdm-series.php';
|
|
||||||
require_once BD_BOOKSHELF_DIR . 'includes/class-bdm-retail-sites.php';
|
|
||||||
require_once BD_BOOKSHELF_DIR . 'includes/class-bdm-rating-sync.php';
|
|
||||||
require_once BD_BOOKSHELF_DIR . 'includes/class-bdm-shortcodes.php';
|
|
||||||
require_once BD_BOOKSHELF_DIR . 'includes/class-bdm-seo.php';
|
|
||||||
require_once BD_BOOKSHELF_DIR . 'includes/class-bdm-gdpr.php';
|
|
||||||
require_once BD_BOOKSHELF_DIR . 'includes/class-bdm-goodreads-scraper.php';
|
|
||||||
require_once BD_BOOKSHELF_DIR . 'includes/class-bdm-amazon-scraper.php';
|
|
||||||
require_once BD_BOOKSHELF_DIR . 'includes/class-bdm-admin.php';
|
|
||||||
require_once BD_BOOKSHELF_DIR . 'includes/class-bdm-migration.php';
|
|
||||||
require_once BD_BOOKSHELF_DIR . 'includes/class-bdm-theme-compatibility.php';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Main plugin class
|
|
||||||
*/
|
|
||||||
final class BD_Bookshelf {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Single instance of the class
|
|
||||||
*/
|
|
||||||
private static $instance = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get single instance
|
|
||||||
*/
|
|
||||||
public static function instance() {
|
|
||||||
if (is_null(self::$instance)) {
|
|
||||||
self::$instance = new self();
|
|
||||||
}
|
|
||||||
return self::$instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor
|
|
||||||
*/
|
|
||||||
private function __construct() {
|
|
||||||
$this->init_hooks();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize hooks
|
|
||||||
*/
|
|
||||||
private function init_hooks() {
|
|
||||||
register_activation_hook(__FILE__, [$this, 'activate']);
|
|
||||||
register_deactivation_hook(__FILE__, [$this, 'deactivate']);
|
|
||||||
|
|
||||||
add_action('init', [$this, 'init']);
|
|
||||||
add_action('plugins_loaded', [$this, 'plugins_loaded']);
|
|
||||||
|
|
||||||
// Initialize components
|
|
||||||
add_action('init', [$this, 'init_components']);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Plugin activation
|
|
||||||
*/
|
|
||||||
public function activate() {
|
|
||||||
$database = new BDM_Database();
|
|
||||||
$database->create_tables();
|
|
||||||
|
|
||||||
// Flush rewrite rules
|
|
||||||
flush_rewrite_rules();
|
|
||||||
|
|
||||||
// Store activation time
|
|
||||||
update_option('bd_bookshelf_activation_time', current_time('timestamp'));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Plugin deactivation
|
|
||||||
*/
|
|
||||||
public function deactivate() {
|
|
||||||
// Clear scheduled events
|
|
||||||
wp_clear_scheduled_hook('bd_bookshelf_sync_ratings');
|
|
||||||
|
|
||||||
// Flush rewrite rules
|
|
||||||
flush_rewrite_rules();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Init hook
|
|
||||||
*/
|
|
||||||
public function init() {
|
|
||||||
// Load text domain
|
|
||||||
load_plugin_textdomain('bd-bookshelf', false, dirname(BD_BOOKSHELF_BASENAME) . '/languages');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Plugins loaded hook
|
|
||||||
*/
|
|
||||||
public function plugins_loaded() {
|
|
||||||
// Check for updates, etc.
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize components
|
|
||||||
*/
|
|
||||||
public function init_components() {
|
|
||||||
// Initialize admin
|
|
||||||
if (is_admin()) {
|
|
||||||
new BDM_Admin();
|
|
||||||
new BDM_Migration();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize public facing features
|
|
||||||
new BDM_Shortcodes();
|
|
||||||
new BDM_GDPR();
|
|
||||||
|
|
||||||
// Initialize rating sync (cron)
|
|
||||||
new BDM_Rating_Sync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize plugin
|
|
||||||
*/
|
|
||||||
function bd_bookshelf() {
|
|
||||||
return BD_Bookshelf::instance();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start the plugin
|
|
||||||
bd_bookshelf();
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
=== BD Bookshelf ===
|
|
||||||
Contributors: bdmurphy
|
|
||||||
Requires at least: 6.0
|
|
||||||
Tested up to: 6.5
|
|
||||||
Requires PHP: 7.4
|
|
||||||
Stable tag: 0.1.0
|
|
||||||
License: GPLv2 or later
|
|
||||||
License URI: https://www.gnu.org/licenses/gpl-2.0.html
|
|
||||||
|
|
||||||
Copyright 2026 B.D. Murphy
|
|
||||||
|
|
||||||
Book management plugin for authors with ISBN tracking, retail links, and rating synchronization.
|
|
||||||
|
|
||||||
== Description ==
|
|
||||||
|
|
||||||
BD Bookshelf is a comprehensive book management plugin designed for authors. It provides:
|
|
||||||
|
|
||||||
* **Book Management** - Custom database tables for efficient book storage
|
|
||||||
* **ISBN Tracking** - Support for 4 ISBN formats (eBook, Paperback, Hard Cover, Audio)
|
|
||||||
* **Retail Links** - Automatic link generation from ISBNs for Amazon, B&N, Apple Books, Google Play, and Kobo
|
|
||||||
* **Rating Synchronization** - Automated syncing of ratings from Amazon and Goodreads via web scraping
|
|
||||||
* **Shortcodes** - Display retail grids and ratings anywhere on your site
|
|
||||||
* **Series Management** - Organize books into series with ordering
|
|
||||||
* **Author Management** - Support for up to 3 authors per book
|
|
||||||
* **SEO Metadata** - Custom meta tags and Open Graph data for books
|
|
||||||
* **GDPR Compliance** - Cookie consent banner for affiliate tracking
|
|
||||||
* **Click Analytics** - Track clicks on custom redirect shortcodes
|
|
||||||
* **Data Migration** - Import existing books from WordPress posts or CSV files
|
|
||||||
|
|
||||||
== Installation ==
|
|
||||||
|
|
||||||
1. Upload the `bd-bookshelf` folder to the `/wp-content/plugins/` directory
|
|
||||||
2. Activate the plugin through the 'Plugins' menu in WordPress
|
|
||||||
3. Navigate to the 'Books' menu to start adding books
|
|
||||||
4. Configure affiliate codes in Settings > Books > Settings
|
|
||||||
|
|
||||||
== Frequently Asked Questions ==
|
|
||||||
|
|
||||||
= How do I add a new book? =
|
|
||||||
|
|
||||||
Go to Books > Add New Book. Fill in the title, description, ISBNs, and other details. Retail links are automatically generated when you enter ISBNs.
|
|
||||||
|
|
||||||
= How do I display retail links? =
|
|
||||||
|
|
||||||
Use the shortcodes `[sell_ebook]`, `[sell_pb]`, `[sell_hc]`, or `[sell_audio]` with the book ID:
|
|
||||||
|
|
||||||
`[sell_ebook id="123"]`
|
|
||||||
|
|
||||||
= How do I display ratings? =
|
|
||||||
|
|
||||||
Use the `[rating]` shortcode:
|
|
||||||
|
|
||||||
`[rating title="My Book" format="both"]`
|
|
||||||
|
|
||||||
= How often are ratings synced? =
|
|
||||||
|
|
||||||
Ratings are automatically synced every 12 hours. You can also trigger a manual sync from the Rating Sync page.
|
|
||||||
|
|
||||||
== Changelog ==
|
|
||||||
|
|
||||||
= 0.1.0 =
|
|
||||||
* Initial release
|
|
||||||
* Book management with custom database tables
|
|
||||||
* ISBN tracking for 4 formats
|
|
||||||
* Automatic retail link generation
|
|
||||||
* Rating synchronization from Amazon and Goodreads
|
|
||||||
* Shortcode suite for retail grids and ratings
|
|
||||||
* Series and author management
|
|
||||||
* SEO metadata
|
|
||||||
* GDPR compliance
|
|
||||||
* Click analytics
|
|
||||||
* Data migration tools
|
|
||||||
|
|
||||||
== Upgrade Notice ==
|
|
||||||
|
|
||||||
= 0.1.0 =
|
|
||||||
Initial release of BD Bookshelf plugin.
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
<?php
|
|
||||||
// Copyright 2026 Umbra Tools (umbratools.com)
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
/**
|
|
||||||
* BD Bookshelf Uninstall
|
|
||||||
*
|
|
||||||
* Fired when the plugin is deleted from the Plugins page.
|
|
||||||
*
|
|
||||||
* @package BD_Bookshelf
|
|
||||||
*/
|
|
||||||
|
|
||||||
if (!defined('WP_UNINSTALL_PLUGIN')) {
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Include database class
|
|
||||||
require_once __DIR__ . '/includes/class-bdm-database.php';
|
|
||||||
|
|
||||||
// Drop all tables
|
|
||||||
$database = new BDM_Database();
|
|
||||||
$database->drop_tables();
|
|
||||||
|
|
||||||
// Remove options
|
|
||||||
global $wpdb;
|
|
||||||
$wpdb->query("DELETE FROM {$wpdb->options} WHERE option_name LIKE 'bd_bookshelf_%'");
|
|
||||||
|
|
||||||
// Remove transients
|
|
||||||
$wpdb->query("DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_bd_bookshelf_%'");
|
|
||||||
$wpdb->query("DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_timeout_bd_bookshelf_%'");
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
<?php
|
|
||||||
// Copyright 2026 Umbra Tools (umbratools.com)
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Plugin Name: BD BookShelfHelper
|
|
||||||
* Plugin URI: https://authorbdmurphy.com/plugins/bd-bookshelfhelper
|
|
||||||
* Description: Manages book data, series, pen names, short links, reviews, and ratings for the BD Murphy Bookshelf theme.
|
|
||||||
* Version: 2.0.0
|
|
||||||
* Author: B.D. Murphy
|
|
||||||
* Author URI: https://authorbdmurphy.com
|
|
||||||
* License: GPLv2 or later
|
|
||||||
* Text Domain: bd-bookshelfhelper
|
|
||||||
* Requires at least: 6.0
|
|
||||||
* Requires PHP: 7.4
|
|
||||||
*/
|
|
||||||
|
|
||||||
if (!defined('ABSPATH')) {
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
define('BD_BHS_VERSION', '2.0.0');
|
|
||||||
define('BD_BHS_DIR', plugin_dir_path(__FILE__));
|
|
||||||
define('BD_BHS_URI', plugin_dir_url(__FILE__));
|
|
||||||
|
|
||||||
// Main plugin class
|
|
||||||
require_once BD_BHS_DIR . 'inc/class-bookhelper.php';
|
|
||||||
require_once BD_BHS_DIR . 'inc/class-admin.php';
|
|
||||||
require_once BD_BHS_DIR . 'inc/class-install.php';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get plugin instance
|
|
||||||
*
|
|
||||||
* @return BD_BookShelfHelper
|
|
||||||
*/
|
|
||||||
function BD_BookShelfHelper() {
|
|
||||||
return BD_BookShelfHelper::instance();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize plugin
|
|
||||||
*/
|
|
||||||
function bd_bookshelf_init() {
|
|
||||||
// Initialize main plugin
|
|
||||||
BD_BookShelfHelper::instance();
|
|
||||||
|
|
||||||
// Initialize admin (only in admin)
|
|
||||||
if (is_admin()) {
|
|
||||||
BD_BookShelfHelper_Admin::instance();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
add_action('plugins_loaded', 'bd_bookshelf_init', 10);
|
|
||||||
|
|
||||||
// Activation/Deactivation hooks
|
|
||||||
register_activation_hook(__FILE__, array('BD_BookShelfHelper_Install', 'activate'));
|
|
||||||
register_deactivation_hook(__FILE__, array('BD_BookShelfHelper_Install', 'deactivate'));
|
|
||||||
|
|
||||||
// Load plugin text domain
|
|
||||||
add_action('plugins_loaded', function() {
|
|
||||||
load_plugin_textdomain('bd-bookshelfhelper', false, dirname(plugin_basename(__FILE__)) . '/languages');
|
|
||||||
});
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
# BD BookShelfHelper
|
|
||||||
|
|
||||||
**Contributors:** B.D. Murphy
|
|
||||||
**Tags:** books, author, series, pen names, reviews
|
|
||||||
**Requires at least:** WordPress 6.0
|
|
||||||
**Tested up to:** WordPress 6.4
|
|
||||||
**Requires PHP:** 7.4
|
|
||||||
**License:** GPL v2 or later
|
|
||||||
**License URI:** https://www.gnu.org/licenses/gpl-2.0.html
|
|
||||||
|
|
||||||
Manages book data, series, pen names, short links, reviews, and ratings for the BD Murphy Bookshelf theme.
|
|
||||||
|
|
||||||
## Description
|
|
||||||
|
|
||||||
BD BookShelfHelper is a WordPress plugin designed to work with the BDMurphy Bookshelf theme. It provides comprehensive management for:
|
|
||||||
|
|
||||||
- **Book Management:** Full custom post type for books with multi-language support
|
|
||||||
- **Series System:** Organize books into series with volume numbers
|
|
||||||
- **Pen Names:** Support for multiple author pen names
|
|
||||||
- **Short Links:** Auto-generated short URLs for books (e.g., /ph/ for "Pandemic Hacker")
|
|
||||||
- **Review Short Links:** Dedicated URLs for reader reviews on each retailer
|
|
||||||
- **Ratings:** Amazon and Goodreads rating integration
|
|
||||||
- **SEO:** Auto-generated meta tags and schema.org Book structured data
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
1. Upload the `bd-bookshelfhelper` folder to the `/wp-content/plugins/` directory
|
|
||||||
2. Activate the plugin through the 'Plugins' menu in WordPress
|
|
||||||
3. Configure settings under 'BD BookHelper' in the admin sidebar
|
|
||||||
|
|
||||||
## Configuration
|
|
||||||
|
|
||||||
### Setting Up Books
|
|
||||||
|
|
||||||
1. Go to BD BookHelper > Add Book
|
|
||||||
2. Fill in book details including ASIN/ISBN
|
|
||||||
3. Add localized content for each supported language
|
|
||||||
4. Set retailer purchase links
|
|
||||||
5. Add reviews and ratings
|
|
||||||
|
|
||||||
### Pen Names
|
|
||||||
|
|
||||||
1. Go to BD BookHelper > Pen Names
|
|
||||||
2. Add author pen names
|
|
||||||
3. Assign to books in the book editor
|
|
||||||
|
|
||||||
### Series
|
|
||||||
|
|
||||||
1. Go to BD BookHelper > Series (taxonomy)
|
|
||||||
2. Create series with name, slug, and description
|
|
||||||
3. Assign books to series with volume numbers
|
|
||||||
|
|
||||||
### Settings
|
|
||||||
|
|
||||||
Configure affiliate codes and integrations under BD BookHelper > Settings.
|
|
||||||
|
|
||||||
## Short Links
|
|
||||||
|
|
||||||
Books automatically get short URLs based on their title:
|
|
||||||
- "Pandemic Hacker" → /ph/
|
|
||||||
- "Deal Hunter" → /dh/
|
|
||||||
|
|
||||||
Review short links follow the pattern: `{book-slug}r{site-code}`:
|
|
||||||
- /phrA = Amazon review
|
|
||||||
- /phrG = Goodreads review
|
|
||||||
|
|
||||||
## Frequently Asked Questions
|
|
||||||
|
|
||||||
### Do I need the theme?
|
|
||||||
Yes, this plugin is designed to work with the BDMurphy Bookshelf theme.
|
|
||||||
|
|
||||||
### Can I use without affiliate codes?
|
|
||||||
Yes, the plugin works without affiliate codes. They are optional.
|
|
||||||
|
|
||||||
### What languages are supported?
|
|
||||||
English, French, Spanish, German, Italian, Portuguese, Russian, Japanese, and Chinese (Simplified).
|
|
||||||
|
|
||||||
## Changelog
|
|
||||||
|
|
||||||
### 2.0.0
|
|
||||||
- Initial release with full book management, series, pen names, and integrations
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
<?php
|
|
||||||
// Copyright 2026 Umbra Tools (umbratools.com)
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Uninstall BD BookShelfHelper
|
|
||||||
*
|
|
||||||
* Removes all plugin data when the plugin is deleted.
|
|
||||||
*/
|
|
||||||
|
|
||||||
if (!defined('WP_UNINSTALL_PLUGIN')) {
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Drop database tables
|
|
||||||
global $wpdb;
|
|
||||||
|
|
||||||
$tables = array(
|
|
||||||
$wpdb->prefix . 'bd_books',
|
|
||||||
$wpdb->prefix . 'bd_book_meta',
|
|
||||||
$wpdb->prefix . 'bd_series',
|
|
||||||
$wpdb->prefix . 'bd_pen_names',
|
|
||||||
$wpdb->prefix . 'bd_reviews',
|
|
||||||
$wpdb->prefix . 'bd_ratings',
|
|
||||||
$wpdb->prefix . 'bd_review_shortlinks',
|
|
||||||
);
|
|
||||||
|
|
||||||
foreach ($tables as $table) {
|
|
||||||
$wpdb->query("DROP TABLE IF EXISTS {$table}");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete options
|
|
||||||
delete_option('bdm_affiliate_codes');
|
|
||||||
delete_option('bdm_goodreads_author_id');
|
|
||||||
delete_option('bdm_awards');
|
|
||||||
|
|
||||||
// Clear scheduled events
|
|
||||||
wp_clear_scheduled_hook('bd_bookshelf_goodreads_refresh');
|
|
||||||
wp_clear_scheduled_hook('bd_bookshelf_amazon_scrape');
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
<?php
|
|
||||||
// Copyright 2026 Umbra Tools (umbratools.com)
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Plugin Name: BD SimpleEmail
|
|
||||||
* Plugin URI: https://authorbdmurphy.com/plugins/bd-simpleemail
|
|
||||||
* Description: Email newsletter management with AI-assisted composition using Gemini API and subscriber management.
|
|
||||||
* Version: 2.0.0
|
|
||||||
* Author: B.D. Murphy
|
|
||||||
* Author URI: https://authorbdmurphy.com
|
|
||||||
* License: GPLv2 or later
|
|
||||||
* Text Domain: bd-simpleemail
|
|
||||||
* Requires at least: 6.0
|
|
||||||
* Requires PHP: 7.4
|
|
||||||
*/
|
|
||||||
|
|
||||||
if (!defined('ABSPATH')) {
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
define('BD_EMAIL_VERSION', '2.0.0');
|
|
||||||
define('BD_EMAIL_DIR', plugin_dir_path(__FILE__));
|
|
||||||
define('BD_EMAIL_URI', plugin_dir_url(__FILE__));
|
|
||||||
|
|
||||||
require_once BD_EMAIL_DIR . 'inc/class-simpleemail.php';
|
|
||||||
require_once BD_EMAIL_DIR . 'inc/class-email-admin.php';
|
|
||||||
require_once BD_EMAIL_DIR . 'inc/class-email-install.php';
|
|
||||||
require_once BD_EMAIL_DIR . 'inc/class-email-sender.php';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get plugin instance
|
|
||||||
*
|
|
||||||
* @return BD_SimpleEmail
|
|
||||||
*/
|
|
||||||
function BD_SimpleEmail() {
|
|
||||||
return BD_SimpleEmail::instance();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize plugin
|
|
||||||
*/
|
|
||||||
add_action('plugins_loaded', 'BD_SimpleEmail');
|
|
||||||
|
|
||||||
// Activation/Deactivation hooks
|
|
||||||
register_activation_hook(__FILE__, array('BD_SimpleEmail_Install', 'activate'));
|
|
||||||
register_deactivation_hook(__FILE__, array('BD_SimpleEmail_Install', 'deactivate'));
|
|
||||||
|
|
||||||
// Load plugin text domain
|
|
||||||
add_action('plugins_loaded', function() {
|
|
||||||
load_plugin_textdomain('bd-simpleemail', false, dirname(plugin_basename(__FILE__)) . '/languages');
|
|
||||||
});
|
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
# BD SimpleEmail
|
|
||||||
|
|
||||||
**Contributors:** B.D. Murphy
|
|
||||||
**Tags:** email, newsletter, subscribers, mailing list
|
|
||||||
**Requires at least:** WordPress 6.0
|
|
||||||
**Tested up to:** WordPress 6.4
|
|
||||||
**Requires PHP:** 7.4
|
|
||||||
**License:** GPL v2 or later
|
|
||||||
**License URI:** https://www.gnu.org/licenses/gpl-2.0.html
|
|
||||||
|
|
||||||
Email newsletter management with AI-assisted composition using Gemini API and subscriber management.
|
|
||||||
|
|
||||||
## Description
|
|
||||||
|
|
||||||
BD SimpleEmail provides comprehensive email newsletter capabilities:
|
|
||||||
|
|
||||||
- **Subscriber Management:** Add, remove, and manage email subscribers
|
|
||||||
- **AI-Assisted Writing:** Generate newsletter drafts using Gemini AI
|
|
||||||
- **Gmail SMTP:** Send newsletters via your Gmail account
|
|
||||||
- **One-Click Unsubscribe:** Compliant with Gmail/Yahoo requirements
|
|
||||||
- **CAN-SPAM Compliance:** Automatic business information in email footers
|
|
||||||
- **Shortcode Support:** `[bdm_signup]` for embeddable signup forms
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
1. Upload the `bd-simpleemail` folder to the `/wp-content/plugins/` directory
|
|
||||||
2. Activate the plugin through the 'Plugins' menu in WordPress
|
|
||||||
3. Configure Gmail settings under BD SimpleEmail > Settings
|
|
||||||
|
|
||||||
## Configuration
|
|
||||||
|
|
||||||
### Gmail Setup
|
|
||||||
|
|
||||||
1. Enable 2-Factor Authentication on your Google account
|
|
||||||
2. Generate an App Password: https://support.google.com/accounts/answer/185833
|
|
||||||
3. Enter your Gmail address and App Password in plugin settings
|
|
||||||
|
|
||||||
### Gemini AI (Optional)
|
|
||||||
|
|
||||||
1. Get an API key: https://makersuite.google.com/app/apikey
|
|
||||||
2. Enter the API key in plugin settings
|
|
||||||
|
|
||||||
### Business Information
|
|
||||||
|
|
||||||
Configure your business address in Appearance > Theme Options (required for CAN-SPAM compliance).
|
|
||||||
|
|
||||||
## Sending Newsletters
|
|
||||||
|
|
||||||
1. Go to BD SimpleEmail > New Newsletter
|
|
||||||
2. Enter a subject line
|
|
||||||
3. (Optional) Use AI to generate content
|
|
||||||
4. Write or edit your newsletter
|
|
||||||
5. Click "Send Newsletter"
|
|
||||||
|
|
||||||
## Shortcodes
|
|
||||||
|
|
||||||
Use `[bdm_signup]` to display a signup form anywhere on your site.
|
|
||||||
|
|
||||||
## Frequently Asked Questions
|
|
||||||
|
|
||||||
### Do I need to use Gmail?
|
|
||||||
Yes, this plugin uses Gmail SMTP for sending. App passwords are required.
|
|
||||||
|
|
||||||
### Is AI required?
|
|
||||||
No, AI assistance is optional. You can write newsletters manually.
|
|
||||||
|
|
||||||
### Is one-click unsubscribe included?
|
|
||||||
Yes, all emails include one-click unsubscribe links compliant with Gmail/Yahoo requirements.
|
|
||||||
|
|
||||||
## Changelog
|
|
||||||
|
|
||||||
### 2.0.0
|
|
||||||
- Initial release with subscriber management, AI composition, and Gmail SMTP sending
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
<?php
|
|
||||||
// Copyright 2026 Umbra Tools (umbratools.com)
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Uninstall BD SimpleEmail
|
|
||||||
*
|
|
||||||
* Removes all plugin data when the plugin is deleted.
|
|
||||||
*/
|
|
||||||
|
|
||||||
if (!defined('WP_UNINSTALL_PLUGIN')) {
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Drop database tables
|
|
||||||
global $wpdb;
|
|
||||||
|
|
||||||
$tables = array(
|
|
||||||
$wpdb->prefix . 'bd_subscribers',
|
|
||||||
$wpdb->prefix . 'bd_newsletters',
|
|
||||||
);
|
|
||||||
|
|
||||||
foreach ($tables as $table) {
|
|
||||||
$wpdb->query("DROP TABLE IF EXISTS {$table}");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete options
|
|
||||||
delete_option('bdm_gmail_address');
|
|
||||||
delete_option('bdm_gmail_password');
|
|
||||||
delete_option('bdm_sender_name');
|
|
||||||
delete_option('bdm_reply_to');
|
|
||||||
delete_option('bdm_gemini_api_key');
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* BD Bookshelf - Admin Styles
|
* BD Bookshelf - Admin Styles
|
||||||
*
|
*
|
||||||
* @package BD_Bookshelf
|
* @package Umbra_Bookshelf
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* Form Layout */
|
/* Form Layout */
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
/**
|
/**
|
||||||
* BD Bookshelf - Admin JavaScript
|
* BD Bookshelf - Admin JavaScript
|
||||||
*
|
*
|
||||||
* @package BD_Bookshelf
|
* @package Umbra_Bookshelf
|
||||||
*/
|
*/
|
||||||
|
|
||||||
(function($) {
|
(function($) {
|
||||||
@@ -71,11 +71,11 @@
|
|||||||
btn.prop('disabled', true).text('Syncing...');
|
btn.prop('disabled', true).text('Syncing...');
|
||||||
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: bdBookshelfAdmin.ajaxurl,
|
url: umbraBookshelfAdmin.ajaxurl,
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
data: {
|
data: {
|
||||||
action: 'bd_bookshelf_sync_ratings',
|
action: 'bd_bookshelf_sync_ratings',
|
||||||
nonce: bdBookshelfAdmin.nonce
|
nonce: umbraBookshelfAdmin.nonce
|
||||||
},
|
},
|
||||||
success: function(response) {
|
success: function(response) {
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
@@ -98,12 +98,12 @@
|
|||||||
btn.prop('disabled', true).text('Regenerating...');
|
btn.prop('disabled', true).text('Regenerating...');
|
||||||
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: bdBookshelfAdmin.ajaxurl,
|
url: umbraBookshelfAdmin.ajaxurl,
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
data: {
|
data: {
|
||||||
action: 'bd_bookshelf_regenerate_links',
|
action: 'bd_bookshelf_regenerate_links',
|
||||||
book_id: bookId,
|
book_id: bookId,
|
||||||
nonce: bdBookshelfAdmin.nonce
|
nonce: umbraBookshelfAdmin.nonce
|
||||||
},
|
},
|
||||||
success: function(response) {
|
success: function(response) {
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
@@ -170,12 +170,12 @@
|
|||||||
$('body').css('cursor', 'wait');
|
$('body').css('cursor', 'wait');
|
||||||
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: bdBookshelfAdmin.ajaxurl,
|
url: umbraBookshelfAdmin.ajaxurl,
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
data: {
|
data: {
|
||||||
action: action,
|
action: action,
|
||||||
author_id: authorId,
|
author_id: authorId,
|
||||||
nonce: bdBookshelfAdmin.nonce
|
nonce: umbraBookshelfAdmin.nonce
|
||||||
},
|
},
|
||||||
success: function(response) {
|
success: function(response) {
|
||||||
$('body').css('cursor', 'default');
|
$('body').css('cursor', 'default');
|
||||||
@@ -300,7 +300,7 @@
|
|||||||
// Build form data manually for proper nested array handling
|
// Build form data manually for proper nested array handling
|
||||||
var formData = new FormData();
|
var formData = new FormData();
|
||||||
formData.append('action', 'bd_bookshelf_import_scraped');
|
formData.append('action', 'bd_bookshelf_import_scraped');
|
||||||
formData.append('nonce', bdBookshelfAdmin.nonce);
|
formData.append('nonce', umbraBookshelfAdmin.nonce);
|
||||||
formData.append('source', source);
|
formData.append('source', source);
|
||||||
|
|
||||||
// Add author data
|
// Add author data
|
||||||
@@ -326,7 +326,7 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: bdBookshelfAdmin.ajaxurl,
|
url: umbraBookshelfAdmin.ajaxurl,
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
data: formData,
|
data: formData,
|
||||||
processData: false,
|
processData: false,
|
||||||
@@ -365,8 +365,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
coverFrame = wp.media({
|
coverFrame = wp.media({
|
||||||
title: bdBookshelfAdmin.mediaTitle,
|
title: umbraBookshelfAdmin.mediaTitle,
|
||||||
button: { text: bdBookshelfAdmin.mediaButton },
|
button: { text: umbraBookshelfAdmin.mediaButton },
|
||||||
multiple: false,
|
multiple: false,
|
||||||
library: { type: 'image' }
|
library: { type: 'image' }
|
||||||
});
|
});
|
||||||
@@ -458,12 +458,12 @@
|
|||||||
|
|
||||||
// Fetch author data
|
// Fetch author data
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: bdBookshelfAdmin.ajaxurl,
|
url: umbraBookshelfAdmin.ajaxurl,
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
data: {
|
data: {
|
||||||
action: 'bd_bookshelf_get_author',
|
action: 'bd_bookshelf_get_author',
|
||||||
id: authorId,
|
id: authorId,
|
||||||
nonce: bdBookshelfAdmin.nonce
|
nonce: umbraBookshelfAdmin.nonce
|
||||||
},
|
},
|
||||||
success: function(response) {
|
success: function(response) {
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
@@ -497,12 +497,12 @@
|
|||||||
var authorId = $(this).data('id');
|
var authorId = $(this).data('id');
|
||||||
if (confirm('Are you sure you want to delete this author?')) {
|
if (confirm('Are you sure you want to delete this author?')) {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: bdBookshelfAdmin.ajaxurl,
|
url: umbraBookshelfAdmin.ajaxurl,
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
data: {
|
data: {
|
||||||
action: 'bd_bookshelf_delete_author',
|
action: 'bd_bookshelf_delete_author',
|
||||||
id: authorId,
|
id: authorId,
|
||||||
nonce: bdBookshelfAdmin.nonce
|
nonce: umbraBookshelfAdmin.nonce
|
||||||
},
|
},
|
||||||
success: function(response) {
|
success: function(response) {
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
@@ -521,7 +521,7 @@
|
|||||||
var data = $(this).serialize();
|
var data = $(this).serialize();
|
||||||
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: bdBookshelfAdmin.ajaxurl,
|
url: umbraBookshelfAdmin.ajaxurl,
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
data: data + '&action=bd_bookshelf_save_author',
|
data: data + '&action=bd_bookshelf_save_author',
|
||||||
success: function(response) {
|
success: function(response) {
|
||||||
@@ -560,12 +560,12 @@
|
|||||||
|
|
||||||
// Fetch series data
|
// Fetch series data
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: bdBookshelfAdmin.ajaxurl,
|
url: umbraBookshelfAdmin.ajaxurl,
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
data: {
|
data: {
|
||||||
action: 'bd_bookshelf_get_series',
|
action: 'bd_bookshelf_get_series',
|
||||||
id: seriesId,
|
id: seriesId,
|
||||||
nonce: bdBookshelfAdmin.nonce
|
nonce: umbraBookshelfAdmin.nonce
|
||||||
},
|
},
|
||||||
success: function(response) {
|
success: function(response) {
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
@@ -586,12 +586,12 @@
|
|||||||
var seriesId = $(this).data('id');
|
var seriesId = $(this).data('id');
|
||||||
if (confirm('Are you sure you want to delete this series? Books in this series will be unlinked.')) {
|
if (confirm('Are you sure you want to delete this series? Books in this series will be unlinked.')) {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: bdBookshelfAdmin.ajaxurl,
|
url: umbraBookshelfAdmin.ajaxurl,
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
data: {
|
data: {
|
||||||
action: 'bd_bookshelf_delete_series',
|
action: 'bd_bookshelf_delete_series',
|
||||||
id: seriesId,
|
id: seriesId,
|
||||||
nonce: bdBookshelfAdmin.nonce
|
nonce: umbraBookshelfAdmin.nonce
|
||||||
},
|
},
|
||||||
success: function(response) {
|
success: function(response) {
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
@@ -610,7 +610,7 @@
|
|||||||
var data = $(this).serialize();
|
var data = $(this).serialize();
|
||||||
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: bdBookshelfAdmin.ajaxurl,
|
url: umbraBookshelfAdmin.ajaxurl,
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
data: data + '&action=bd_bookshelf_save_series',
|
data: data + '&action=bd_bookshelf_save_series',
|
||||||
success: function(response) {
|
success: function(response) {
|
||||||
@@ -646,12 +646,12 @@
|
|||||||
var shortcodeId = $(this).data('id');
|
var shortcodeId = $(this).data('id');
|
||||||
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: bdBookshelfAdmin.ajaxurl,
|
url: umbraBookshelfAdmin.ajaxurl,
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
data: {
|
data: {
|
||||||
action: 'bd_bookshelf_get_shortcode',
|
action: 'bd_bookshelf_get_shortcode',
|
||||||
id: shortcodeId,
|
id: shortcodeId,
|
||||||
nonce: bdBookshelfAdmin.nonce
|
nonce: umbraBookshelfAdmin.nonce
|
||||||
},
|
},
|
||||||
success: function(response) {
|
success: function(response) {
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
@@ -674,12 +674,12 @@
|
|||||||
var shortcodeId = $(this).data('id');
|
var shortcodeId = $(this).data('id');
|
||||||
if (confirm('Are you sure you want to delete this shortcode?')) {
|
if (confirm('Are you sure you want to delete this shortcode?')) {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: bdBookshelfAdmin.ajaxurl,
|
url: umbraBookshelfAdmin.ajaxurl,
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
data: {
|
data: {
|
||||||
action: 'bd_bookshelf_delete_shortcode',
|
action: 'bd_bookshelf_delete_shortcode',
|
||||||
id: shortcodeId,
|
id: shortcodeId,
|
||||||
nonce: bdBookshelfAdmin.nonce
|
nonce: umbraBookshelfAdmin.nonce
|
||||||
},
|
},
|
||||||
success: function(response) {
|
success: function(response) {
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
@@ -698,7 +698,7 @@
|
|||||||
var data = $(this).serialize();
|
var data = $(this).serialize();
|
||||||
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: bdBookshelfAdmin.ajaxurl,
|
url: umbraBookshelfAdmin.ajaxurl,
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
data: data + '&action=bd_bookshelf_save_shortcode',
|
data: data + '&action=bd_bookshelf_save_shortcode',
|
||||||
success: function(response) {
|
success: function(response) {
|
||||||
@@ -720,7 +720,7 @@
|
|||||||
var data = form.serialize();
|
var data = form.serialize();
|
||||||
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: bdBookshelfAdmin.ajaxurl,
|
url: umbraBookshelfAdmin.ajaxurl,
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
data: data + '&action=bd_bookshelf_save_book',
|
data: data + '&action=bd_bookshelf_save_book',
|
||||||
success: function(response) {
|
success: function(response) {
|
||||||
@@ -741,12 +741,12 @@
|
|||||||
*/
|
*/
|
||||||
function deleteBook(bookId) {
|
function deleteBook(bookId) {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: bdBookshelfAdmin.ajaxurl,
|
url: umbraBookshelfAdmin.ajaxurl,
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
data: {
|
data: {
|
||||||
action: 'bd_bookshelf_delete_book',
|
action: 'bd_bookshelf_delete_book',
|
||||||
id: bookId,
|
id: bookId,
|
||||||
nonce: bdBookshelfAdmin.nonce
|
nonce: umbraBookshelfAdmin.nonce
|
||||||
},
|
},
|
||||||
success: function(response) {
|
success: function(response) {
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
@@ -766,7 +766,7 @@
|
|||||||
var data = form.serialize();
|
var data = form.serialize();
|
||||||
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: bdBookshelfAdmin.ajaxurl,
|
url: umbraBookshelfAdmin.ajaxurl,
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
data: data + '&action=bd_bookshelf_save_sites',
|
data: data + '&action=bd_bookshelf_save_sites',
|
||||||
success: function(response) {
|
success: function(response) {
|
||||||
@@ -787,7 +787,7 @@
|
|||||||
var data = form.serialize();
|
var data = form.serialize();
|
||||||
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: bdBookshelfAdmin.ajaxurl,
|
url: umbraBookshelfAdmin.ajaxurl,
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
data: data + '&action=bd_bookshelf_save_settings',
|
data: data + '&action=bd_bookshelf_save_settings',
|
||||||
success: function(response) {
|
success: function(response) {
|
||||||
@@ -809,7 +809,7 @@
|
|||||||
formData.append('action', 'bd_bookshelf_import_csv');
|
formData.append('action', 'bd_bookshelf_import_csv');
|
||||||
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: bdBookshelfAdmin.ajaxurl,
|
url: umbraBookshelfAdmin.ajaxurl,
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
data: formData,
|
data: formData,
|
||||||
processData: false,
|
processData: false,
|
||||||
@@ -833,7 +833,7 @@
|
|||||||
var data = form.serialize();
|
var data = form.serialize();
|
||||||
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: bdBookshelfAdmin.ajaxurl,
|
url: umbraBookshelfAdmin.ajaxurl,
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
data: data + '&action=bd_bookshelf_import_posts',
|
data: data + '&action=bd_bookshelf_import_posts',
|
||||||
success: function(response) {
|
success: function(response) {
|
||||||
BIN
umbra-bookshelf/assets/images/default-cover.png
Normal file
BIN
umbra-bookshelf/assets/images/default-cover.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.8 KiB |
70
umbra-bookshelf/docs/ARCHITECTURE.md
Normal file
70
umbra-bookshelf/docs/ARCHITECTURE.md
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
# Architecture
|
||||||
|
|
||||||
|
## Why one plugin
|
||||||
|
|
||||||
|
Three plugins shipped the same product surface under three admin menus and
|
||||||
|
shared two cron hooks. Users installed, updated and deactivated them
|
||||||
|
independently while the code assumed all three were present. Consolidating into
|
||||||
|
one plugin makes the dependency explicit and lets one activation hook set up a
|
||||||
|
consistent state.
|
||||||
|
|
||||||
|
The trade-off is that three independent `add_menu_page` calls, three sets of
|
||||||
|
admin assets and three activation routines now share a single namespace. Every
|
||||||
|
one of those collided, and each collision is recorded in `KNOWN-ISSUES.md`.
|
||||||
|
|
||||||
|
## Layers
|
||||||
|
|
||||||
|
```
|
||||||
|
umbra-bookshelf.php (bootstrap)
|
||||||
|
├── constants one UMBRA_BOOKSHELF_* set
|
||||||
|
├── requires 21 class files, three modules
|
||||||
|
├── plugins_loaded construct modules
|
||||||
|
├── init text domain
|
||||||
|
└── admin_menu(9) single top-level menu
|
||||||
|
│
|
||||||
|
┌──────────────────────────┼──────────────────────────┐
|
||||||
|
│ │ │
|
||||||
|
includes/books includes/library includes/email
|
||||||
|
Umbra_Database Umbra_Bookshelf_Helper Umbra_Simple_Email
|
||||||
|
Umbra_Book_Manager …_Helper_Admin …_Admin
|
||||||
|
Umbra_Authors …_Helper_Install …_Install
|
||||||
|
Umbra_Series Umbra_Email_Sender
|
||||||
|
Umbra_Retail_Sites
|
||||||
|
Umbra_Rating_Sync
|
||||||
|
Umbra_Shortcodes
|
||||||
|
Umbra_SEO assets: admin/, public/, library/, email/
|
||||||
|
Umbra_GDPR assets/
|
||||||
|
Umbra_Goodreads_Scraper
|
||||||
|
Umbra_Amazon_Scraper
|
||||||
|
Umbra_Admin
|
||||||
|
Umbra_Migration
|
||||||
|
Umbra_Theme_Compatibility -> class_alias BD_BookShelfHelper
|
||||||
|
```
|
||||||
|
|
||||||
|
## Design rules
|
||||||
|
|
||||||
|
**One owner per name.** Every class, constant, asset handle, menu slug and AJAX
|
||||||
|
action is owned by exactly one module. The bootstrap owns the top-level menu and
|
||||||
|
the shared constants; modules own their submenus and assets.
|
||||||
|
|
||||||
|
**Persistence is not part of the identity.** Anything written to the database or
|
||||||
|
embedded in published content keeps its historical name. The rename covers
|
||||||
|
headers, text domains, classes, constants, handles, slugs and JS globals — never
|
||||||
|
table names, option keys, meta keys, cron hooks, nonces, shortcodes, post types,
|
||||||
|
taxonomies or cookies. See §2.2 of the specification for the full list.
|
||||||
|
|
||||||
|
**Registration order is explicit.** The top-level menu registers at
|
||||||
|
`admin_menu` priority 9 so the parent exists before modules append submenus at
|
||||||
|
the default 10. Modules construct on `plugins_loaded` so their own `init`
|
||||||
|
callbacks are registered before `init` fires.
|
||||||
|
|
||||||
|
**Compatibility shims are explicit.** `Umbra_Theme_Compatibility` exposes the
|
||||||
|
static API the unmodified theme calls, and re-exports the legacy
|
||||||
|
`BD_BookShelfHelper` name via `class_alias()`.
|
||||||
|
|
||||||
|
## Not resolved here
|
||||||
|
|
||||||
|
The catalogue and library modules keep two independent data models that happen
|
||||||
|
to share table names `bd_books` and `bd_series`. Consolidation made them
|
||||||
|
unconditionally conflict; it did not make them agree. Resolving this is the next
|
||||||
|
piece of work — see `KNOWN-ISSUES.md` §1 for the options.
|
||||||
132
umbra-bookshelf/docs/KNOWN-ISSUES.md
Normal file
132
umbra-bookshelf/docs/KNOWN-ISSUES.md
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
# Known issues
|
||||||
|
|
||||||
|
Carried into the Umbra Bookshelf consolidation. Items 1–4 are **pre-existing**
|
||||||
|
defects inherited from the three merged plugins; item 5 and the two repaired
|
||||||
|
below are noted for traceability.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. `bd_books` and `bd_series` have two incompatible schemas (DEFERRED — unresolved)
|
||||||
|
|
||||||
|
The books and library modules both create tables with identical names and
|
||||||
|
different columns.
|
||||||
|
|
||||||
|
`Umbra_Database::table()` returns `$wpdb->prefix . 'bd_' . $name`, and the
|
||||||
|
catalogue issues `CREATE TABLE {prefix}bd_books` with **no** `IF NOT EXISTS`
|
||||||
|
guard:
|
||||||
|
|
||||||
|
```
|
||||||
|
catalogue wp_bd_books id, title, slug, description, isbn_*, amazon_asin,
|
||||||
|
goodreads_id, rating_*, created_at, ...
|
||||||
|
library wp_bd_books id, post_id, pen_name_id, series_id, volume_number,
|
||||||
|
asin, isbn, publish_date, sort_order, created_at
|
||||||
|
```
|
||||||
|
|
||||||
|
Same for `wp_bd_series` (catalogue: `name`, `slug`, `description` …;
|
||||||
|
library: `name`, `slug`, `cover_attachment_id`, `header_attachment_id`, `blurb`, …).
|
||||||
|
|
||||||
|
**Consequence.** Whichever module is activated first wins; the second either
|
||||||
|
no-ops (`IF NOT EXISTS`, library) or raises a database error (catalogue's
|
||||||
|
unguarded `CREATE TABLE`). Whichever schema exists first is what the other
|
||||||
|
module then reads and writes against, producing wrong results rather than an
|
||||||
|
obvious failure.
|
||||||
|
|
||||||
|
**Why deferred.** Resolving it means choosing a canonical schema and migrating
|
||||||
|
live data — a data-migration project, not a rename. Both datasets may hold rows
|
||||||
|
that matter.
|
||||||
|
|
||||||
|
**Options.**
|
||||||
|
|
||||||
|
1. Library schema becomes canonical; catalogue tables move to `bd_bookshelf_*`
|
||||||
|
with an upgrade routine copying existing rows. Requires renaming DB keys,
|
||||||
|
which this consolidation deliberately avoids.
|
||||||
|
2. Catalogue schema becomes canonical; library relies on the `book` post type and
|
||||||
|
postmeta instead of `bd_books`.
|
||||||
|
3. Delete the catalogue duplicates and keep only the library model.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Goodreads and Amazon scraping is implemented twice
|
||||||
|
|
||||||
|
* Catalogue: `Umbra_Goodreads_Scraper`, `Umbra_Amazon_Scraper`.
|
||||||
|
* Library: `Umbra_Bookshelf_Helper::refresh_goodreads_data()`,
|
||||||
|
`::scrape_amazon_ratings()`.
|
||||||
|
|
||||||
|
Both attach to the **same** cron hooks (`bd_bookshelf_goodreads_refresh`,
|
||||||
|
`bd_bookshelf_amazon_scrape`), so each scheduled run performs duplicate
|
||||||
|
requests to third-party APIs — wasted quota, faster rate limiting, and two
|
||||||
|
places to fix a parsing bug. Needs a decision on which implementation survives.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. `assets/images/default-cover.png` was missing
|
||||||
|
|
||||||
|
`Umbra_Bookshelf_Helper` returns `assets/images/default-cover.png` as the
|
||||||
|
fallback cover, but the repository contained no images at all, so every
|
||||||
|
cover-less book rendered a broken image.
|
||||||
|
|
||||||
|
**Repaired during consolidation:** a neutral 600×900 placeholder PNG was
|
||||||
|
generated at that path. Replace it with real artwork before release.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. `email/assets/js/admin.js` did not exist
|
||||||
|
|
||||||
|
`Umbra_Simple_Email_Admin::enqueue_assets()` enqueued it on every email admin
|
||||||
|
page, but no such file was ever committed — a guaranteed 404.
|
||||||
|
|
||||||
|
**Repaired during consolidation:** an empty placeholder was created with a
|
||||||
|
comment explaining the history. Add the intended behaviour, or drop the enqueue.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. `Umbra_SEO` is never instantiated
|
||||||
|
|
||||||
|
`Umbra_SEO` registers its hooks from a constructor, but the bootstrap does not
|
||||||
|
construct it — carried over unchanged from the original BD Bookshelf bootstrap.
|
||||||
|
The class is loaded and its hooks never attach, so the module is inert.
|
||||||
|
|
||||||
|
Left as-is because enabling it changes behaviour. Either instantiate it in
|
||||||
|
`Umbra_Bookshelf::init_modules()` or delete it.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Licence metadata is inconsistent (NOT CHANGED)
|
||||||
|
|
||||||
|
Plugin headers declared `License: GPLv2 or later` while the repository's
|
||||||
|
`LICENSE`/`NOTICE` files (added in `85de97b`) and every SPDX header state
|
||||||
|
**Apache-2.0**. The consolidated header now says Apache-2.0 to match the SPDX
|
||||||
|
headers and `LICENSE`, but this needs a deliberate decision — WordPress
|
||||||
|
plugin-directory submissions require a GPL-compatible licence.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Fixed during consolidation
|
||||||
|
|
||||||
|
* **`Cannot declare class` fatal.** `class-bdm-theme-compatibility.php` declared
|
||||||
|
`BD_BookShelfHelper`, the same class name as the library plugin's own helper.
|
||||||
|
Latent while the two were separate plugins (it only fired when both were
|
||||||
|
active), but an unconditional fatal once merged. The shim is now
|
||||||
|
`Umbra_Theme_Compatibility`, with a `class_alias()` preserving the legacy name
|
||||||
|
for the unmodified theme.
|
||||||
|
* **Duplicate `bd_bookshelf_import_csv` AJAX handler.** Both `Umbra_Admin` and
|
||||||
|
`Umbra_Migration` registered it; the second silently replaced the first. The
|
||||||
|
import page now lives solely in `Umbra_Admin` (it is the variant wired to
|
||||||
|
`admin/js/umbra-bookshelf-admin.js`); `Umbra_Migration` keeps only the
|
||||||
|
preview/run endpoints it uniquely provides.
|
||||||
|
* **Duplicate `bd-bookshelf-settings` submenu.** Catalogue and library both
|
||||||
|
claimed the slug, leaving one Settings screen unreachable. Now
|
||||||
|
`umbra-bookshelf-settings` and `umbra-bookshelf-library-settings`.
|
||||||
|
* **Duplicate admin menu.** All three modules called `add_menu_page`. Now one
|
||||||
|
top-level menu registered by the bootstrap at priority 9.
|
||||||
|
* **Colliding asset handles.** Catalogue and library both enqueued
|
||||||
|
`bd-bookshelf-admin`; the second enqueue was silently discarded. Handles are
|
||||||
|
now unique per module.
|
||||||
|
* **Modules could miss their own `init` callbacks.** Constructing modules from
|
||||||
|
inside an `init` callback means their `init` registrations race the hook
|
||||||
|
currently being dispatched. Modules are now constructed on `plugins_loaded`,
|
||||||
|
behind a re-entrancy guard.
|
||||||
|
* **Over-broad asset hook check.** The library module's
|
||||||
|
`strpos($hook, 'bd-bookshelf')` matched every catalogue and email screen once
|
||||||
|
all slugs shared the `umbra-bookshelf-` prefix. Now matched against its own
|
||||||
|
slug list.
|
||||||
11
umbra-bookshelf/email/assets/js/admin.js
Normal file
11
umbra-bookshelf/email/assets/js/admin.js
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
// Copyright 2026 Umbra Tools (umbratools.com)
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Umbra Bookshelf - Email module admin scripts.
|
||||||
|
*
|
||||||
|
* NOTE: Umbra_Simple_Email_Admin::enqueue_assets() has always enqueued this
|
||||||
|
* file, but the file was never present in the source repository, so every
|
||||||
|
* email admin page issued a 404 for it. It is created here as an empty
|
||||||
|
* placeholder so the enqueue resolves. Add email-module admin behaviour below.
|
||||||
|
*/
|
||||||
@@ -5,14 +5,14 @@
|
|||||||
/**
|
/**
|
||||||
* Admin Interface class
|
* Admin Interface class
|
||||||
*
|
*
|
||||||
* @package BD_Bookshelf
|
* @package Umbra_Bookshelf
|
||||||
*/
|
*/
|
||||||
|
|
||||||
if (!defined('ABSPATH')) {
|
if (!defined('ABSPATH')) {
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
class BDM_Admin {
|
class Umbra_Admin {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
@@ -44,44 +44,37 @@ class BDM_Admin {
|
|||||||
* Add admin menu
|
* Add admin menu
|
||||||
*/
|
*/
|
||||||
public function add_admin_menu() {
|
public function add_admin_menu() {
|
||||||
add_menu_page(
|
// The top-level "Umbra Bookshelf" menu is registered once by the plugin
|
||||||
__('Books', 'bd-bookshelf'),
|
// bootstrap (Umbra_Bookshelf::register_admin_menu); each module only
|
||||||
__('Books', 'bd-bookshelf'),
|
// contributes submenus beneath it.
|
||||||
'manage_options',
|
add_submenu_page('umbra-bookshelf', __('All Books', 'umbra-bookshelf'), __('All Books', 'umbra-bookshelf'), 'manage_options', 'umbra-bookshelf-all', [$this, 'render_books_page']);
|
||||||
'bd-bookshelf',
|
add_submenu_page('umbra-bookshelf', __('Add New Book', 'umbra-bookshelf'), __('Add New Book', 'umbra-bookshelf'), 'manage_options', 'umbra-bookshelf-add', [$this, 'render_book_form']);
|
||||||
[$this, 'render_books_page'],
|
add_submenu_page('umbra-bookshelf', __('Authors', 'umbra-bookshelf'), __('Authors', 'umbra-bookshelf'), 'manage_options', 'umbra-bookshelf-authors', [$this, 'render_authors_page']);
|
||||||
'dashicons-book',
|
add_submenu_page('umbra-bookshelf', __('Series', 'umbra-bookshelf'), __('Series', 'umbra-bookshelf'), 'manage_options', 'umbra-bookshelf-series', [$this, 'render_series_page']);
|
||||||
30
|
add_submenu_page('umbra-bookshelf', __('Retail Sites', 'umbra-bookshelf'), __('Retail Sites', 'umbra-bookshelf'), 'manage_options', 'umbra-bookshelf-sites', [$this, 'render_sites_page']);
|
||||||
);
|
add_submenu_page('umbra-bookshelf', __('Shortcodes', 'umbra-bookshelf'), __('Shortcodes', 'umbra-bookshelf'), 'manage_options', 'umbra-bookshelf-shortcodes', [$this, 'render_shortcodes_page']);
|
||||||
|
add_submenu_page('umbra-bookshelf', __('Rating Sync', 'umbra-bookshelf'), __('Rating Sync', 'umbra-bookshelf'), 'manage_options', 'umbra-bookshelf-sync', [$this, 'render_sync_page']);
|
||||||
add_submenu_page('bd-bookshelf', __('All Books', 'bd-bookshelf'), __('All Books', 'bd-bookshelf'), 'manage_options', 'bd-bookshelf', [$this, 'render_books_page']);
|
add_submenu_page('umbra-bookshelf', __('Import', 'umbra-bookshelf'), __('Import', 'umbra-bookshelf'), 'manage_options', 'umbra-bookshelf-import', [$this, 'render_import_page']);
|
||||||
add_submenu_page('bd-bookshelf', __('Add New Book', 'bd-bookshelf'), __('Add New Book', 'bd-bookshelf'), 'manage_options', 'bd-bookshelf-add', [$this, 'render_book_form']);
|
add_submenu_page('umbra-bookshelf', __('Settings', 'umbra-bookshelf'), __('Settings', 'umbra-bookshelf'), 'manage_options', 'umbra-bookshelf-settings', [$this, 'render_settings_page']);
|
||||||
add_submenu_page('bd-bookshelf', __('Authors', 'bd-bookshelf'), __('Authors', 'bd-bookshelf'), 'manage_options', 'bd-bookshelf-authors', [$this, 'render_authors_page']);
|
|
||||||
add_submenu_page('bd-bookshelf', __('Series', 'bd-bookshelf'), __('Series', 'bd-bookshelf'), 'manage_options', 'bd-bookshelf-series', [$this, 'render_series_page']);
|
|
||||||
add_submenu_page('bd-bookshelf', __('Retail Sites', 'bd-bookshelf'), __('Retail Sites', 'bd-bookshelf'), 'manage_options', 'bd-bookshelf-sites', [$this, 'render_sites_page']);
|
|
||||||
add_submenu_page('bd-bookshelf', __('Shortcodes', 'bd-bookshelf'), __('Shortcodes', 'bd-bookshelf'), 'manage_options', 'bd-bookshelf-shortcodes', [$this, 'render_shortcodes_page']);
|
|
||||||
add_submenu_page('bd-bookshelf', __('Rating Sync', 'bd-bookshelf'), __('Rating Sync', 'bd-bookshelf'), 'manage_options', 'bd-bookshelf-sync', [$this, 'render_sync_page']);
|
|
||||||
add_submenu_page('bd-bookshelf', __('Settings', 'bd-bookshelf'), __('Settings', 'bd-bookshelf'), 'manage_options', 'bd-bookshelf-settings', [$this, 'render_settings_page']);
|
|
||||||
add_submenu_page('bd-bookshelf', __('Import', 'bd-bookshelf'), __('Import', 'bd-bookshelf'), 'manage_options', 'bd-bookshelf-import', [$this, 'render_import_page']);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enqueue admin scripts
|
* Enqueue admin scripts
|
||||||
*/
|
*/
|
||||||
public function enqueue_admin_scripts($hook) {
|
public function enqueue_admin_scripts($hook) {
|
||||||
if (strpos($hook, 'bd-bookshelf') === false) {
|
if (strpos($hook, 'umbra-bookshelf') === false) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
wp_enqueue_media();
|
wp_enqueue_media();
|
||||||
wp_enqueue_style('bd-bookshelf-admin', BD_BOOKSHELF_URL . 'admin/css/bd-bookshelf-admin.css', [], BD_BOOKSHELF_VERSION);
|
wp_enqueue_style('umbra-bookshelf-admin', UMBRA_BOOKSHELF_URL . 'admin/css/umbra-bookshelf-admin.css', [], UMBRA_BOOKSHELF_VERSION);
|
||||||
wp_enqueue_script('bd-bookshelf-admin', BD_BOOKSHELF_URL . 'admin/js/bd-bookshelf-admin.js', ['jquery'], BD_BOOKSHELF_VERSION, true);
|
wp_enqueue_script('umbra-bookshelf-admin', UMBRA_BOOKSHELF_URL . 'admin/js/umbra-bookshelf-admin.js', ['jquery'], UMBRA_BOOKSHELF_VERSION, true);
|
||||||
|
|
||||||
wp_localize_script('bd-bookshelf-admin', 'bdBookshelfAdmin', [
|
wp_localize_script('umbra-bookshelf-admin', 'umbraBookshelfAdmin', [
|
||||||
'ajaxurl' => admin_url('admin-ajax.php'),
|
'ajaxurl' => admin_url('admin-ajax.php'),
|
||||||
'nonce' => wp_create_nonce('bd_bookshelf_admin_nonce'),
|
'nonce' => wp_create_nonce('bd_bookshelf_admin_nonce'),
|
||||||
'mediaTitle' => __('Select Cover Image', 'bd-bookshelf'),
|
'mediaTitle' => __('Select Cover Image', 'umbra-bookshelf'),
|
||||||
'mediaButton' => __('Use This Image', 'bd-bookshelf'),
|
'mediaButton' => __('Use This Image', 'umbra-bookshelf'),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,29 +83,29 @@ class BDM_Admin {
|
|||||||
*/
|
*/
|
||||||
public function render_books_page() {
|
public function render_books_page() {
|
||||||
if (!current_user_can('manage_options')) {
|
if (!current_user_can('manage_options')) {
|
||||||
wp_die(__('You do not have sufficient permissions to access this page.', 'bd-bookshelf'));
|
wp_die(__('You do not have sufficient permissions to access this page.', 'umbra-bookshelf'));
|
||||||
}
|
}
|
||||||
|
|
||||||
$books = BDM_Book_Manager::get_books(['number' => 50]);
|
$books = Umbra_Book_Manager::get_books(['number' => 50]);
|
||||||
?>
|
?>
|
||||||
<div class="wrap">
|
<div class="wrap">
|
||||||
<h1 class="wp-heading-inline"><?php esc_html_e('Books', 'bd-bookshelf'); ?></h1>
|
<h1 class="wp-heading-inline"><?php esc_html_e('Books', 'umbra-bookshelf'); ?></h1>
|
||||||
<a href="<?php echo admin_url('admin.php?page=bd-bookshelf-add'); ?>" class="page-title-action"><?php esc_html_e('Add New Book', 'bd-bookshelf'); ?></a>
|
<a href="<?php echo admin_url('admin.php?page=bd-bookshelf-add'); ?>" class="page-title-action"><?php esc_html_e('Add New Book', 'umbra-bookshelf'); ?></a>
|
||||||
<hr class="wp-header-end">
|
<hr class="wp-header-end">
|
||||||
|
|
||||||
<table class="wp-list-table widefat fixed striped">
|
<table class="wp-list-table widefat fixed striped">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th style="width:30%"><?php esc_html_e('Title', 'bd-bookshelf'); ?></th>
|
<th style="width:30%"><?php esc_html_e('Title', 'umbra-bookshelf'); ?></th>
|
||||||
<th style="width:15%"><?php esc_html_e('ISBNs', 'bd-bookshelf'); ?></th>
|
<th style="width:15%"><?php esc_html_e('ISBNs', 'umbra-bookshelf'); ?></th>
|
||||||
<th style="width:15%"><?php esc_html_e('Ratings', 'bd-bookshelf'); ?></th>
|
<th style="width:15%"><?php esc_html_e('Ratings', 'umbra-bookshelf'); ?></th>
|
||||||
<th style="width:15%"><?php esc_html_e('Last Sync', 'bd-bookshelf'); ?></th>
|
<th style="width:15%"><?php esc_html_e('Last Sync', 'umbra-bookshelf'); ?></th>
|
||||||
<th style="width:15%"><?php esc_html_e('Actions', 'bd-bookshelf'); ?></th>
|
<th style="width:15%"><?php esc_html_e('Actions', 'umbra-bookshelf'); ?></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<?php if (empty($books)) : ?>
|
<?php if (empty($books)) : ?>
|
||||||
<tr><td colspan="5"><?php esc_html_e('No books found. Click "Add New Book" to get started.', 'bd-bookshelf'); ?></td></tr>
|
<tr><td colspan="5"><?php esc_html_e('No books found. Click "Add New Book" to get started.', 'umbra-bookshelf'); ?></td></tr>
|
||||||
<?php else : ?>
|
<?php else : ?>
|
||||||
<?php foreach ($books as $book) : ?>
|
<?php foreach ($books as $book) : ?>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -137,8 +130,8 @@ class BDM_Admin {
|
|||||||
</td>
|
</td>
|
||||||
<td><?php echo $book->last_sync ? date('M j, Y g:i a', strtotime($book->last_sync)) : '-'; ?></td>
|
<td><?php echo $book->last_sync ? date('M j, Y g:i a', strtotime($book->last_sync)) : '-'; ?></td>
|
||||||
<td>
|
<td>
|
||||||
<a href="<?php echo admin_url('admin.php?page=bd-bookshelf-add&book_id=' . $book->id); ?>"><?php esc_html_e('Edit', 'bd-bookshelf'); ?></a> |
|
<a href="<?php echo admin_url('admin.php?page=bd-bookshelf-add&book_id=' . $book->id); ?>"><?php esc_html_e('Edit', 'umbra-bookshelf'); ?></a> |
|
||||||
<a href="#" class="bd-bookshelf-delete" data-id="<?php echo $book->id; ?>" style="color:#a00"><?php esc_html_e('Delete', 'bd-bookshelf'); ?></a>
|
<a href="#" class="bd-bookshelf-delete" data-id="<?php echo $book->id; ?>" style="color:#a00"><?php esc_html_e('Delete', 'umbra-bookshelf'); ?></a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
@@ -154,45 +147,45 @@ class BDM_Admin {
|
|||||||
*/
|
*/
|
||||||
public function render_book_form() {
|
public function render_book_form() {
|
||||||
if (!current_user_can('manage_options')) {
|
if (!current_user_can('manage_options')) {
|
||||||
wp_die(__('You do not have sufficient permissions to access this page.', 'bd-bookshelf'));
|
wp_die(__('You do not have sufficient permissions to access this page.', 'umbra-bookshelf'));
|
||||||
}
|
}
|
||||||
|
|
||||||
$book_id = isset($_GET['book_id']) ? absint($_GET['book_id']) : 0;
|
$book_id = isset($_GET['book_id']) ? absint($_GET['book_id']) : 0;
|
||||||
$book = $book_id ? BDM_Book_Manager::get_book($book_id) : null;
|
$book = $book_id ? Umbra_Book_Manager::get_book($book_id) : null;
|
||||||
$series = BDM_Series::get_series(['number' => 100]);
|
$series = Umbra_Series::get_series(['number' => 100]);
|
||||||
$authors = BDM_Authors::get_authors(['number' => 100]);
|
$authors = Umbra_Authors::get_authors(['number' => 100]);
|
||||||
|
|
||||||
// Get existing retail links grouped by format and site
|
// Get existing retail links grouped by format and site
|
||||||
$existing_links = [];
|
$existing_links = [];
|
||||||
if ($book) {
|
if ($book) {
|
||||||
$raw_links = BDM_Book_Manager::get_book_retail_links($book_id);
|
$raw_links = Umbra_Book_Manager::get_book_retail_links($book_id);
|
||||||
foreach ($raw_links as $link) {
|
foreach ($raw_links as $link) {
|
||||||
$existing_links[$link->format][$link->site_name] = $link->url;
|
$existing_links[$link->format][$link->site_name] = $link->url;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retail sites for URL inputs
|
// Retail sites for URL inputs
|
||||||
$retail_sites = BDM_Retail_Sites::get_sites(true);
|
$retail_sites = Umbra_Retail_Sites::get_sites(true);
|
||||||
?>
|
?>
|
||||||
<div class="wrap">
|
<div class="wrap">
|
||||||
<h1><?php echo $book ? esc_html__('Edit Book', 'bd-bookshelf') : esc_html__('Add New Book', 'bd-bookshelf'); ?></h1>
|
<h1><?php echo $book ? esc_html__('Edit Book', 'umbra-bookshelf') : esc_html__('Add New Book', 'umbra-bookshelf'); ?></h1>
|
||||||
|
|
||||||
<form id="bd-bookshelf-book-form">
|
<form id="bd-bookshelf-book-form">
|
||||||
<?php wp_nonce_field('bd_bookshelf_save_book', 'nonce'); ?>
|
<?php wp_nonce_field('bd_bookshelf_save_book', 'nonce'); ?>
|
||||||
<input type="hidden" name="book_id" value="<?php echo $book_id; ?>">
|
<input type="hidden" name="book_id" value="<?php echo $book_id; ?>">
|
||||||
|
|
||||||
<h2><?php esc_html_e('Book Details', 'bd-bookshelf'); ?></h2>
|
<h2><?php esc_html_e('Book Details', 'umbra-bookshelf'); ?></h2>
|
||||||
<table class="form-table">
|
<table class="form-table">
|
||||||
<tr>
|
<tr>
|
||||||
<th><label for="title"><?php esc_html_e('Title', 'bd-bookshelf'); ?> <span class="required">*</span></label></th>
|
<th><label for="title"><?php esc_html_e('Title', 'umbra-bookshelf'); ?> <span class="required">*</span></label></th>
|
||||||
<td><input type="text" name="title" id="title" value="<?php echo $book ? esc_attr($book->title) : ''; ?>" class="regular-text" required></td>
|
<td><input type="text" name="title" id="title" value="<?php echo $book ? esc_attr($book->title) : ''; ?>" class="regular-text" required></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th><label for="description"><?php esc_html_e('Description', 'bd-bookshelf'); ?></label></th>
|
<th><label for="description"><?php esc_html_e('Description', 'umbra-bookshelf'); ?></label></th>
|
||||||
<td><textarea name="description" id="description" rows="5" class="large-text"><?php echo $book ? esc_textarea($book->description) : ''; ?></textarea></td>
|
<td><textarea name="description" id="description" rows="5" class="large-text"><?php echo $book ? esc_textarea($book->description) : ''; ?></textarea></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th><label><?php esc_html_e('Cover Image', 'bd-bookshelf'); ?></label></th>
|
<th><label><?php esc_html_e('Cover Image', 'umbra-bookshelf'); ?></label></th>
|
||||||
<td>
|
<td>
|
||||||
<div class="bd-bookshelf-cover-upload">
|
<div class="bd-bookshelf-cover-upload">
|
||||||
<input type="hidden" name="cover_image" id="cover_image" value="<?php echo $book ? esc_url($book->cover_image) : ''; ?>">
|
<input type="hidden" name="cover_image" id="cover_image" value="<?php echo $book ? esc_url($book->cover_image) : ''; ?>">
|
||||||
@@ -201,27 +194,27 @@ class BDM_Admin {
|
|||||||
<img src="<?php echo esc_url($book->cover_image); ?>" style="max-width:200px;height:auto">
|
<img src="<?php echo esc_url($book->cover_image); ?>" style="max-width:200px;height:auto">
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
<button type="button" id="cover-upload-btn" class="button"><?php esc_html_e('Select or Upload Cover', 'bd-bookshelf'); ?></button>
|
<button type="button" id="cover-upload-btn" class="button"><?php esc_html_e('Select or Upload Cover', 'umbra-bookshelf'); ?></button>
|
||||||
<button type="button" id="cover-remove-btn" class="button" style="display:none;color:#a00"><?php esc_html_e('Remove', 'bd-bookshelf'); ?></button>
|
<button type="button" id="cover-remove-btn" class="button" style="display:none;color:#a00"><?php esc_html_e('Remove', 'umbra-bookshelf'); ?></button>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th><label for="series_id"><?php esc_html_e('Series', 'bd-bookshelf'); ?></label></th>
|
<th><label for="series_id"><?php esc_html_e('Series', 'umbra-bookshelf'); ?></label></th>
|
||||||
<td>
|
<td>
|
||||||
<select name="series_id" id="series_id">
|
<select name="series_id" id="series_id">
|
||||||
<option value="0"><?php esc_html_e('None', 'bd-bookshelf'); ?></option>
|
<option value="0"><?php esc_html_e('None', 'umbra-bookshelf'); ?></option>
|
||||||
<?php foreach ($series as $s) : ?>
|
<?php foreach ($series as $s) : ?>
|
||||||
<option value="<?php echo $s->id; ?>" <?php selected($book->series_id ?? 0, $s->id); ?>><?php echo esc_html($s->name); ?></option>
|
<option value="<?php echo $s->id; ?>" <?php selected($book->series_id ?? 0, $s->id); ?>><?php echo esc_html($s->name); ?></option>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
</select>
|
</select>
|
||||||
<a href="<?php echo admin_url('admin.php?page=bd-bookshelf-series'); ?>" target="_blank" class="button button-small"><?php esc_html_e('Manage Series', 'bd-bookshelf'); ?></a>
|
<a href="<?php echo admin_url('admin.php?page=bd-bookshelf-series'); ?>" target="_blank" class="button button-small"><?php esc_html_e('Manage Series', 'umbra-bookshelf'); ?></a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<h2><?php esc_html_e('Authors', 'bd-bookshelf'); ?></h2>
|
<h2><?php esc_html_e('Authors', 'umbra-bookshelf'); ?></h2>
|
||||||
<p class="description"><?php esc_html_e('Select up to 3 authors for this book.', 'bd-bookshelf'); ?></p>
|
<p class="description"><?php esc_html_e('Select up to 3 authors for this book.', 'umbra-bookshelf'); ?></p>
|
||||||
<div id="bd-bookshelf-authors-container">
|
<div id="bd-bookshelf-authors-container">
|
||||||
<?php
|
<?php
|
||||||
$book_authors = [];
|
$book_authors = [];
|
||||||
@@ -235,7 +228,7 @@ class BDM_Admin {
|
|||||||
?>
|
?>
|
||||||
<div class="bd-bookshelf-author-row">
|
<div class="bd-bookshelf-author-row">
|
||||||
<select name="authors[]" class="bd-bookshelf-author-select">
|
<select name="authors[]" class="bd-bookshelf-author-select">
|
||||||
<option value="0"><?php esc_html_e('-- Select Author --', 'bd-bookshelf'); ?></option>
|
<option value="0"><?php esc_html_e('-- Select Author --', 'umbra-bookshelf'); ?></option>
|
||||||
<?php foreach ($authors as $a) : ?>
|
<?php foreach ($authors as $a) : ?>
|
||||||
<option value="<?php echo $a->id; ?>" <?php selected($selected, $a->id); ?>><?php echo esc_html($a->display_name); ?></option>
|
<option value="<?php echo $a->id; ?>" <?php selected($selected, $a->id); ?>><?php echo esc_html($a->display_name); ?></option>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
@@ -243,35 +236,35 @@ class BDM_Admin {
|
|||||||
</div>
|
</div>
|
||||||
<?php endfor; ?>
|
<?php endfor; ?>
|
||||||
</div>
|
</div>
|
||||||
<a href="<?php echo admin_url('admin.php?page=bd-bookshelf-authors'); ?>" target="_blank" class="button button-small"><?php esc_html_e('Manage Authors', 'bd-bookshelf'); ?></a>
|
<a href="<?php echo admin_url('admin.php?page=bd-bookshelf-authors'); ?>" target="_blank" class="button button-small"><?php esc_html_e('Manage Authors', 'umbra-bookshelf'); ?></a>
|
||||||
|
|
||||||
<h2><?php esc_html_e('ISBNs & ASINs', 'bd-bookshelf'); ?></h2>
|
<h2><?php esc_html_e('ISBNs & ASINs', 'umbra-bookshelf'); ?></h2>
|
||||||
<table class="wp-list-table widefat fixed striped bd-bookshelf-isbn-grid">
|
<table class="wp-list-table widefat fixed striped bd-bookshelf-isbn-grid">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th style="width:120px"><?php esc_html_e('Format', 'bd-bookshelf'); ?></th>
|
<th style="width:120px"><?php esc_html_e('Format', 'umbra-bookshelf'); ?></th>
|
||||||
<th><?php esc_html_e('ISBN', 'bd-bookshelf'); ?></th>
|
<th><?php esc_html_e('ISBN', 'umbra-bookshelf'); ?></th>
|
||||||
<th><?php esc_html_e('Amazon ASIN', 'bd-bookshelf'); ?></th>
|
<th><?php esc_html_e('Amazon ASIN', 'umbra-bookshelf'); ?></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td><strong><?php esc_html_e('eBook', 'bd-bookshelf'); ?></strong></td>
|
<td><strong><?php esc_html_e('eBook', 'umbra-bookshelf'); ?></strong></td>
|
||||||
<td><input type="text" name="isbn_ebook" value="<?php echo $book ? esc_attr($book->isbn_ebook) : ''; ?>" class="regular-text"></td>
|
<td><input type="text" name="isbn_ebook" value="<?php echo $book ? esc_attr($book->isbn_ebook) : ''; ?>" class="regular-text"></td>
|
||||||
<td><input type="text" name="asin_ebook" value="<?php echo $book ? esc_attr($book->asin_ebook ?? '') : ''; ?>" class="regular-text"></td>
|
<td><input type="text" name="asin_ebook" value="<?php echo $book ? esc_attr($book->asin_ebook ?? '') : ''; ?>" class="regular-text"></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><strong><?php esc_html_e('Paperback', 'bd-bookshelf'); ?></strong></td>
|
<td><strong><?php esc_html_e('Paperback', 'umbra-bookshelf'); ?></strong></td>
|
||||||
<td><input type="text" name="isbn_paperback" value="<?php echo $book ? esc_attr($book->isbn_paperback) : ''; ?>" class="regular-text"></td>
|
<td><input type="text" name="isbn_paperback" value="<?php echo $book ? esc_attr($book->isbn_paperback) : ''; ?>" class="regular-text"></td>
|
||||||
<td><input type="text" name="asin_paperback" value="<?php echo $book ? esc_attr($book->asin_paperback ?? '') : ''; ?>" class="regular-text"></td>
|
<td><input type="text" name="asin_paperback" value="<?php echo $book ? esc_attr($book->asin_paperback ?? '') : ''; ?>" class="regular-text"></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><strong><?php esc_html_e('Hardcover', 'bd-bookshelf'); ?></strong></td>
|
<td><strong><?php esc_html_e('Hardcover', 'umbra-bookshelf'); ?></strong></td>
|
||||||
<td><input type="text" name="isbn_hardcover" value="<?php echo $book ? esc_attr($book->isbn_hardcover) : ''; ?>" class="regular-text"></td>
|
<td><input type="text" name="isbn_hardcover" value="<?php echo $book ? esc_attr($book->isbn_hardcover) : ''; ?>" class="regular-text"></td>
|
||||||
<td><input type="text" name="asin_hardcover" value="<?php echo $book ? esc_attr($book->asin_hardcover ?? '') : ''; ?>" class="regular-text"></td>
|
<td><input type="text" name="asin_hardcover" value="<?php echo $book ? esc_attr($book->asin_hardcover ?? '') : ''; ?>" class="regular-text"></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><strong><?php esc_html_e('Audio', 'bd-bookshelf'); ?></strong></td>
|
<td><strong><?php esc_html_e('Audio', 'umbra-bookshelf'); ?></strong></td>
|
||||||
<td><input type="text" name="isbn_audio" value="<?php echo $book ? esc_attr($book->isbn_audio) : ''; ?>" class="regular-text"></td>
|
<td><input type="text" name="isbn_audio" value="<?php echo $book ? esc_attr($book->isbn_audio) : ''; ?>" class="regular-text"></td>
|
||||||
<td><input type="text" name="asin_audio" value="<?php echo $book ? esc_attr($book->asin_audio ?? '') : ''; ?>" class="regular-text"></td>
|
<td><input type="text" name="asin_audio" value="<?php echo $book ? esc_attr($book->asin_audio ?? '') : ''; ?>" class="regular-text"></td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -279,8 +272,8 @@ class BDM_Admin {
|
|||||||
</table>
|
</table>
|
||||||
<input type="hidden" name="goodreads_id" value="<?php echo $book ? esc_attr($book->goodreads_id) : ''; ?>">
|
<input type="hidden" name="goodreads_id" value="<?php echo $book ? esc_attr($book->goodreads_id) : ''; ?>">
|
||||||
|
|
||||||
<h2><?php esc_html_e('Retail Links', 'bd-bookshelf'); ?></h2>
|
<h2><?php esc_html_e('Retail Links', 'umbra-bookshelf'); ?></h2>
|
||||||
<p class="description"><?php esc_html_e('Paste the direct purchase URL for each format and retailer. Leave blank if not available.', 'bd-bookshelf'); ?></p>
|
<p class="description"><?php esc_html_e('Paste the direct purchase URL for each format and retailer. Leave blank if not available.', 'umbra-bookshelf'); ?></p>
|
||||||
|
|
||||||
<?php
|
<?php
|
||||||
$formats_config = [
|
$formats_config = [
|
||||||
@@ -322,7 +315,7 @@ class BDM_Admin {
|
|||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
|
|
||||||
<p class="submit">
|
<p class="submit">
|
||||||
<button type="submit" class="button button-primary button-hero"><?php esc_html_e('Save Book', 'bd-bookshelf'); ?></button>
|
<button type="submit" class="button button-primary button-hero"><?php esc_html_e('Save Book', 'umbra-bookshelf'); ?></button>
|
||||||
</p>
|
</p>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@@ -334,29 +327,29 @@ class BDM_Admin {
|
|||||||
*/
|
*/
|
||||||
public function render_authors_page() {
|
public function render_authors_page() {
|
||||||
if (!current_user_can('manage_options')) {
|
if (!current_user_can('manage_options')) {
|
||||||
wp_die(__('You do not have sufficient permissions to access this page.', 'bd-bookshelf'));
|
wp_die(__('You do not have sufficient permissions to access this page.', 'umbra-bookshelf'));
|
||||||
}
|
}
|
||||||
|
|
||||||
$authors = BDM_Authors::get_authors(['number' => 100]);
|
$authors = Umbra_Authors::get_authors(['number' => 100]);
|
||||||
?>
|
?>
|
||||||
<div class="wrap">
|
<div class="wrap">
|
||||||
<h1 class="wp-heading-inline"><?php esc_html_e('Authors', 'bd-bookshelf'); ?></h1>
|
<h1 class="wp-heading-inline"><?php esc_html_e('Authors', 'umbra-bookshelf'); ?></h1>
|
||||||
<button type="button" id="bd-bookshelf-add-author" class="page-title-action"><?php esc_html_e('Add New Author', 'bd-bookshelf'); ?></button>
|
<button type="button" id="bd-bookshelf-add-author" class="page-title-action"><?php esc_html_e('Add New Author', 'umbra-bookshelf'); ?></button>
|
||||||
<hr class="wp-header-end">
|
<hr class="wp-header-end">
|
||||||
|
|
||||||
<table class="wp-list-table widefat fixed striped">
|
<table class="wp-list-table widefat fixed striped">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th style="width:20%"><?php esc_html_e('Name', 'bd-bookshelf'); ?></th>
|
<th style="width:20%"><?php esc_html_e('Name', 'umbra-bookshelf'); ?></th>
|
||||||
<th style="width:20%"><?php esc_html_e('Email', 'bd-bookshelf'); ?></th>
|
<th style="width:20%"><?php esc_html_e('Email', 'umbra-bookshelf'); ?></th>
|
||||||
<th style="width:15%"><?php esc_html_e('Website', 'bd-bookshelf'); ?></th>
|
<th style="width:15%"><?php esc_html_e('Website', 'umbra-bookshelf'); ?></th>
|
||||||
<th style="width:15%"><?php esc_html_e('Goodreads ID', 'bd-bookshelf'); ?></th>
|
<th style="width:15%"><?php esc_html_e('Goodreads ID', 'umbra-bookshelf'); ?></th>
|
||||||
<th style="width:15%"><?php esc_html_e('Actions', 'bd-bookshelf'); ?></th>
|
<th style="width:15%"><?php esc_html_e('Actions', 'umbra-bookshelf'); ?></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<?php if (empty($authors)) : ?>
|
<?php if (empty($authors)) : ?>
|
||||||
<tr><td colspan="5"><?php esc_html_e('No authors found.', 'bd-bookshelf'); ?></td></tr>
|
<tr><td colspan="5"><?php esc_html_e('No authors found.', 'umbra-bookshelf'); ?></td></tr>
|
||||||
<?php else : ?>
|
<?php else : ?>
|
||||||
<?php foreach ($authors as $author) : ?>
|
<?php foreach ($authors as $author) : ?>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -365,8 +358,8 @@ class BDM_Admin {
|
|||||||
<td><?php echo $author->website ? '<a href="' . esc_url($author->website) . '" target="_blank">' . esc_html($author->website) . '</a>' : '-'; ?></td>
|
<td><?php echo $author->website ? '<a href="' . esc_url($author->website) . '" target="_blank">' . esc_html($author->website) . '</a>' : '-'; ?></td>
|
||||||
<td><?php echo esc_html($author->goodreads_author_id); ?></td>
|
<td><?php echo esc_html($author->goodreads_author_id); ?></td>
|
||||||
<td>
|
<td>
|
||||||
<a href="#" class="bd-bookshelf-edit-author" data-id="<?php echo $author->id; ?>"><?php esc_html_e('Edit', 'bd-bookshelf'); ?></a> |
|
<a href="#" class="bd-bookshelf-edit-author" data-id="<?php echo $author->id; ?>"><?php esc_html_e('Edit', 'umbra-bookshelf'); ?></a> |
|
||||||
<a href="#" class="bd-bookshelf-delete-author" data-id="<?php echo $author->id; ?>" style="color:#a00"><?php esc_html_e('Delete', 'bd-bookshelf'); ?></a>
|
<a href="#" class="bd-bookshelf-delete-author" data-id="<?php echo $author->id; ?>" style="color:#a00"><?php esc_html_e('Delete', 'umbra-bookshelf'); ?></a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
@@ -379,7 +372,7 @@ class BDM_Admin {
|
|||||||
<div id="bd-bookshelf-author-modal" class="bd-bookshelf-modal" style="display:none">
|
<div id="bd-bookshelf-author-modal" class="bd-bookshelf-modal" style="display:none">
|
||||||
<div class="bd-bookshelf-modal-content">
|
<div class="bd-bookshelf-modal-content">
|
||||||
<div class="bd-bookshelf-modal-header">
|
<div class="bd-bookshelf-modal-header">
|
||||||
<h2 id="bd-bookshelf-author-modal-title"><?php esc_html_e('Add New Author', 'bd-bookshelf'); ?></h2>
|
<h2 id="bd-bookshelf-author-modal-title"><?php esc_html_e('Add New Author', 'umbra-bookshelf'); ?></h2>
|
||||||
<button type="button" class="bd-bookshelf-modal-close">×</button>
|
<button type="button" class="bd-bookshelf-modal-close">×</button>
|
||||||
</div>
|
</div>
|
||||||
<form id="bd-bookshelf-author-form">
|
<form id="bd-bookshelf-author-form">
|
||||||
@@ -387,37 +380,37 @@ class BDM_Admin {
|
|||||||
<input type="hidden" name="author_id" id="author_id" value="0">
|
<input type="hidden" name="author_id" id="author_id" value="0">
|
||||||
<table class="form-table">
|
<table class="form-table">
|
||||||
<tr>
|
<tr>
|
||||||
<th><label for="author_display_name"><?php esc_html_e('Display Name', 'bd-bookshelf'); ?> <span class="required">*</span></label></th>
|
<th><label for="author_display_name"><?php esc_html_e('Display Name', 'umbra-bookshelf'); ?> <span class="required">*</span></label></th>
|
||||||
<td><input type="text" name="display_name" id="author_display_name" class="regular-text" required></td>
|
<td><input type="text" name="display_name" id="author_display_name" class="regular-text" required></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th><label for="author_email"><?php esc_html_e('Email', 'bd-bookshelf'); ?></label></th>
|
<th><label for="author_email"><?php esc_html_e('Email', 'umbra-bookshelf'); ?></label></th>
|
||||||
<td><input type="email" name="email" id="author_email" class="regular-text"></td>
|
<td><input type="email" name="email" id="author_email" class="regular-text"></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th><label for="author_website"><?php esc_html_e('Website', 'bd-bookshelf'); ?></label></th>
|
<th><label for="author_website"><?php esc_html_e('Website', 'umbra-bookshelf'); ?></label></th>
|
||||||
<td><input type="url" name="website" id="author_website" class="regular-text"></td>
|
<td><input type="url" name="website" id="author_website" class="regular-text"></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th><label for="author_bio"><?php esc_html_e('Bio', 'bd-bookshelf'); ?></label></th>
|
<th><label for="author_bio"><?php esc_html_e('Bio', 'umbra-bookshelf'); ?></label></th>
|
||||||
<td><textarea name="bio" id="author_bio" rows="4" class="large-text"></textarea></td>
|
<td><textarea name="bio" id="author_bio" rows="4" class="large-text"></textarea></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th><label for="author_goodreads_id"><?php esc_html_e('Goodreads Author ID', 'bd-bookshelf'); ?></label></th>
|
<th><label for="author_goodreads_id"><?php esc_html_e('Goodreads Author ID', 'umbra-bookshelf'); ?></label></th>
|
||||||
<td><input type="text" name="goodreads_author_id" id="author_goodreads_id" class="regular-text"></td>
|
<td><input type="text" name="goodreads_author_id" id="author_goodreads_id" class="regular-text"></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th><label><?php esc_html_e('Photo', 'bd-bookshelf'); ?></label></th>
|
<th><label><?php esc_html_e('Photo', 'umbra-bookshelf'); ?></label></th>
|
||||||
<td>
|
<td>
|
||||||
<input type="hidden" name="photo_url" id="author_photo_url" value="">
|
<input type="hidden" name="photo_url" id="author_photo_url" value="">
|
||||||
<div id="author-photo-preview" style="margin-bottom:10px"></div>
|
<div id="author-photo-preview" style="margin-bottom:10px"></div>
|
||||||
<button type="button" id="author-photo-btn" class="button"><?php esc_html_e('Select Photo', 'bd-bookshelf'); ?></button>
|
<button type="button" id="author-photo-btn" class="button"><?php esc_html_e('Select Photo', 'umbra-bookshelf'); ?></button>
|
||||||
<button type="button" id="author-photo-remove" class="button" style="display:none;color:#a00"><?php esc_html_e('Remove', 'bd-bookshelf'); ?></button>
|
<button type="button" id="author-photo-remove" class="button" style="display:none;color:#a00"><?php esc_html_e('Remove', 'umbra-bookshelf'); ?></button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
<p class="submit">
|
<p class="submit">
|
||||||
<button type="submit" class="button button-primary"><?php esc_html_e('Save Author', 'bd-bookshelf'); ?></button>
|
<button type="submit" class="button button-primary"><?php esc_html_e('Save Author', 'umbra-bookshelf'); ?></button>
|
||||||
</p>
|
</p>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@@ -430,35 +423,35 @@ class BDM_Admin {
|
|||||||
*/
|
*/
|
||||||
public function render_series_page() {
|
public function render_series_page() {
|
||||||
if (!current_user_can('manage_options')) {
|
if (!current_user_can('manage_options')) {
|
||||||
wp_die(__('You do not have sufficient permissions to access this page.', 'bd-bookshelf'));
|
wp_die(__('You do not have sufficient permissions to access this page.', 'umbra-bookshelf'));
|
||||||
}
|
}
|
||||||
|
|
||||||
$series = BDM_Series::get_series(['number' => 100]);
|
$series = Umbra_Series::get_series(['number' => 100]);
|
||||||
?>
|
?>
|
||||||
<div class="wrap">
|
<div class="wrap">
|
||||||
<h1 class="wp-heading-inline"><?php esc_html_e('Series', 'bd-bookshelf'); ?></h1>
|
<h1 class="wp-heading-inline"><?php esc_html_e('Series', 'umbra-bookshelf'); ?></h1>
|
||||||
<button type="button" id="bd-bookshelf-add-series" class="page-title-action"><?php esc_html_e('Add New Series', 'bd-bookshelf'); ?></button>
|
<button type="button" id="bd-bookshelf-add-series" class="page-title-action"><?php esc_html_e('Add New Series', 'umbra-bookshelf'); ?></button>
|
||||||
<hr class="wp-header-end">
|
<hr class="wp-header-end">
|
||||||
|
|
||||||
<table class="wp-list-table widefat fixed striped">
|
<table class="wp-list-table widefat fixed striped">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th style="width:30%"><?php esc_html_e('Name', 'bd-bookshelf'); ?></th>
|
<th style="width:30%"><?php esc_html_e('Name', 'umbra-bookshelf'); ?></th>
|
||||||
<th style="width:50%"><?php esc_html_e('Description', 'bd-bookshelf'); ?></th>
|
<th style="width:50%"><?php esc_html_e('Description', 'umbra-bookshelf'); ?></th>
|
||||||
<th style="width:20%"><?php esc_html_e('Actions', 'bd-bookshelf'); ?></th>
|
<th style="width:20%"><?php esc_html_e('Actions', 'umbra-bookshelf'); ?></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<?php if (empty($series)) : ?>
|
<?php if (empty($series)) : ?>
|
||||||
<tr><td colspan="3"><?php esc_html_e('No series found.', 'bd-bookshelf'); ?></td></tr>
|
<tr><td colspan="3"><?php esc_html_e('No series found.', 'umbra-bookshelf'); ?></td></tr>
|
||||||
<?php else : ?>
|
<?php else : ?>
|
||||||
<?php foreach ($series as $s) : ?>
|
<?php foreach ($series as $s) : ?>
|
||||||
<tr>
|
<tr>
|
||||||
<td><strong><?php echo esc_html($s->name); ?></strong></td>
|
<td><strong><?php echo esc_html($s->name); ?></strong></td>
|
||||||
<td><?php echo esc_html(wp_trim_words($s->description, 15)); ?></td>
|
<td><?php echo esc_html(wp_trim_words($s->description, 15)); ?></td>
|
||||||
<td>
|
<td>
|
||||||
<a href="#" class="bd-bookshelf-edit-series" data-id="<?php echo $s->id; ?>"><?php esc_html_e('Edit', 'bd-bookshelf'); ?></a> |
|
<a href="#" class="bd-bookshelf-edit-series" data-id="<?php echo $s->id; ?>"><?php esc_html_e('Edit', 'umbra-bookshelf'); ?></a> |
|
||||||
<a href="#" class="bd-bookshelf-delete-series" data-id="<?php echo $s->id; ?>" style="color:#a00"><?php esc_html_e('Delete', 'bd-bookshelf'); ?></a>
|
<a href="#" class="bd-bookshelf-delete-series" data-id="<?php echo $s->id; ?>" style="color:#a00"><?php esc_html_e('Delete', 'umbra-bookshelf'); ?></a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
@@ -471,7 +464,7 @@ class BDM_Admin {
|
|||||||
<div id="bd-bookshelf-series-modal" class="bd-bookshelf-modal" style="display:none">
|
<div id="bd-bookshelf-series-modal" class="bd-bookshelf-modal" style="display:none">
|
||||||
<div class="bd-bookshelf-modal-content">
|
<div class="bd-bookshelf-modal-content">
|
||||||
<div class="bd-bookshelf-modal-header">
|
<div class="bd-bookshelf-modal-header">
|
||||||
<h2 id="bd-bookshelf-series-modal-title"><?php esc_html_e('Add New Series', 'bd-bookshelf'); ?></h2>
|
<h2 id="bd-bookshelf-series-modal-title"><?php esc_html_e('Add New Series', 'umbra-bookshelf'); ?></h2>
|
||||||
<button type="button" class="bd-bookshelf-modal-close">×</button>
|
<button type="button" class="bd-bookshelf-modal-close">×</button>
|
||||||
</div>
|
</div>
|
||||||
<form id="bd-bookshelf-series-form">
|
<form id="bd-bookshelf-series-form">
|
||||||
@@ -479,16 +472,16 @@ class BDM_Admin {
|
|||||||
<input type="hidden" name="series_id" id="series_id_input" value="0">
|
<input type="hidden" name="series_id" id="series_id_input" value="0">
|
||||||
<table class="form-table">
|
<table class="form-table">
|
||||||
<tr>
|
<tr>
|
||||||
<th><label for="series_name"><?php esc_html_e('Series Name', 'bd-bookshelf'); ?> <span class="required">*</span></label></th>
|
<th><label for="series_name"><?php esc_html_e('Series Name', 'umbra-bookshelf'); ?> <span class="required">*</span></label></th>
|
||||||
<td><input type="text" name="name" id="series_name" class="regular-text" required></td>
|
<td><input type="text" name="name" id="series_name" class="regular-text" required></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th><label for="series_description"><?php esc_html_e('Description', 'bd-bookshelf'); ?></label></th>
|
<th><label for="series_description"><?php esc_html_e('Description', 'umbra-bookshelf'); ?></label></th>
|
||||||
<td><textarea name="description" id="series_description" rows="4" class="large-text"></textarea></td>
|
<td><textarea name="description" id="series_description" rows="4" class="large-text"></textarea></td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
<p class="submit">
|
<p class="submit">
|
||||||
<button type="submit" class="button button-primary"><?php esc_html_e('Save Series', 'bd-bookshelf'); ?></button>
|
<button type="submit" class="button button-primary"><?php esc_html_e('Save Series', 'umbra-bookshelf'); ?></button>
|
||||||
</p>
|
</p>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@@ -501,14 +494,14 @@ class BDM_Admin {
|
|||||||
*/
|
*/
|
||||||
public function render_sites_page() {
|
public function render_sites_page() {
|
||||||
if (!current_user_can('manage_options')) {
|
if (!current_user_can('manage_options')) {
|
||||||
wp_die(__('You do not have sufficient permissions to access this page.', 'bd-bookshelf'));
|
wp_die(__('You do not have sufficient permissions to access this page.', 'umbra-bookshelf'));
|
||||||
}
|
}
|
||||||
|
|
||||||
$sites = BDM_Retail_Sites::get_sites(false);
|
$sites = Umbra_Retail_Sites::get_sites(false);
|
||||||
?>
|
?>
|
||||||
<div class="wrap">
|
<div class="wrap">
|
||||||
<h1><?php esc_html_e('Retail Sites & Affiliate Codes', 'bd-bookshelf'); ?></h1>
|
<h1><?php esc_html_e('Retail Sites & Affiliate Codes', 'umbra-bookshelf'); ?></h1>
|
||||||
<p class="description"><?php esc_html_e('Enter your affiliate codes for each retail site. These codes will be appended to book links.', 'bd-bookshelf'); ?></p>
|
<p class="description"><?php esc_html_e('Enter your affiliate codes for each retail site. These codes will be appended to book links.', 'umbra-bookshelf'); ?></p>
|
||||||
|
|
||||||
<form id="bd-bookshelf-sites-form">
|
<form id="bd-bookshelf-sites-form">
|
||||||
<?php wp_nonce_field('bd_bookshelf_admin_nonce', 'sites_nonce'); ?>
|
<?php wp_nonce_field('bd_bookshelf_admin_nonce', 'sites_nonce'); ?>
|
||||||
@@ -516,10 +509,10 @@ class BDM_Admin {
|
|||||||
<table class="wp-list-table widefat fixed striped">
|
<table class="wp-list-table widefat fixed striped">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th style="width:25%"><?php esc_html_e('Site', 'bd-bookshelf'); ?></th>
|
<th style="width:25%"><?php esc_html_e('Site', 'umbra-bookshelf'); ?></th>
|
||||||
<th style="width:35%"><?php esc_html_e('URL Pattern', 'bd-bookshelf'); ?></th>
|
<th style="width:35%"><?php esc_html_e('URL Pattern', 'umbra-bookshelf'); ?></th>
|
||||||
<th style="width:15%"><?php esc_html_e('Affiliate Param', 'bd-bookshelf'); ?></th>
|
<th style="width:15%"><?php esc_html_e('Affiliate Param', 'umbra-bookshelf'); ?></th>
|
||||||
<th style="width:25%"><?php esc_html_e('Your Affiliate Code', 'bd-bookshelf'); ?></th>
|
<th style="width:25%"><?php esc_html_e('Your Affiliate Code', 'umbra-bookshelf'); ?></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -529,7 +522,7 @@ class BDM_Admin {
|
|||||||
<td><code style="font-size:11px"><?php echo esc_html($site->url_pattern); ?></code></td>
|
<td><code style="font-size:11px"><?php echo esc_html($site->url_pattern); ?></code></td>
|
||||||
<td><?php echo esc_html($site->affiliate_param); ?></td>
|
<td><?php echo esc_html($site->affiliate_param); ?></td>
|
||||||
<td>
|
<td>
|
||||||
<input type="text" name="affiliate[<?php echo $site->id; ?>]" value="<?php echo esc_attr($site->affiliate_code); ?>" class="regular-text" placeholder="<?php esc_attr_e('Enter affiliate code', 'bd-bookshelf'); ?>">
|
<input type="text" name="affiliate[<?php echo $site->id; ?>]" value="<?php echo esc_attr($site->affiliate_code); ?>" class="regular-text" placeholder="<?php esc_attr_e('Enter affiliate code', 'umbra-bookshelf'); ?>">
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
@@ -537,7 +530,7 @@ class BDM_Admin {
|
|||||||
</table>
|
</table>
|
||||||
|
|
||||||
<p class="submit">
|
<p class="submit">
|
||||||
<button type="submit" class="button button-primary button-hero"><?php esc_html_e('Save Affiliate Codes', 'bd-bookshelf'); ?></button>
|
<button type="submit" class="button button-primary button-hero"><?php esc_html_e('Save Affiliate Codes', 'umbra-bookshelf'); ?></button>
|
||||||
</p>
|
</p>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@@ -549,33 +542,33 @@ class BDM_Admin {
|
|||||||
*/
|
*/
|
||||||
public function render_shortcodes_page() {
|
public function render_shortcodes_page() {
|
||||||
if (!current_user_can('manage_options')) {
|
if (!current_user_can('manage_options')) {
|
||||||
wp_die(__('You do not have sufficient permissions to access this page.', 'bd-bookshelf'));
|
wp_die(__('You do not have sufficient permissions to access this page.', 'umbra-bookshelf'));
|
||||||
}
|
}
|
||||||
|
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
$table = BDM_Database::table('shortcodes');
|
$table = Umbra_Database::table('shortcodes');
|
||||||
$shortcodes = $wpdb->get_results("SELECT * FROM {$table} ORDER BY created_at DESC");
|
$shortcodes = $wpdb->get_results("SELECT * FROM {$table} ORDER BY created_at DESC");
|
||||||
?>
|
?>
|
||||||
<div class="wrap">
|
<div class="wrap">
|
||||||
<h1 class="wp-heading-inline"><?php esc_html_e('Custom Shortcodes', 'bd-bookshelf'); ?></h1>
|
<h1 class="wp-heading-inline"><?php esc_html_e('Custom Shortcodes', 'umbra-bookshelf'); ?></h1>
|
||||||
<button type="button" id="bd-bookshelf-add-shortcode" class="page-title-action"><?php esc_html_e('Add New Shortcode', 'bd-bookshelf'); ?></button>
|
<button type="button" id="bd-bookshelf-add-shortcode" class="page-title-action"><?php esc_html_e('Add New Shortcode', 'umbra-bookshelf'); ?></button>
|
||||||
<hr class="wp-header-end">
|
<hr class="wp-header-end">
|
||||||
|
|
||||||
<p class="description"><?php esc_html_e('Create shortcodes that redirect to external URLs. Use [slug] in your posts to redirect visitors.', 'bd-bookshelf'); ?></p>
|
<p class="description"><?php esc_html_e('Create shortcodes that redirect to external URLs. Use [slug] in your posts to redirect visitors.', 'umbra-bookshelf'); ?></p>
|
||||||
|
|
||||||
<table class="wp-list-table widefat fixed striped">
|
<table class="wp-list-table widefat fixed striped">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th style="width:20%"><?php esc_html_e('Shortcode', 'bd-bookshelf'); ?></th>
|
<th style="width:20%"><?php esc_html_e('Shortcode', 'umbra-bookshelf'); ?></th>
|
||||||
<th style="width:30%"><?php esc_html_e('Target URL', 'bd-bookshelf'); ?></th>
|
<th style="width:30%"><?php esc_html_e('Target URL', 'umbra-bookshelf'); ?></th>
|
||||||
<th style="width:15%"><?php esc_html_e('Redirect', 'bd-bookshelf'); ?></th>
|
<th style="width:15%"><?php esc_html_e('Redirect', 'umbra-bookshelf'); ?></th>
|
||||||
<th style="width:15%"><?php esc_html_e('Clicks', 'bd-bookshelf'); ?></th>
|
<th style="width:15%"><?php esc_html_e('Clicks', 'umbra-bookshelf'); ?></th>
|
||||||
<th style="width:20%"><?php esc_html_e('Actions', 'bd-bookshelf'); ?></th>
|
<th style="width:20%"><?php esc_html_e('Actions', 'umbra-bookshelf'); ?></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<?php if (empty($shortcodes)) : ?>
|
<?php if (empty($shortcodes)) : ?>
|
||||||
<tr><td colspan="5"><?php esc_html_e('No shortcodes found.', 'bd-bookshelf'); ?></td></tr>
|
<tr><td colspan="5"><?php esc_html_e('No shortcodes found.', 'umbra-bookshelf'); ?></td></tr>
|
||||||
<?php else : ?>
|
<?php else : ?>
|
||||||
<?php foreach ($shortcodes as $sc) : ?>
|
<?php foreach ($shortcodes as $sc) : ?>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -584,8 +577,8 @@ class BDM_Admin {
|
|||||||
<td><?php echo esc_html($sc->redirect_type); ?></td>
|
<td><?php echo esc_html($sc->redirect_type); ?></td>
|
||||||
<td><?php echo number_format($sc->click_count); ?></td>
|
<td><?php echo number_format($sc->click_count); ?></td>
|
||||||
<td>
|
<td>
|
||||||
<a href="#" class="bd-bookshelf-edit-shortcode" data-id="<?php echo $sc->id; ?>"><?php esc_html_e('Edit', 'bd-bookshelf'); ?></a> |
|
<a href="#" class="bd-bookshelf-edit-shortcode" data-id="<?php echo $sc->id; ?>"><?php esc_html_e('Edit', 'umbra-bookshelf'); ?></a> |
|
||||||
<a href="#" class="bd-bookshelf-delete-shortcode" data-id="<?php echo $sc->id; ?>" style="color:#a00"><?php esc_html_e('Delete', 'bd-bookshelf'); ?></a>
|
<a href="#" class="bd-bookshelf-delete-shortcode" data-id="<?php echo $sc->id; ?>" style="color:#a00"><?php esc_html_e('Delete', 'umbra-bookshelf'); ?></a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
@@ -598,7 +591,7 @@ class BDM_Admin {
|
|||||||
<div id="bd-bookshelf-shortcode-modal" class="bd-bookshelf-modal" style="display:none">
|
<div id="bd-bookshelf-shortcode-modal" class="bd-bookshelf-modal" style="display:none">
|
||||||
<div class="bd-bookshelf-modal-content">
|
<div class="bd-bookshelf-modal-content">
|
||||||
<div class="bd-bookshelf-modal-header">
|
<div class="bd-bookshelf-modal-header">
|
||||||
<h2 id="bd-bookshelf-shortcode-modal-title"><?php esc_html_e('Add New Shortcode', 'bd-bookshelf'); ?></h2>
|
<h2 id="bd-bookshelf-shortcode-modal-title"><?php esc_html_e('Add New Shortcode', 'umbra-bookshelf'); ?></h2>
|
||||||
<button type="button" class="bd-bookshelf-modal-close">×</button>
|
<button type="button" class="bd-bookshelf-modal-close">×</button>
|
||||||
</div>
|
</div>
|
||||||
<form id="bd-bookshelf-shortcode-form">
|
<form id="bd-bookshelf-shortcode-form">
|
||||||
@@ -606,18 +599,18 @@ class BDM_Admin {
|
|||||||
<input type="hidden" name="shortcode_id" id="shortcode_id" value="0">
|
<input type="hidden" name="shortcode_id" id="shortcode_id" value="0">
|
||||||
<table class="form-table">
|
<table class="form-table">
|
||||||
<tr>
|
<tr>
|
||||||
<th><label for="shortcode_slug"><?php esc_html_e('Shortcode Slug', 'bd-bookshelf'); ?> <span class="required">*</span></label></th>
|
<th><label for="shortcode_slug"><?php esc_html_e('Shortcode Slug', 'umbra-bookshelf'); ?> <span class="required">*</span></label></th>
|
||||||
<td>
|
<td>
|
||||||
<input type="text" name="slug" id="shortcode_slug" class="regular-text" required pattern="[a-z0-9\-]+">
|
<input type="text" name="slug" id="shortcode_slug" class="regular-text" required pattern="[a-z0-9\-]+">
|
||||||
<p class="description"><?php esc_html_e('Only lowercase letters, numbers, and hyphens. Use as: [your-slug]', 'bd-bookshelf'); ?></p>
|
<p class="description"><?php esc_html_e('Only lowercase letters, numbers, and hyphens. Use as: [your-slug]', 'umbra-bookshelf'); ?></p>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th><label for="shortcode_target"><?php esc_html_e('Target URL', 'bd-bookshelf'); ?> <span class="required">*</span></label></th>
|
<th><label for="shortcode_target"><?php esc_html_e('Target URL', 'umbra-bookshelf'); ?> <span class="required">*</span></label></th>
|
||||||
<td><input type="url" name="target_url" id="shortcode_target" class="regular-text" required></td>
|
<td><input type="url" name="target_url" id="shortcode_target" class="regular-text" required></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th><label for="shortcode_redirect"><?php esc_html_e('Redirect Type', 'bd-bookshelf'); ?></label></th>
|
<th><label for="shortcode_redirect"><?php esc_html_e('Redirect Type', 'umbra-bookshelf'); ?></label></th>
|
||||||
<td>
|
<td>
|
||||||
<select name="redirect_type" id="shortcode_redirect">
|
<select name="redirect_type" id="shortcode_redirect">
|
||||||
<option value="301">301 - Permanent</option>
|
<option value="301">301 - Permanent</option>
|
||||||
@@ -627,12 +620,12 @@ class BDM_Admin {
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th><label for="shortcode_description"><?php esc_html_e('Description', 'bd-bookshelf'); ?></label></th>
|
<th><label for="shortcode_description"><?php esc_html_e('Description', 'umbra-bookshelf'); ?></label></th>
|
||||||
<td><input type="text" name="description" id="shortcode_description" class="regular-text"></td>
|
<td><input type="text" name="description" id="shortcode_description" class="regular-text"></td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
<p class="submit">
|
<p class="submit">
|
||||||
<button type="submit" class="button button-primary"><?php esc_html_e('Save Shortcode', 'bd-bookshelf'); ?></button>
|
<button type="submit" class="button button-primary"><?php esc_html_e('Save Shortcode', 'umbra-bookshelf'); ?></button>
|
||||||
</p>
|
</p>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@@ -645,42 +638,42 @@ class BDM_Admin {
|
|||||||
*/
|
*/
|
||||||
public function render_sync_page() {
|
public function render_sync_page() {
|
||||||
if (!current_user_can('manage_options')) {
|
if (!current_user_can('manage_options')) {
|
||||||
wp_die(__('You do not have sufficient permissions to access this page.', 'bd-bookshelf'));
|
wp_die(__('You do not have sufficient permissions to access this page.', 'umbra-bookshelf'));
|
||||||
}
|
}
|
||||||
|
|
||||||
$last_sync = BDM_Rating_Sync::get_last_sync_info();
|
$last_sync = Umbra_Rating_Sync::get_last_sync_info();
|
||||||
?>
|
?>
|
||||||
<div class="wrap">
|
<div class="wrap">
|
||||||
<h1><?php esc_html_e('Rating Sync', 'bd-bookshelf'); ?></h1>
|
<h1><?php esc_html_e('Rating Sync', 'umbra-bookshelf'); ?></h1>
|
||||||
|
|
||||||
<div class="bd-bookshelf-import-section">
|
<div class="bd-bookshelf-import-section">
|
||||||
<h2><?php esc_html_e('Sync Status', 'bd-bookshelf'); ?></h2>
|
<h2><?php esc_html_e('Sync Status', 'umbra-bookshelf'); ?></h2>
|
||||||
|
|
||||||
<?php if (!empty($last_sync)) : ?>
|
<?php if (!empty($last_sync)) : ?>
|
||||||
<table class="form-table">
|
<table class="form-table">
|
||||||
<tr>
|
<tr>
|
||||||
<th><?php esc_html_e('Last Sync Time', 'bd-bookshelf'); ?></th>
|
<th><?php esc_html_e('Last Sync Time', 'umbra-bookshelf'); ?></th>
|
||||||
<td><?php echo esc_html($last_sync['time']); ?></td>
|
<td><?php echo esc_html($last_sync['time']); ?></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th><?php esc_html_e('Books Synced', 'bd-bookshelf'); ?></th>
|
<th><?php esc_html_e('Books Synced', 'umbra-bookshelf'); ?></th>
|
||||||
<td><?php echo esc_html($last_sync['synced']); ?></td>
|
<td><?php echo esc_html($last_sync['synced']); ?></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th><?php esc_html_e('Books Failed', 'bd-bookshelf'); ?></th>
|
<th><?php esc_html_e('Books Failed', 'umbra-bookshelf'); ?></th>
|
||||||
<td><?php echo esc_html($last_sync['failed']); ?></td>
|
<td><?php echo esc_html($last_sync['failed']); ?></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th><?php esc_html_e('Total Books', 'bd-bookshelf'); ?></th>
|
<th><?php esc_html_e('Total Books', 'umbra-bookshelf'); ?></th>
|
||||||
<td><?php echo esc_html($last_sync['total']); ?></td>
|
<td><?php echo esc_html($last_sync['total']); ?></td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
<?php else : ?>
|
<?php else : ?>
|
||||||
<p><?php esc_html_e('No sync has been performed yet.', 'bd-bookshelf'); ?></p>
|
<p><?php esc_html_e('No sync has been performed yet.', 'umbra-bookshelf'); ?></p>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<p class="submit">
|
<p class="submit">
|
||||||
<button type="button" id="bd-bookshelf-manual-sync" class="button button-primary"><?php esc_html_e('Sync Now', 'bd-bookshelf'); ?></button>
|
<button type="button" id="bd-bookshelf-manual-sync" class="button button-primary"><?php esc_html_e('Sync Now', 'umbra-bookshelf'); ?></button>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -692,28 +685,28 @@ class BDM_Admin {
|
|||||||
*/
|
*/
|
||||||
public function render_settings_page() {
|
public function render_settings_page() {
|
||||||
if (!current_user_can('manage_options')) {
|
if (!current_user_can('manage_options')) {
|
||||||
wp_die(__('You do not have sufficient permissions to access this page.', 'bd-bookshelf'));
|
wp_die(__('You do not have sufficient permissions to access this page.', 'umbra-bookshelf'));
|
||||||
}
|
}
|
||||||
|
|
||||||
?>
|
?>
|
||||||
<div class="wrap">
|
<div class="wrap">
|
||||||
<h1><?php esc_html_e('Settings', 'bd-bookshelf'); ?></h1>
|
<h1><?php esc_html_e('Settings', 'umbra-bookshelf'); ?></h1>
|
||||||
|
|
||||||
<form id="bd-bookshelf-settings-form">
|
<form id="bd-bookshelf-settings-form">
|
||||||
<?php wp_nonce_field('bd_bookshelf_save_settings', 'nonce'); ?>
|
<?php wp_nonce_field('bd_bookshelf_save_settings', 'nonce'); ?>
|
||||||
|
|
||||||
<h2><?php esc_html_e('GDPR Settings', 'bd-bookshelf'); ?></h2>
|
<h2><?php esc_html_e('GDPR Settings', 'umbra-bookshelf'); ?></h2>
|
||||||
<table class="form-table">
|
<table class="form-table">
|
||||||
<tr>
|
<tr>
|
||||||
<th><label for="gdpr_text"><?php esc_html_e('Consent Banner Text', 'bd-bookshelf'); ?></label></th>
|
<th><label for="gdpr_text"><?php esc_html_e('Consent Banner Text', 'umbra-bookshelf'); ?></label></th>
|
||||||
<td>
|
<td>
|
||||||
<textarea name="gdpr_text" id="gdpr_text" rows="3" class="large-text"><?php echo esc_textarea(BDM_GDPR::get_consent_text()); ?></textarea>
|
<textarea name="gdpr_text" id="gdpr_text" rows="3" class="large-text"><?php echo esc_textarea(Umbra_GDPR::get_consent_text()); ?></textarea>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<p class="submit">
|
<p class="submit">
|
||||||
<button type="submit" class="button button-primary"><?php esc_html_e('Save Settings', 'bd-bookshelf'); ?></button>
|
<button type="submit" class="button button-primary"><?php esc_html_e('Save Settings', 'umbra-bookshelf'); ?></button>
|
||||||
</p>
|
</p>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@@ -725,23 +718,23 @@ class BDM_Admin {
|
|||||||
*/
|
*/
|
||||||
public function render_import_page() {
|
public function render_import_page() {
|
||||||
if (!current_user_can('manage_options')) {
|
if (!current_user_can('manage_options')) {
|
||||||
wp_die(__('You do not have sufficient permissions to access this page.', 'bd-bookshelf'));
|
wp_die(__('You do not have sufficient permissions to access this page.', 'umbra-bookshelf'));
|
||||||
}
|
}
|
||||||
|
|
||||||
?>
|
?>
|
||||||
<div class="wrap">
|
<div class="wrap">
|
||||||
<h1><?php esc_html_e('Import Books', 'bd-bookshelf'); ?></h1>
|
<h1><?php esc_html_e('Import Books', 'umbra-bookshelf'); ?></h1>
|
||||||
|
|
||||||
<div class="bd-bookshelf-import-section">
|
<div class="bd-bookshelf-import-section">
|
||||||
<h2><?php esc_html_e('Import from CSV', 'bd-bookshelf'); ?></h2>
|
<h2><?php esc_html_e('Import from CSV', 'umbra-bookshelf'); ?></h2>
|
||||||
<p><?php esc_html_e('Import books from a CSV file. Required column: title. Optional columns: description, isbn_ebook, isbn_paperback, isbn_hardcover, isbn_audio, amazon_asin, goodreads_id.', 'bd-bookshelf'); ?></p>
|
<p><?php esc_html_e('Import books from a CSV file. Required column: title. Optional columns: description, isbn_ebook, isbn_paperback, isbn_hardcover, isbn_audio, amazon_asin, goodreads_id.', 'umbra-bookshelf'); ?></p>
|
||||||
|
|
||||||
<form id="bd-bookshelf-import-csv-form" enctype="multipart/form-data">
|
<form id="bd-bookshelf-import-csv-form" enctype="multipart/form-data">
|
||||||
<?php wp_nonce_field('bd_bookshelf_admin_nonce', 'import_nonce'); ?>
|
<?php wp_nonce_field('bd_bookshelf_admin_nonce', 'import_nonce'); ?>
|
||||||
|
|
||||||
<table class="form-table">
|
<table class="form-table">
|
||||||
<tr>
|
<tr>
|
||||||
<th><label for="csv_file"><?php esc_html_e('CSV File', 'bd-bookshelf'); ?></label></th>
|
<th><label for="csv_file"><?php esc_html_e('CSV File', 'umbra-bookshelf'); ?></label></th>
|
||||||
<td>
|
<td>
|
||||||
<input type="file" name="csv_file" id="csv_file" accept=".csv" required>
|
<input type="file" name="csv_file" id="csv_file" accept=".csv" required>
|
||||||
</td>
|
</td>
|
||||||
@@ -749,36 +742,36 @@ class BDM_Admin {
|
|||||||
</table>
|
</table>
|
||||||
|
|
||||||
<p class="submit">
|
<p class="submit">
|
||||||
<button type="submit" class="button button-primary"><?php esc_html_e('Import CSV', 'bd-bookshelf'); ?></button>
|
<button type="submit" class="button button-primary"><?php esc_html_e('Import CSV', 'umbra-bookshelf'); ?></button>
|
||||||
</p>
|
</p>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<div id="bd-bookshelf-import-results" style="display:none">
|
<div id="bd-bookshelf-import-results" style="display:none">
|
||||||
<h3><?php esc_html_e('Import Results', 'bd-bookshelf'); ?></h3>
|
<h3><?php esc_html_e('Import Results', 'umbra-bookshelf'); ?></h3>
|
||||||
<p id="bd-bookshelf-import-message"></p>
|
<p id="bd-bookshelf-import-message"></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="bd-bookshelf-import-section">
|
<div class="bd-bookshelf-import-section">
|
||||||
<h2><?php esc_html_e('Import from WordPress Posts', 'bd-bookshelf'); ?></h2>
|
<h2><?php esc_html_e('Import from WordPress Posts', 'umbra-bookshelf'); ?></h2>
|
||||||
<p><?php esc_html_e('Import books from existing WordPress posts or custom post types.', 'bd-bookshelf'); ?></p>
|
<p><?php esc_html_e('Import books from existing WordPress posts or custom post types.', 'umbra-bookshelf'); ?></p>
|
||||||
|
|
||||||
<form id="bd-bookshelf-import-posts-form">
|
<form id="bd-bookshelf-import-posts-form">
|
||||||
<?php wp_nonce_field('bd_bookshelf_admin_nonce', 'import_posts_nonce'); ?>
|
<?php wp_nonce_field('bd_bookshelf_admin_nonce', 'import_posts_nonce'); ?>
|
||||||
|
|
||||||
<table class="form-table">
|
<table class="form-table">
|
||||||
<tr>
|
<tr>
|
||||||
<th><label for="post_type"><?php esc_html_e('Post Type', 'bd-bookshelf'); ?></label></th>
|
<th><label for="post_type"><?php esc_html_e('Post Type', 'umbra-bookshelf'); ?></label></th>
|
||||||
<td>
|
<td>
|
||||||
<select name="post_type" id="post_type">
|
<select name="post_type" id="post_type">
|
||||||
<option value="post"><?php esc_html_e('Posts', 'bd-bookshelf'); ?></option>
|
<option value="post"><?php esc_html_e('Posts', 'umbra-bookshelf'); ?></option>
|
||||||
<option value="page"><?php esc_html_e('Pages', 'bd-bookshelf'); ?></option>
|
<option value="page"><?php esc_html_e('Pages', 'umbra-bookshelf'); ?></option>
|
||||||
<option value="book"><?php esc_html_e('Books (if available)', 'bd-bookshelf'); ?></option>
|
<option value="book"><?php esc_html_e('Books (if available)', 'umbra-bookshelf'); ?></option>
|
||||||
</select>
|
</select>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th><label for="limit"><?php esc_html_e('Limit', 'bd-bookshelf'); ?></label></th>
|
<th><label for="limit"><?php esc_html_e('Limit', 'umbra-bookshelf'); ?></label></th>
|
||||||
<td>
|
<td>
|
||||||
<input type="number" name="limit" id="limit" value="100" min="1" max="1000">
|
<input type="number" name="limit" id="limit" value="100" min="1" max="1000">
|
||||||
</td>
|
</td>
|
||||||
@@ -786,7 +779,7 @@ class BDM_Admin {
|
|||||||
</table>
|
</table>
|
||||||
|
|
||||||
<p class="submit">
|
<p class="submit">
|
||||||
<button type="submit" class="button button-primary"><?php esc_html_e('Import Posts', 'bd-bookshelf'); ?></button>
|
<button type="submit" class="button button-primary"><?php esc_html_e('Import Posts', 'umbra-bookshelf'); ?></button>
|
||||||
</p>
|
</p>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@@ -825,10 +818,10 @@ class BDM_Admin {
|
|||||||
];
|
];
|
||||||
|
|
||||||
if ($book_id) {
|
if ($book_id) {
|
||||||
$result = BDM_Book_Manager::update_book($book_id, $data);
|
$result = Umbra_Book_Manager::update_book($book_id, $data);
|
||||||
} else {
|
} else {
|
||||||
$data['slug'] = sanitize_title($data['title']);
|
$data['slug'] = sanitize_title($data['title']);
|
||||||
$book_id = BDM_Book_Manager::create_book($data);
|
$book_id = Umbra_Book_Manager::create_book($data);
|
||||||
$result = $book_id !== false;
|
$result = $book_id !== false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -849,8 +842,8 @@ class BDM_Admin {
|
|||||||
private function save_retail_urls($book_id, $retail_urls) {
|
private function save_retail_urls($book_id, $retail_urls) {
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
|
|
||||||
$links_table = BDM_Database::table('retail_links');
|
$links_table = Umbra_Database::table('retail_links');
|
||||||
$sites_table = BDM_Database::table('retail_sites');
|
$sites_table = Umbra_Database::table('retail_sites');
|
||||||
|
|
||||||
// Delete existing non-custom links
|
// Delete existing non-custom links
|
||||||
$wpdb->delete($links_table, ['book_id' => $book_id, 'is_custom' => 0], ['%d', '%d']);
|
$wpdb->delete($links_table, ['book_id' => $book_id, 'is_custom' => 0], ['%d', '%d']);
|
||||||
@@ -910,7 +903,7 @@ class BDM_Admin {
|
|||||||
wp_send_json_error(['message' => 'Invalid book ID']);
|
wp_send_json_error(['message' => 'Invalid book ID']);
|
||||||
}
|
}
|
||||||
|
|
||||||
$result = BDM_Book_Manager::delete_book($book_id);
|
$result = Umbra_Book_Manager::delete_book($book_id);
|
||||||
|
|
||||||
if ($result) {
|
if ($result) {
|
||||||
wp_send_json_success(['message' => 'Book deleted']);
|
wp_send_json_success(['message' => 'Book deleted']);
|
||||||
@@ -934,7 +927,7 @@ class BDM_Admin {
|
|||||||
wp_send_json_error(['message' => 'Invalid author ID']);
|
wp_send_json_error(['message' => 'Invalid author ID']);
|
||||||
}
|
}
|
||||||
|
|
||||||
$author = BDM_Authors::get_author($author_id);
|
$author = Umbra_Authors::get_author($author_id);
|
||||||
if (!$author) {
|
if (!$author) {
|
||||||
wp_send_json_error(['message' => 'Author not found']);
|
wp_send_json_error(['message' => 'Author not found']);
|
||||||
}
|
}
|
||||||
@@ -964,9 +957,9 @@ class BDM_Admin {
|
|||||||
];
|
];
|
||||||
|
|
||||||
if ($author_id) {
|
if ($author_id) {
|
||||||
$result = BDM_Authors::update_author($author_id, $data);
|
$result = Umbra_Authors::update_author($author_id, $data);
|
||||||
} else {
|
} else {
|
||||||
$author_id = BDM_Authors::create_author($data);
|
$author_id = Umbra_Authors::create_author($data);
|
||||||
$result = $author_id !== false;
|
$result = $author_id !== false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -992,7 +985,7 @@ class BDM_Admin {
|
|||||||
wp_send_json_error(['message' => 'Invalid author ID']);
|
wp_send_json_error(['message' => 'Invalid author ID']);
|
||||||
}
|
}
|
||||||
|
|
||||||
$result = BDM_Authors::delete_author($author_id);
|
$result = Umbra_Authors::delete_author($author_id);
|
||||||
|
|
||||||
if ($result) {
|
if ($result) {
|
||||||
wp_send_json_success(['message' => 'Author deleted']);
|
wp_send_json_success(['message' => 'Author deleted']);
|
||||||
@@ -1016,7 +1009,7 @@ class BDM_Admin {
|
|||||||
wp_send_json_error(['message' => 'Invalid series ID']);
|
wp_send_json_error(['message' => 'Invalid series ID']);
|
||||||
}
|
}
|
||||||
|
|
||||||
$series = BDM_Series::get_series_by_id($series_id);
|
$series = Umbra_Series::get_series_by_id($series_id);
|
||||||
if (!$series) {
|
if (!$series) {
|
||||||
wp_send_json_error(['message' => 'Series not found']);
|
wp_send_json_error(['message' => 'Series not found']);
|
||||||
}
|
}
|
||||||
@@ -1042,9 +1035,9 @@ class BDM_Admin {
|
|||||||
];
|
];
|
||||||
|
|
||||||
if ($series_id) {
|
if ($series_id) {
|
||||||
$result = BDM_Series::update_series($series_id, $data);
|
$result = Umbra_Series::update_series($series_id, $data);
|
||||||
} else {
|
} else {
|
||||||
$series_id = BDM_Series::create_series($data);
|
$series_id = Umbra_Series::create_series($data);
|
||||||
$result = $series_id !== false;
|
$result = $series_id !== false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1070,7 +1063,7 @@ class BDM_Admin {
|
|||||||
wp_send_json_error(['message' => 'Invalid series ID']);
|
wp_send_json_error(['message' => 'Invalid series ID']);
|
||||||
}
|
}
|
||||||
|
|
||||||
$result = BDM_Series::delete_series($series_id);
|
$result = Umbra_Series::delete_series($series_id);
|
||||||
|
|
||||||
if ($result) {
|
if ($result) {
|
||||||
wp_send_json_success(['message' => 'Series deleted']);
|
wp_send_json_success(['message' => 'Series deleted']);
|
||||||
@@ -1095,7 +1088,7 @@ class BDM_Admin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
$table = BDM_Database::table('shortcodes');
|
$table = Umbra_Database::table('shortcodes');
|
||||||
$shortcode = $wpdb->get_row(
|
$shortcode = $wpdb->get_row(
|
||||||
$wpdb->prepare(
|
$wpdb->prepare(
|
||||||
"SELECT * FROM {$table} WHERE id = %d",
|
"SELECT * FROM {$table} WHERE id = %d",
|
||||||
@@ -1134,9 +1127,9 @@ class BDM_Admin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($shortcode_id) {
|
if ($shortcode_id) {
|
||||||
$result = BDM_Shortcodes::update_shortcode($shortcode_id, $data);
|
$result = Umbra_Shortcodes::update_shortcode($shortcode_id, $data);
|
||||||
} else {
|
} else {
|
||||||
$shortcode_id = BDM_Shortcodes::create_shortcode($data['slug'], $data['target_url'], $data['redirect_type'], $data['description']);
|
$shortcode_id = Umbra_Shortcodes::create_shortcode($data['slug'], $data['target_url'], $data['redirect_type'], $data['description']);
|
||||||
$result = $shortcode_id !== false;
|
$result = $shortcode_id !== false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1162,7 +1155,7 @@ class BDM_Admin {
|
|||||||
wp_send_json_error(['message' => 'Invalid shortcode ID']);
|
wp_send_json_error(['message' => 'Invalid shortcode ID']);
|
||||||
}
|
}
|
||||||
|
|
||||||
$result = BDM_Shortcodes::delete_shortcode($shortcode_id);
|
$result = Umbra_Shortcodes::delete_shortcode($shortcode_id);
|
||||||
|
|
||||||
if ($result) {
|
if ($result) {
|
||||||
wp_send_json_success(['message' => 'Shortcode deleted']);
|
wp_send_json_success(['message' => 'Shortcode deleted']);
|
||||||
@@ -1183,7 +1176,7 @@ class BDM_Admin {
|
|||||||
|
|
||||||
if (!empty($_POST['affiliate'])) {
|
if (!empty($_POST['affiliate'])) {
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
$table = BDM_Database::table('retail_sites');
|
$table = Umbra_Database::table('retail_sites');
|
||||||
|
|
||||||
foreach ($_POST['affiliate'] as $site_id => $code) {
|
foreach ($_POST['affiliate'] as $site_id => $code) {
|
||||||
$wpdb->update(
|
$wpdb->update(
|
||||||
@@ -1209,7 +1202,7 @@ class BDM_Admin {
|
|||||||
wp_send_json_error(['message' => 'Insufficient permissions']);
|
wp_send_json_error(['message' => 'Insufficient permissions']);
|
||||||
}
|
}
|
||||||
|
|
||||||
BDM_GDPR::update_consent_text($_POST['gdpr_text'] ?? '');
|
Umbra_GDPR::update_consent_text($_POST['gdpr_text'] ?? '');
|
||||||
|
|
||||||
wp_send_json_success(['message' => 'Settings saved']);
|
wp_send_json_success(['message' => 'Settings saved']);
|
||||||
}
|
}
|
||||||
@@ -1252,7 +1245,7 @@ class BDM_Admin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if already imported - update instead of skip
|
// Check if already imported - update instead of skip
|
||||||
$existing = BDM_Book_Manager::get_book_by_title($data['title']);
|
$existing = Umbra_Book_Manager::get_book_by_title($data['title']);
|
||||||
|
|
||||||
$book_data = [
|
$book_data = [
|
||||||
'title' => $data['title'],
|
'title' => $data['title'],
|
||||||
@@ -1276,14 +1269,14 @@ class BDM_Admin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!empty($update_data)) {
|
if (!empty($update_data)) {
|
||||||
BDM_Book_Manager::update_book($existing->id, $update_data);
|
Umbra_Book_Manager::update_book($existing->id, $update_data);
|
||||||
$updated++;
|
$updated++;
|
||||||
} else {
|
} else {
|
||||||
$skipped++;
|
$skipped++;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Create new book
|
// Create new book
|
||||||
$book_id = BDM_Book_Manager::create_book($book_data);
|
$book_id = Umbra_Book_Manager::create_book($book_data);
|
||||||
if ($book_id) {
|
if ($book_id) {
|
||||||
$imported++;
|
$imported++;
|
||||||
} else {
|
} else {
|
||||||
@@ -1332,14 +1325,14 @@ class BDM_Admin {
|
|||||||
|
|
||||||
foreach ($posts as $post) {
|
foreach ($posts as $post) {
|
||||||
// Check if already imported
|
// Check if already imported
|
||||||
$existing = BDM_Book_Manager::get_book_by_title($post->post_title);
|
$existing = Umbra_Book_Manager::get_book_by_title($post->post_title);
|
||||||
if ($existing) {
|
if ($existing) {
|
||||||
$skipped++;
|
$skipped++;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Import book
|
// Import book
|
||||||
$book_id = BDM_Book_Manager::create_book([
|
$book_id = Umbra_Book_Manager::create_book([
|
||||||
'title' => $post->post_title,
|
'title' => $post->post_title,
|
||||||
'description' => $post->post_content,
|
'description' => $post->post_content,
|
||||||
]);
|
]);
|
||||||
@@ -1368,7 +1361,7 @@ class BDM_Admin {
|
|||||||
wp_send_json_error(['message' => 'Insufficient permissions']);
|
wp_send_json_error(['message' => 'Insufficient permissions']);
|
||||||
}
|
}
|
||||||
|
|
||||||
$result = BDM_Rating_Sync::manual_sync();
|
$result = Umbra_Rating_Sync::manual_sync();
|
||||||
|
|
||||||
wp_send_json_success([
|
wp_send_json_success([
|
||||||
'message' => sprintf('Synced %d books, failed %d', $result['synced'], $result['failed']),
|
'message' => sprintf('Synced %d books, failed %d', $result['synced'], $result['failed']),
|
||||||
@@ -7,14 +7,14 @@
|
|||||||
*
|
*
|
||||||
* Fetches author and book data from Amazon
|
* Fetches author and book data from Amazon
|
||||||
*
|
*
|
||||||
* @package BD_Bookshelf
|
* @package Umbra_Bookshelf
|
||||||
*/
|
*/
|
||||||
|
|
||||||
if (!defined('ABSPATH')) {
|
if (!defined('ABSPATH')) {
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
class BDM_Amazon_Scraper {
|
class Umbra_Amazon_Scraper {
|
||||||
|
|
||||||
private $user_agent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36';
|
private $user_agent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36';
|
||||||
|
|
||||||
@@ -5,14 +5,14 @@
|
|||||||
/**
|
/**
|
||||||
* Authors Management class
|
* Authors Management class
|
||||||
*
|
*
|
||||||
* @package BD_Bookshelf
|
* @package Umbra_Bookshelf
|
||||||
*/
|
*/
|
||||||
|
|
||||||
if (!defined('ABSPATH')) {
|
if (!defined('ABSPATH')) {
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
class BDM_Authors {
|
class Umbra_Authors {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all authors
|
* Get all authors
|
||||||
@@ -20,7 +20,7 @@ class BDM_Authors {
|
|||||||
public static function get_authors($args = []) {
|
public static function get_authors($args = []) {
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
|
|
||||||
$table = BDM_Database::table('authors');
|
$table = Umbra_Database::table('authors');
|
||||||
|
|
||||||
$defaults = [
|
$defaults = [
|
||||||
'number' => 20,
|
'number' => 20,
|
||||||
@@ -59,7 +59,7 @@ class BDM_Authors {
|
|||||||
public static function get_author($author_id) {
|
public static function get_author($author_id) {
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
|
|
||||||
$table = BDM_Database::table('authors');
|
$table = Umbra_Database::table('authors');
|
||||||
|
|
||||||
return $wpdb->get_row(
|
return $wpdb->get_row(
|
||||||
$wpdb->prepare(
|
$wpdb->prepare(
|
||||||
@@ -75,7 +75,7 @@ class BDM_Authors {
|
|||||||
public static function create_author($data) {
|
public static function create_author($data) {
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
|
|
||||||
$table = BDM_Database::table('authors');
|
$table = Umbra_Database::table('authors');
|
||||||
|
|
||||||
$result = $wpdb->insert($table, [
|
$result = $wpdb->insert($table, [
|
||||||
'display_name' => sanitize_text_field($data['display_name']),
|
'display_name' => sanitize_text_field($data['display_name']),
|
||||||
@@ -95,7 +95,7 @@ class BDM_Authors {
|
|||||||
public static function update_author($author_id, $data) {
|
public static function update_author($author_id, $data) {
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
|
|
||||||
$table = BDM_Database::table('authors');
|
$table = Umbra_Database::table('authors');
|
||||||
|
|
||||||
$update_data = [];
|
$update_data = [];
|
||||||
|
|
||||||
@@ -145,10 +145,10 @@ class BDM_Authors {
|
|||||||
global $wpdb;
|
global $wpdb;
|
||||||
|
|
||||||
// Remove from book_authors
|
// Remove from book_authors
|
||||||
$wpdb->delete(BDM_Database::table('book_authors'), ['author_id' => $author_id], ['%d']);
|
$wpdb->delete(Umbra_Database::table('book_authors'), ['author_id' => $author_id], ['%d']);
|
||||||
|
|
||||||
// Delete author
|
// Delete author
|
||||||
$table = BDM_Database::table('authors');
|
$table = Umbra_Database::table('authors');
|
||||||
return $wpdb->delete($table, ['id' => $author_id], ['%d']) !== false;
|
return $wpdb->delete($table, ['id' => $author_id], ['%d']) !== false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,7 +158,7 @@ class BDM_Authors {
|
|||||||
public static function count_authors($search = '') {
|
public static function count_authors($search = '') {
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
|
|
||||||
$table = BDM_Database::table('authors');
|
$table = Umbra_Database::table('authors');
|
||||||
|
|
||||||
$where = '1=1';
|
$where = '1=1';
|
||||||
$params = [];
|
$params = [];
|
||||||
@@ -5,14 +5,14 @@
|
|||||||
/**
|
/**
|
||||||
* Book Manager class
|
* Book Manager class
|
||||||
*
|
*
|
||||||
* @package BD_Bookshelf
|
* @package Umbra_Bookshelf
|
||||||
*/
|
*/
|
||||||
|
|
||||||
if (!defined('ABSPATH')) {
|
if (!defined('ABSPATH')) {
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
class BDM_Book_Manager {
|
class Umbra_Book_Manager {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all books
|
* Get all books
|
||||||
@@ -20,7 +20,7 @@ class BDM_Book_Manager {
|
|||||||
public static function get_books($args = []) {
|
public static function get_books($args = []) {
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
|
|
||||||
$table = BDM_Database::table('books');
|
$table = Umbra_Database::table('books');
|
||||||
|
|
||||||
$defaults = [
|
$defaults = [
|
||||||
'number' => 20,
|
'number' => 20,
|
||||||
@@ -63,7 +63,7 @@ class BDM_Book_Manager {
|
|||||||
public static function get_book($book_id) {
|
public static function get_book($book_id) {
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
|
|
||||||
$table = BDM_Database::table('books');
|
$table = Umbra_Database::table('books');
|
||||||
|
|
||||||
$book = $wpdb->get_row(
|
$book = $wpdb->get_row(
|
||||||
$wpdb->prepare(
|
$wpdb->prepare(
|
||||||
@@ -75,7 +75,7 @@ class BDM_Book_Manager {
|
|||||||
if ($book) {
|
if ($book) {
|
||||||
$book->authors = self::get_book_authors($book_id);
|
$book->authors = self::get_book_authors($book_id);
|
||||||
$book->retail_links = self::get_book_retail_links($book_id);
|
$book->retail_links = self::get_book_retail_links($book_id);
|
||||||
$book->seo = BDM_SEO::get_book_seo($book_id);
|
$book->seo = Umbra_SEO::get_book_seo($book_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $book;
|
return $book;
|
||||||
@@ -87,7 +87,7 @@ class BDM_Book_Manager {
|
|||||||
public static function get_book_by_title($title) {
|
public static function get_book_by_title($title) {
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
|
|
||||||
$table = BDM_Database::table('books');
|
$table = Umbra_Database::table('books');
|
||||||
|
|
||||||
$book = $wpdb->get_row(
|
$book = $wpdb->get_row(
|
||||||
$wpdb->prepare(
|
$wpdb->prepare(
|
||||||
@@ -105,7 +105,7 @@ class BDM_Book_Manager {
|
|||||||
public static function get_book_by_slug($slug) {
|
public static function get_book_by_slug($slug) {
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
|
|
||||||
$table = BDM_Database::table('books');
|
$table = Umbra_Database::table('books');
|
||||||
|
|
||||||
$book = $wpdb->get_row(
|
$book = $wpdb->get_row(
|
||||||
$wpdb->prepare(
|
$wpdb->prepare(
|
||||||
@@ -117,7 +117,7 @@ class BDM_Book_Manager {
|
|||||||
if ($book) {
|
if ($book) {
|
||||||
$book->authors = self::get_book_authors($book->id);
|
$book->authors = self::get_book_authors($book->id);
|
||||||
$book->retail_links = self::get_book_retail_links($book->id);
|
$book->retail_links = self::get_book_retail_links($book->id);
|
||||||
$book->seo = BDM_SEO::get_book_seo($book->id);
|
$book->seo = Umbra_SEO::get_book_seo($book->id);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $book;
|
return $book;
|
||||||
@@ -129,7 +129,7 @@ class BDM_Book_Manager {
|
|||||||
public static function create_book($data) {
|
public static function create_book($data) {
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
|
|
||||||
$table = BDM_Database::table('books');
|
$table = Umbra_Database::table('books');
|
||||||
|
|
||||||
$slug = sanitize_title($data['slug'] ?? $data['title'] ?? '');
|
$slug = sanitize_title($data['slug'] ?? $data['title'] ?? '');
|
||||||
|
|
||||||
@@ -184,7 +184,7 @@ class BDM_Book_Manager {
|
|||||||
public static function update_book($book_id, $data) {
|
public static function update_book($book_id, $data) {
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
|
|
||||||
$table = BDM_Database::table('books');
|
$table = Umbra_Database::table('books');
|
||||||
|
|
||||||
$update_data = [];
|
$update_data = [];
|
||||||
|
|
||||||
@@ -274,12 +274,12 @@ class BDM_Book_Manager {
|
|||||||
global $wpdb;
|
global $wpdb;
|
||||||
|
|
||||||
// Delete related data
|
// Delete related data
|
||||||
$wpdb->delete(BDM_Database::table('book_authors'), ['book_id' => $book_id], ['%d']);
|
$wpdb->delete(Umbra_Database::table('book_authors'), ['book_id' => $book_id], ['%d']);
|
||||||
$wpdb->delete(BDM_Database::table('retail_links'), ['book_id' => $book_id], ['%d']);
|
$wpdb->delete(Umbra_Database::table('retail_links'), ['book_id' => $book_id], ['%d']);
|
||||||
$wpdb->delete(BDM_Database::table('book_seo'), ['book_id' => $book_id], ['%d']);
|
$wpdb->delete(Umbra_Database::table('book_seo'), ['book_id' => $book_id], ['%d']);
|
||||||
|
|
||||||
// Delete book
|
// Delete book
|
||||||
$table = BDM_Database::table('books');
|
$table = Umbra_Database::table('books');
|
||||||
$result = $wpdb->delete($table, ['id' => $book_id], ['%d']);
|
$result = $wpdb->delete($table, ['id' => $book_id], ['%d']);
|
||||||
|
|
||||||
return $result !== false;
|
return $result !== false;
|
||||||
@@ -291,7 +291,7 @@ class BDM_Book_Manager {
|
|||||||
public static function get_book_authors($book_id) {
|
public static function get_book_authors($book_id) {
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
|
|
||||||
$table = BDM_Database::table('book_authors');
|
$table = Umbra_Database::table('book_authors');
|
||||||
|
|
||||||
$authors = $wpdb->get_results(
|
$authors = $wpdb->get_results(
|
||||||
$wpdb->prepare(
|
$wpdb->prepare(
|
||||||
@@ -309,8 +309,8 @@ class BDM_Book_Manager {
|
|||||||
public static function update_book_authors_by_id($book_id, $author_ids) {
|
public static function update_book_authors_by_id($book_id, $author_ids) {
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
|
|
||||||
$table = BDM_Database::table('book_authors');
|
$table = Umbra_Database::table('book_authors');
|
||||||
$authors_table = BDM_Database::table('authors');
|
$authors_table = Umbra_Database::table('authors');
|
||||||
|
|
||||||
// Delete existing authors
|
// Delete existing authors
|
||||||
$wpdb->delete($table, ['book_id' => $book_id], ['%d']);
|
$wpdb->delete($table, ['book_id' => $book_id], ['%d']);
|
||||||
@@ -349,7 +349,7 @@ class BDM_Book_Manager {
|
|||||||
public static function update_book_authors($book_id, $authors) {
|
public static function update_book_authors($book_id, $authors) {
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
|
|
||||||
$table = BDM_Database::table('book_authors');
|
$table = Umbra_Database::table('book_authors');
|
||||||
|
|
||||||
// Delete existing authors
|
// Delete existing authors
|
||||||
$wpdb->delete($table, ['book_id' => $book_id], ['%d']);
|
$wpdb->delete($table, ['book_id' => $book_id], ['%d']);
|
||||||
@@ -373,8 +373,8 @@ class BDM_Book_Manager {
|
|||||||
public static function get_book_retail_links($book_id, $format = '') {
|
public static function get_book_retail_links($book_id, $format = '') {
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
|
|
||||||
$table = BDM_Database::table('retail_links');
|
$table = Umbra_Database::table('retail_links');
|
||||||
$sites_table = BDM_Database::table('retail_sites');
|
$sites_table = Umbra_Database::table('retail_sites');
|
||||||
|
|
||||||
$where = 'rl.book_id = %d AND rs.is_active = 1';
|
$where = 'rl.book_id = %d AND rs.is_active = 1';
|
||||||
$params = [$book_id];
|
$params = [$book_id];
|
||||||
@@ -404,7 +404,7 @@ class BDM_Book_Manager {
|
|||||||
public static function get_books_by_series($series_id) {
|
public static function get_books_by_series($series_id) {
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
|
|
||||||
$table = BDM_Database::table('books');
|
$table = Umbra_Database::table('books');
|
||||||
|
|
||||||
$books = $wpdb->get_results(
|
$books = $wpdb->get_results(
|
||||||
$wpdb->prepare(
|
$wpdb->prepare(
|
||||||
@@ -429,7 +429,7 @@ class BDM_Book_Manager {
|
|||||||
public static function get_series_list() {
|
public static function get_series_list() {
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
|
|
||||||
$table = BDM_Database::table('series');
|
$table = Umbra_Database::table('series');
|
||||||
|
|
||||||
return $wpdb->get_results("SELECT * FROM {$table} ORDER BY name ASC");
|
return $wpdb->get_results("SELECT * FROM {$table} ORDER BY name ASC");
|
||||||
}
|
}
|
||||||
@@ -440,7 +440,7 @@ class BDM_Book_Manager {
|
|||||||
public static function count_books($search = '') {
|
public static function count_books($search = '') {
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
|
|
||||||
$table = BDM_Database::table('books');
|
$table = Umbra_Database::table('books');
|
||||||
|
|
||||||
$where = '1=1';
|
$where = '1=1';
|
||||||
$params = [];
|
$params = [];
|
||||||
@@ -5,14 +5,14 @@
|
|||||||
/**
|
/**
|
||||||
* Database management class
|
* Database management class
|
||||||
*
|
*
|
||||||
* @package BD_Bookshelf
|
* @package Umbra_Bookshelf
|
||||||
*/
|
*/
|
||||||
|
|
||||||
if (!defined('ABSPATH')) {
|
if (!defined('ABSPATH')) {
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
class BDM_Database {
|
class Umbra_Database {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get table names with prefix
|
* Get table names with prefix
|
||||||
@@ -187,7 +187,7 @@ class BDM_Database {
|
|||||||
$this->insert_default_sites();
|
$this->insert_default_sites();
|
||||||
|
|
||||||
// Store schema version
|
// Store schema version
|
||||||
update_option('bd_bookshelf_db_version', BD_BOOKSHELF_VERSION);
|
update_option('bd_bookshelf_db_version', UMBRA_BOOKSHELF_VERSION);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -5,14 +5,14 @@
|
|||||||
/**
|
/**
|
||||||
* GDPR Compliance class
|
* GDPR Compliance class
|
||||||
*
|
*
|
||||||
* @package BD_Bookshelf
|
* @package Umbra_Bookshelf
|
||||||
*/
|
*/
|
||||||
|
|
||||||
if (!defined('ABSPATH')) {
|
if (!defined('ABSPATH')) {
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
class BDM_GDPR {
|
class Umbra_GDPR {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
@@ -35,14 +35,14 @@ class BDM_GDPR {
|
|||||||
}
|
}
|
||||||
|
|
||||||
wp_enqueue_script(
|
wp_enqueue_script(
|
||||||
'bd-bookshelf-gdpr',
|
'umbra-bookshelf-gdpr',
|
||||||
BD_BOOKSHELF_URL . 'public/js/bd-bookshelf-gdpr.js',
|
UMBRA_BOOKSHELF_URL . 'public/js/umbra-bookshelf-gdpr.js',
|
||||||
[],
|
[],
|
||||||
BD_BOOKSHELF_VERSION,
|
UMBRA_BOOKSHELF_VERSION,
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
|
|
||||||
wp_localize_script('bd-bookshelf-gdpr', 'bdBookshelfGdpr', [
|
wp_localize_script('umbra-bookshelf-gdpr', 'umbraBookshelfGdpr', [
|
||||||
'ajaxurl' => admin_url('admin-ajax.php'),
|
'ajaxurl' => admin_url('admin-ajax.php'),
|
||||||
'nonce' => wp_create_nonce('bd_bookshelf_gdpr_nonce'),
|
'nonce' => wp_create_nonce('bd_bookshelf_gdpr_nonce'),
|
||||||
'cookieDays' => 30,
|
'cookieDays' => 30,
|
||||||
@@ -69,7 +69,7 @@ class BDM_GDPR {
|
|||||||
echo '<div id="bd-bookshelf-gdpr-banner" class="bd-bookshelf-gdpr-banner">';
|
echo '<div id="bd-bookshelf-gdpr-banner" class="bd-bookshelf-gdpr-banner">';
|
||||||
echo '<div class="bd-bookshelf-gdpr-content">';
|
echo '<div class="bd-bookshelf-gdpr-content">';
|
||||||
echo '<p>' . wp_kses_post($privacy_text) . '</p>';
|
echo '<p>' . wp_kses_post($privacy_text) . '</p>';
|
||||||
echo '<button id="bd-bookshelf-gdpr-accept" class="bd-bookshelf-gdpr-accept">' . esc_html__('Accept', 'bd-bookshelf') . '</button>';
|
echo '<button id="bd-bookshelf-gdpr-accept" class="bd-bookshelf-gdpr-accept">' . esc_html__('Accept', 'umbra-bookshelf') . '</button>';
|
||||||
echo '</div>';
|
echo '</div>';
|
||||||
echo '</div>';
|
echo '</div>';
|
||||||
}
|
}
|
||||||
@@ -7,14 +7,14 @@
|
|||||||
*
|
*
|
||||||
* Fetches author and book data from Goodreads
|
* Fetches author and book data from Goodreads
|
||||||
*
|
*
|
||||||
* @package BD_Bookshelf
|
* @package Umbra_Bookshelf
|
||||||
*/
|
*/
|
||||||
|
|
||||||
if (!defined('ABSPATH')) {
|
if (!defined('ABSPATH')) {
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
class BDM_Goodreads_Scraper {
|
class Umbra_Goodreads_Scraper {
|
||||||
|
|
||||||
private $user_agent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36';
|
private $user_agent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36';
|
||||||
|
|
||||||
@@ -5,39 +5,31 @@
|
|||||||
/**
|
/**
|
||||||
* Migration Manager class
|
* Migration Manager class
|
||||||
*
|
*
|
||||||
* @package BD_Bookshelf
|
* @package Umbra_Bookshelf
|
||||||
*/
|
*/
|
||||||
|
|
||||||
if (!defined('ABSPATH')) {
|
if (!defined('ABSPATH')) {
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
class BDM_Migration {
|
class Umbra_Migration {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
|
*
|
||||||
|
* The "Import" admin page itself is registered once by Umbra_Admin
|
||||||
|
* (slug: umbra-bookshelf-import), which is the variant wired to
|
||||||
|
* admin/js/umbra-bookshelf-admin.js. This class only supplies the
|
||||||
|
* two-phase preview/run AJAX endpoints that page has no equivalent for.
|
||||||
|
*
|
||||||
|
* Note: the bd_bookshelf_import_csv action is intentionally NOT
|
||||||
|
* registered here -- Umbra_Admin already owns that action. Registering
|
||||||
|
* it twice silently overwrote one of the two handlers.
|
||||||
*/
|
*/
|
||||||
public function __construct() {
|
public function __construct() {
|
||||||
add_action('admin_menu', [$this, 'add_admin_menu']);
|
|
||||||
|
|
||||||
// AJAX handlers
|
// AJAX handlers
|
||||||
add_action('wp_ajax_bd_bookshelf_preview_import', [$this, 'ajax_import_posts']);
|
add_action('wp_ajax_bd_bookshelf_preview_import', [$this, 'ajax_import_posts']);
|
||||||
add_action('wp_ajax_bd_bookshelf_run_import', [$this, 'ajax_run_import_posts']);
|
add_action('wp_ajax_bd_bookshelf_run_import', [$this, 'ajax_run_import_posts']);
|
||||||
add_action('wp_ajax_bd_bookshelf_import_csv', [$this, 'ajax_import_csv']);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add admin menu
|
|
||||||
*/
|
|
||||||
public function add_admin_menu() {
|
|
||||||
add_submenu_page(
|
|
||||||
'bd-bookshelf',
|
|
||||||
__('Import Books', 'bd-bookshelf'),
|
|
||||||
__('Import', 'bd-bookshelf'),
|
|
||||||
'manage_options',
|
|
||||||
'bd-bookshelf-import',
|
|
||||||
[$this, 'render_import_page']
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -45,33 +37,33 @@ class BDM_Migration {
|
|||||||
*/
|
*/
|
||||||
public function render_import_page() {
|
public function render_import_page() {
|
||||||
if (!current_user_can('manage_options')) {
|
if (!current_user_can('manage_options')) {
|
||||||
wp_die(__('You do not have sufficient permissions to access this page.', 'bd-bookshelf'));
|
wp_die(__('You do not have sufficient permissions to access this page.', 'umbra-bookshelf'));
|
||||||
}
|
}
|
||||||
|
|
||||||
?>
|
?>
|
||||||
<div class="wrap">
|
<div class="wrap">
|
||||||
<h1><?php esc_html_e('Import Books', 'bd-bookshelf'); ?></h1>
|
<h1><?php esc_html_e('Import Books', 'umbra-bookshelf'); ?></h1>
|
||||||
|
|
||||||
<div class="bd-bookshelf-import-section">
|
<div class="bd-bookshelf-import-section">
|
||||||
<h2><?php esc_html_e('Import from WordPress Posts', 'bd-bookshelf'); ?></h2>
|
<h2><?php esc_html_e('Import from WordPress Posts', 'umbra-bookshelf'); ?></h2>
|
||||||
<p><?php esc_html_e('Import books from existing WordPress posts or custom post types.', 'bd-bookshelf'); ?></p>
|
<p><?php esc_html_e('Import books from existing WordPress posts or custom post types.', 'umbra-bookshelf'); ?></p>
|
||||||
|
|
||||||
<form id="bd-bookshelf-import-posts-form">
|
<form id="bd-bookshelf-import-posts-form">
|
||||||
<?php wp_nonce_field('bd_bookshelf_import_posts', 'nonce'); ?>
|
<?php wp_nonce_field('bd_bookshelf_import_posts', 'nonce'); ?>
|
||||||
|
|
||||||
<table class="form-table">
|
<table class="form-table">
|
||||||
<tr>
|
<tr>
|
||||||
<th><label for="post_type"><?php esc_html_e('Post Type', 'bd-bookshelf'); ?></label></th>
|
<th><label for="post_type"><?php esc_html_e('Post Type', 'umbra-bookshelf'); ?></label></th>
|
||||||
<td>
|
<td>
|
||||||
<select name="post_type" id="post_type">
|
<select name="post_type" id="post_type">
|
||||||
<option value="post"><?php esc_html_e('Posts', 'bd-bookshelf'); ?></option>
|
<option value="post"><?php esc_html_e('Posts', 'umbra-bookshelf'); ?></option>
|
||||||
<option value="page"><?php esc_html_e('Pages', 'bd-bookshelf'); ?></option>
|
<option value="page"><?php esc_html_e('Pages', 'umbra-bookshelf'); ?></option>
|
||||||
<option value="book"><?php esc_html_e('Books (if available)', 'bd-bookshelf'); ?></option>
|
<option value="book"><?php esc_html_e('Books (if available)', 'umbra-bookshelf'); ?></option>
|
||||||
</select>
|
</select>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th><label for="limit"><?php esc_html_e('Limit', 'bd-bookshelf'); ?></label></th>
|
<th><label for="limit"><?php esc_html_e('Limit', 'umbra-bookshelf'); ?></label></th>
|
||||||
<td>
|
<td>
|
||||||
<input type="number" name="limit" id="limit" value="100" min="1" max="1000">
|
<input type="number" name="limit" id="limit" value="100" min="1" max="1000">
|
||||||
</td>
|
</td>
|
||||||
@@ -79,38 +71,38 @@ class BDM_Migration {
|
|||||||
</table>
|
</table>
|
||||||
|
|
||||||
<p class="submit">
|
<p class="submit">
|
||||||
<button type="submit" class="button button-primary"><?php esc_html_e('Preview Import', 'bd-bookshelf'); ?></button>
|
<button type="submit" class="button button-primary"><?php esc_html_e('Preview Import', 'umbra-bookshelf'); ?></button>
|
||||||
</p>
|
</p>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<div id="bd-bookshelf-import-preview" style="display: none;">
|
<div id="bd-bookshelf-import-preview" style="display: none;">
|
||||||
<h3><?php esc_html_e('Preview', 'bd-bookshelf'); ?></h3>
|
<h3><?php esc_html_e('Preview', 'umbra-bookshelf'); ?></h3>
|
||||||
<div id="bd-bookshelf-import-results"></div>
|
<div id="bd-bookshelf-import-results"></div>
|
||||||
<p class="submit">
|
<p class="submit">
|
||||||
<button type="button" id="bd-bookshelf-run-import" class="button button-primary"><?php esc_html_e('Run Import', 'bd-bookshelf'); ?></button>
|
<button type="button" id="bd-bookshelf-run-import" class="button button-primary"><?php esc_html_e('Run Import', 'umbra-bookshelf'); ?></button>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="bd-bookshelf-import-section">
|
<div class="bd-bookshelf-import-section">
|
||||||
<h2><?php esc_html_e('Import from CSV', 'bd-bookshelf'); ?></h2>
|
<h2><?php esc_html_e('Import from CSV', 'umbra-bookshelf'); ?></h2>
|
||||||
<p><?php esc_html_e('Import books from a CSV file.', 'bd-bookshelf'); ?></p>
|
<p><?php esc_html_e('Import books from a CSV file.', 'umbra-bookshelf'); ?></p>
|
||||||
|
|
||||||
<form id="bd-bookshelf-import-csv-form" enctype="multipart/form-data">
|
<form id="bd-bookshelf-import-csv-form" enctype="multipart/form-data">
|
||||||
<?php wp_nonce_field('bd_bookshelf_import_csv', 'nonce'); ?>
|
<?php wp_nonce_field('bd_bookshelf_import_csv', 'nonce'); ?>
|
||||||
|
|
||||||
<table class="form-table">
|
<table class="form-table">
|
||||||
<tr>
|
<tr>
|
||||||
<th><label for="csv_file"><?php esc_html_e('CSV File', 'bd-bookshelf'); ?></label></th>
|
<th><label for="csv_file"><?php esc_html_e('CSV File', 'umbra-bookshelf'); ?></label></th>
|
||||||
<td>
|
<td>
|
||||||
<input type="file" name="csv_file" id="csv_file" accept=".csv">
|
<input type="file" name="csv_file" id="csv_file" accept=".csv">
|
||||||
<p class="description"><?php esc_html_e('Required columns: title. Optional: description, isbn_ebook, isbn_paperback, isbn_hardcover, isbn_audio, amazon_asin, goodreads_id.', 'bd-bookshelf'); ?></p>
|
<p class="description"><?php esc_html_e('Required columns: title. Optional: description, isbn_ebook, isbn_paperback, isbn_hardcover, isbn_audio, amazon_asin, goodreads_id.', 'umbra-bookshelf'); ?></p>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<p class="submit">
|
<p class="submit">
|
||||||
<button type="submit" class="button button-primary"><?php esc_html_e('Import CSV', 'bd-bookshelf'); ?></button>
|
<button type="submit" class="button button-primary"><?php esc_html_e('Import CSV', 'umbra-bookshelf'); ?></button>
|
||||||
</p>
|
</p>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@@ -174,14 +166,14 @@ class BDM_Migration {
|
|||||||
|
|
||||||
foreach ($posts as $post) {
|
foreach ($posts as $post) {
|
||||||
// Check if already imported
|
// Check if already imported
|
||||||
$existing = BDM_Book_Manager::get_book_by_title($post->post_title);
|
$existing = Umbra_Book_Manager::get_book_by_title($post->post_title);
|
||||||
if ($existing) {
|
if ($existing) {
|
||||||
$skipped++;
|
$skipped++;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Import book
|
// Import book
|
||||||
$book_id = BDM_Book_Manager::create_book([
|
$book_id = Umbra_Book_Manager::create_book([
|
||||||
'title' => $post->post_title,
|
'title' => $post->post_title,
|
||||||
'description' => $post->post_content,
|
'description' => $post->post_content,
|
||||||
]);
|
]);
|
||||||
@@ -237,14 +229,14 @@ class BDM_Migration {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if already imported
|
// Check if already imported
|
||||||
$existing = BDM_Book_Manager::get_book_by_title($data['title']);
|
$existing = Umbra_Book_Manager::get_book_by_title($data['title']);
|
||||||
if ($existing) {
|
if ($existing) {
|
||||||
$skipped++;
|
$skipped++;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create book
|
// Create book
|
||||||
$book_id = BDM_Book_Manager::create_book([
|
$book_id = Umbra_Book_Manager::create_book([
|
||||||
'title' => $data['title'],
|
'title' => $data['title'],
|
||||||
'description' => $data['description'] ?? '',
|
'description' => $data['description'] ?? '',
|
||||||
'isbn_ebook' => $data['isbn_ebook'] ?? '',
|
'isbn_ebook' => $data['isbn_ebook'] ?? '',
|
||||||
@@ -5,14 +5,14 @@
|
|||||||
/**
|
/**
|
||||||
* Rating Sync Engine class
|
* Rating Sync Engine class
|
||||||
*
|
*
|
||||||
* @package BD_Bookshelf
|
* @package Umbra_Bookshelf
|
||||||
*/
|
*/
|
||||||
|
|
||||||
if (!defined('ABSPATH')) {
|
if (!defined('ABSPATH')) {
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
class BDM_Rating_Sync {
|
class Umbra_Rating_Sync {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
@@ -36,7 +36,7 @@ class BDM_Rating_Sync {
|
|||||||
public function add_cron_interval($schedules) {
|
public function add_cron_interval($schedules) {
|
||||||
$schedules['bd_bookshelf_twelve_hours'] = [
|
$schedules['bd_bookshelf_twelve_hours'] = [
|
||||||
'interval' => 43200, // 12 hours in seconds
|
'interval' => 43200, // 12 hours in seconds
|
||||||
'display' => esc_html__('Every 12 Hours', 'bd-bookshelf'),
|
'display' => esc_html__('Every 12 Hours', 'umbra-bookshelf'),
|
||||||
];
|
];
|
||||||
|
|
||||||
return $schedules;
|
return $schedules;
|
||||||
@@ -46,7 +46,7 @@ class BDM_Rating_Sync {
|
|||||||
* Sync all book ratings
|
* Sync all book ratings
|
||||||
*/
|
*/
|
||||||
public function sync_all_ratings() {
|
public function sync_all_ratings() {
|
||||||
$books = BDM_Book_Manager::get_books(['number' => 999]);
|
$books = Umbra_Book_Manager::get_books(['number' => 999]);
|
||||||
|
|
||||||
$synced = 0;
|
$synced = 0;
|
||||||
$failed = 0;
|
$failed = 0;
|
||||||
@@ -81,7 +81,7 @@ class BDM_Rating_Sync {
|
|||||||
public function sync_book_rating($book_id) {
|
public function sync_book_rating($book_id) {
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
|
|
||||||
$book = BDM_Book_Manager::get_book($book_id);
|
$book = Umbra_Book_Manager::get_book($book_id);
|
||||||
if (!$book) {
|
if (!$book) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -111,7 +111,7 @@ class BDM_Rating_Sync {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update book ratings
|
// Update book ratings
|
||||||
$table = BDM_Database::table('books');
|
$table = Umbra_Database::table('books');
|
||||||
$wpdb->update(
|
$wpdb->update(
|
||||||
$table,
|
$table,
|
||||||
[
|
[
|
||||||
@@ -5,14 +5,14 @@
|
|||||||
/**
|
/**
|
||||||
* Retail Sites management class
|
* Retail Sites management class
|
||||||
*
|
*
|
||||||
* @package BD_Bookshelf
|
* @package Umbra_Bookshelf
|
||||||
*/
|
*/
|
||||||
|
|
||||||
if (!defined('ABSPATH')) {
|
if (!defined('ABSPATH')) {
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
class BDM_Retail_Sites {
|
class Umbra_Retail_Sites {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all retail sites
|
* Get all retail sites
|
||||||
@@ -20,7 +20,7 @@ class BDM_Retail_Sites {
|
|||||||
public static function get_sites($active_only = true) {
|
public static function get_sites($active_only = true) {
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
|
|
||||||
$table = BDM_Database::table('retail_sites');
|
$table = Umbra_Database::table('retail_sites');
|
||||||
|
|
||||||
$where = $active_only ? 'is_active = 1' : '1=1';
|
$where = $active_only ? 'is_active = 1' : '1=1';
|
||||||
|
|
||||||
@@ -37,7 +37,7 @@ class BDM_Retail_Sites {
|
|||||||
public static function get_site($site_id) {
|
public static function get_site($site_id) {
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
|
|
||||||
$table = BDM_Database::table('retail_sites');
|
$table = Umbra_Database::table('retail_sites');
|
||||||
|
|
||||||
return $wpdb->get_row(
|
return $wpdb->get_row(
|
||||||
$wpdb->prepare(
|
$wpdb->prepare(
|
||||||
@@ -53,7 +53,7 @@ class BDM_Retail_Sites {
|
|||||||
public static function create_site($data) {
|
public static function create_site($data) {
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
|
|
||||||
$table = BDM_Database::table('retail_sites');
|
$table = Umbra_Database::table('retail_sites');
|
||||||
|
|
||||||
$result = $wpdb->insert($table, [
|
$result = $wpdb->insert($table, [
|
||||||
'name' => sanitize_text_field($data['name']),
|
'name' => sanitize_text_field($data['name']),
|
||||||
@@ -74,7 +74,7 @@ class BDM_Retail_Sites {
|
|||||||
public static function update_site($site_id, $data) {
|
public static function update_site($site_id, $data) {
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
|
|
||||||
$table = BDM_Database::table('retail_sites');
|
$table = Umbra_Database::table('retail_sites');
|
||||||
|
|
||||||
$update_data = [];
|
$update_data = [];
|
||||||
|
|
||||||
@@ -126,8 +126,8 @@ class BDM_Retail_Sites {
|
|||||||
// Only allow deleting custom sites
|
// Only allow deleting custom sites
|
||||||
$site = self::get_site($site_id);
|
$site = self::get_site($site_id);
|
||||||
if ($site && $site->is_custom) {
|
if ($site && $site->is_custom) {
|
||||||
$wpdb->delete(BDM_Database::table('retail_links'), ['site_id' => $site_id], ['%d']);
|
$wpdb->delete(Umbra_Database::table('retail_links'), ['site_id' => $site_id], ['%d']);
|
||||||
return $wpdb->delete(BDM_Database::table('retail_sites'), ['id' => $site_id], ['%d']) !== false;
|
return $wpdb->delete(Umbra_Database::table('retail_sites'), ['id' => $site_id], ['%d']) !== false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
@@ -139,13 +139,13 @@ class BDM_Retail_Sites {
|
|||||||
public static function generate_links_for_book($book_id) {
|
public static function generate_links_for_book($book_id) {
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
|
|
||||||
$book = BDM_Book_Manager::get_book($book_id);
|
$book = Umbra_Book_Manager::get_book($book_id);
|
||||||
if (!$book) {
|
if (!$book) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$sites = self::get_sites(true);
|
$sites = self::get_sites(true);
|
||||||
$links_table = BDM_Database::table('retail_links');
|
$links_table = Umbra_Database::table('retail_links');
|
||||||
|
|
||||||
// Delete existing links
|
// Delete existing links
|
||||||
$wpdb->delete($links_table, ['book_id' => $book_id], ['%d']);
|
$wpdb->delete($links_table, ['book_id' => $book_id], ['%d']);
|
||||||
@@ -266,7 +266,7 @@ class BDM_Retail_Sites {
|
|||||||
public static function update_link($link_id, $url) {
|
public static function update_link($link_id, $url) {
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
|
|
||||||
$table = BDM_Database::table('retail_links');
|
$table = Umbra_Database::table('retail_links');
|
||||||
|
|
||||||
return $wpdb->update(
|
return $wpdb->update(
|
||||||
$table,
|
$table,
|
||||||
@@ -283,7 +283,7 @@ class BDM_Retail_Sites {
|
|||||||
public static function create_custom_link($book_id, $site_id, $format, $url) {
|
public static function create_custom_link($book_id, $site_id, $format, $url) {
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
|
|
||||||
$table = BDM_Database::table('retail_links');
|
$table = Umbra_Database::table('retail_links');
|
||||||
|
|
||||||
$result = $wpdb->insert($table, [
|
$result = $wpdb->insert($table, [
|
||||||
'book_id' => $book_id,
|
'book_id' => $book_id,
|
||||||
@@ -5,14 +5,14 @@
|
|||||||
/**
|
/**
|
||||||
* SEO Metadata Manager class
|
* SEO Metadata Manager class
|
||||||
*
|
*
|
||||||
* @package BD_Bookshelf
|
* @package Umbra_Bookshelf
|
||||||
*/
|
*/
|
||||||
|
|
||||||
if (!defined('ABSPATH')) {
|
if (!defined('ABSPATH')) {
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
class BDM_SEO {
|
class Umbra_SEO {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
@@ -32,7 +32,7 @@ class BDM_SEO {
|
|||||||
public static function get_book_seo($book_id) {
|
public static function get_book_seo($book_id) {
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
|
|
||||||
$table = BDM_Database::table('book_seo');
|
$table = Umbra_Database::table('book_seo');
|
||||||
|
|
||||||
$seo = $wpdb->get_row(
|
$seo = $wpdb->get_row(
|
||||||
$wpdb->prepare(
|
$wpdb->prepare(
|
||||||
@@ -50,7 +50,7 @@ class BDM_SEO {
|
|||||||
public static function update_book_seo($book_id, $data) {
|
public static function update_book_seo($book_id, $data) {
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
|
|
||||||
$table = BDM_Database::table('book_seo');
|
$table = Umbra_Database::table('book_seo');
|
||||||
|
|
||||||
$existing = self::get_book_seo($book_id);
|
$existing = self::get_book_seo($book_id);
|
||||||
|
|
||||||
@@ -85,7 +85,7 @@ class BDM_SEO {
|
|||||||
public static function delete_book_seo($book_id) {
|
public static function delete_book_seo($book_id) {
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
|
|
||||||
$table = BDM_Database::table('book_seo');
|
$table = Umbra_Database::table('book_seo');
|
||||||
return $wpdb->delete($table, ['book_id' => $book_id], ['%d']) !== false;
|
return $wpdb->delete($table, ['book_id' => $book_id], ['%d']) !== false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,7 +138,7 @@ class BDM_SEO {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$seo = self::get_book_seo($book_id);
|
$seo = self::get_book_seo($book_id);
|
||||||
$book = BDM_Book_Manager::get_book($book_id);
|
$book = Umbra_Book_Manager::get_book($book_id);
|
||||||
|
|
||||||
if (!$book) {
|
if (!$book) {
|
||||||
return;
|
return;
|
||||||
@@ -206,7 +206,7 @@ class BDM_SEO {
|
|||||||
return $entry;
|
return $entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
$book = BDM_Book_Manager::get_book($book_id);
|
$book = Umbra_Book_Manager::get_book($book_id);
|
||||||
|
|
||||||
if (!$book) {
|
if (!$book) {
|
||||||
return $entry;
|
return $entry;
|
||||||
@@ -5,14 +5,14 @@
|
|||||||
/**
|
/**
|
||||||
* Series Management class
|
* Series Management class
|
||||||
*
|
*
|
||||||
* @package BD_Bookshelf
|
* @package Umbra_Bookshelf
|
||||||
*/
|
*/
|
||||||
|
|
||||||
if (!defined('ABSPATH')) {
|
if (!defined('ABSPATH')) {
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
class BDM_Series {
|
class Umbra_Series {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all series
|
* Get all series
|
||||||
@@ -20,7 +20,7 @@ class BDM_Series {
|
|||||||
public static function get_series($args = []) {
|
public static function get_series($args = []) {
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
|
|
||||||
$table = BDM_Database::table('series');
|
$table = Umbra_Database::table('series');
|
||||||
|
|
||||||
$defaults = [
|
$defaults = [
|
||||||
'number' => 20,
|
'number' => 20,
|
||||||
@@ -48,7 +48,7 @@ class BDM_Series {
|
|||||||
public static function get_series_by_id($series_id) {
|
public static function get_series_by_id($series_id) {
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
|
|
||||||
$table = BDM_Database::table('series');
|
$table = Umbra_Database::table('series');
|
||||||
|
|
||||||
return $wpdb->get_row(
|
return $wpdb->get_row(
|
||||||
$wpdb->prepare(
|
$wpdb->prepare(
|
||||||
@@ -64,7 +64,7 @@ class BDM_Series {
|
|||||||
public static function create_series($data) {
|
public static function create_series($data) {
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
|
|
||||||
$table = BDM_Database::table('series');
|
$table = Umbra_Database::table('series');
|
||||||
|
|
||||||
$result = $wpdb->insert($table, [
|
$result = $wpdb->insert($table, [
|
||||||
'name' => sanitize_text_field($data['name']),
|
'name' => sanitize_text_field($data['name']),
|
||||||
@@ -81,7 +81,7 @@ class BDM_Series {
|
|||||||
public static function update_series($series_id, $data) {
|
public static function update_series($series_id, $data) {
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
|
|
||||||
$table = BDM_Database::table('series');
|
$table = Umbra_Database::table('series');
|
||||||
|
|
||||||
$update_data = [];
|
$update_data = [];
|
||||||
|
|
||||||
@@ -119,7 +119,7 @@ class BDM_Series {
|
|||||||
global $wpdb;
|
global $wpdb;
|
||||||
|
|
||||||
// Unlink books from series
|
// Unlink books from series
|
||||||
$books_table = BDM_Database::table('books');
|
$books_table = Umbra_Database::table('books');
|
||||||
$wpdb->update(
|
$wpdb->update(
|
||||||
$books_table,
|
$books_table,
|
||||||
['series_id' => 0],
|
['series_id' => 0],
|
||||||
@@ -129,7 +129,7 @@ class BDM_Series {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Delete series
|
// Delete series
|
||||||
$table = BDM_Database::table('series');
|
$table = Umbra_Database::table('series');
|
||||||
return $wpdb->delete($table, ['id' => $series_id], ['%d']) !== false;
|
return $wpdb->delete($table, ['id' => $series_id], ['%d']) !== false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,7 +139,7 @@ class BDM_Series {
|
|||||||
public static function count_series() {
|
public static function count_series() {
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
|
|
||||||
$table = BDM_Database::table('series');
|
$table = Umbra_Database::table('series');
|
||||||
|
|
||||||
return $wpdb->get_var("SELECT COUNT(*) FROM {$table}");
|
return $wpdb->get_var("SELECT COUNT(*) FROM {$table}");
|
||||||
}
|
}
|
||||||
@@ -5,14 +5,14 @@
|
|||||||
/**
|
/**
|
||||||
* Shortcodes Manager class
|
* Shortcodes Manager class
|
||||||
*
|
*
|
||||||
* @package BD_Bookshelf
|
* @package Umbra_Bookshelf
|
||||||
*/
|
*/
|
||||||
|
|
||||||
if (!defined('ABSPATH')) {
|
if (!defined('ABSPATH')) {
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
class BDM_Shortcodes {
|
class Umbra_Shortcodes {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
@@ -75,7 +75,7 @@ class BDM_Shortcodes {
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
$links = BDM_Book_Manager::get_book_retail_links($book_id, $format);
|
$links = Umbra_Book_Manager::get_book_retail_links($book_id, $format);
|
||||||
if (empty($links)) {
|
if (empty($links)) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
@@ -136,12 +136,12 @@ class BDM_Shortcodes {
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
$book = BDM_Book_Manager::get_book_by_title(sanitize_text_field($atts['title']));
|
$book = Umbra_Book_Manager::get_book_by_title(sanitize_text_field($atts['title']));
|
||||||
if (!$book) {
|
if (!$book) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
$rating = BDM_Book_Manager::get_rating($book->id);
|
$rating = Umbra_Book_Manager::get_rating($book->id);
|
||||||
$format = in_array($atts['format'], ['stars', 'numeric', 'both']) ? $atts['format'] : 'both';
|
$format = in_array($atts['format'], ['stars', 'numeric', 'both']) ? $atts['format'] : 'both';
|
||||||
|
|
||||||
$output = '<span class="bd-bookshelf-rating">';
|
$output = '<span class="bd-bookshelf-rating">';
|
||||||
@@ -201,7 +201,7 @@ class BDM_Shortcodes {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$table = BDM_Database::table('shortcodes');
|
$table = Umbra_Database::table('shortcodes');
|
||||||
$shortcode = $wpdb->get_row(
|
$shortcode = $wpdb->get_row(
|
||||||
$wpdb->prepare(
|
$wpdb->prepare(
|
||||||
"SELECT * FROM {$table} WHERE slug = %s AND is_active = 1",
|
"SELECT * FROM {$table} WHERE slug = %s AND is_active = 1",
|
||||||
@@ -228,7 +228,7 @@ class BDM_Shortcodes {
|
|||||||
private function track_click($shortcode_id) {
|
private function track_click($shortcode_id) {
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
|
|
||||||
$table = BDM_Database::table('shortcode_clicks');
|
$table = Umbra_Database::table('shortcode_clicks');
|
||||||
$wpdb->insert($table, [
|
$wpdb->insert($table, [
|
||||||
'shortcode_id' => $shortcode_id,
|
'shortcode_id' => $shortcode_id,
|
||||||
'ip_address' => $this->get_client_ip(),
|
'ip_address' => $this->get_client_ip(),
|
||||||
@@ -239,7 +239,7 @@ class BDM_Shortcodes {
|
|||||||
// Increment click count
|
// Increment click count
|
||||||
$wpdb->query(
|
$wpdb->query(
|
||||||
$wpdb->prepare(
|
$wpdb->prepare(
|
||||||
"UPDATE " . BDM_Database::table('shortcodes') . " SET click_count = click_count + 1 WHERE id = %d",
|
"UPDATE " . Umbra_Database::table('shortcodes') . " SET click_count = click_count + 1 WHERE id = %d",
|
||||||
$shortcode_id
|
$shortcode_id
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@@ -268,7 +268,7 @@ class BDM_Shortcodes {
|
|||||||
public static function create_shortcode($slug, $target_url, $redirect_type = '301', $description = '') {
|
public static function create_shortcode($slug, $target_url, $redirect_type = '301', $description = '') {
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
|
|
||||||
$table = BDM_Database::table('shortcodes');
|
$table = Umbra_Database::table('shortcodes');
|
||||||
|
|
||||||
$result = $wpdb->insert($table, [
|
$result = $wpdb->insert($table, [
|
||||||
'slug' => sanitize_title($slug),
|
'slug' => sanitize_title($slug),
|
||||||
@@ -287,7 +287,7 @@ class BDM_Shortcodes {
|
|||||||
public static function update_shortcode($shortcode_id, $data) {
|
public static function update_shortcode($shortcode_id, $data) {
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
|
|
||||||
$table = BDM_Database::table('shortcodes');
|
$table = Umbra_Database::table('shortcodes');
|
||||||
|
|
||||||
$update_data = [];
|
$update_data = [];
|
||||||
|
|
||||||
@@ -332,7 +332,7 @@ class BDM_Shortcodes {
|
|||||||
public static function delete_shortcode($shortcode_id) {
|
public static function delete_shortcode($shortcode_id) {
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
|
|
||||||
$wpdb->delete(BDM_Database::table('shortcode_clicks'), ['shortcode_id' => $shortcode_id], ['%d']);
|
$wpdb->delete(Umbra_Database::table('shortcode_clicks'), ['shortcode_id' => $shortcode_id], ['%d']);
|
||||||
return $wpdb->delete(BDM_Database::table('shortcodes'), ['id' => $shortcode_id], ['%d']) !== false;
|
return $wpdb->delete(Umbra_Database::table('shortcodes'), ['id' => $shortcode_id], ['%d']) !== false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5,10 +5,19 @@
|
|||||||
/**
|
/**
|
||||||
* Theme Compatibility Layer
|
* Theme Compatibility Layer
|
||||||
*
|
*
|
||||||
* Provides compatibility with BD BookHelper plugin interface
|
* Provides the static helper interface the BD Bookshelf theme calls (book
|
||||||
* so the BD Bookshelf theme works with our new plugin.
|
* covers, pen names, ratings, retailer links, series info, ...), backed by the
|
||||||
|
* catalogue module.
|
||||||
*
|
*
|
||||||
* @package BD_Bookshelf
|
* This class used to be declared as BD_BookShelfHelper, which collided with
|
||||||
|
* the library module's own BD_BookShelfHelper class. That collision was latent
|
||||||
|
* while the two were separate plugins -- it only fired when both were active --
|
||||||
|
* but became an unconditional "Cannot declare class" fatal error once they
|
||||||
|
* were merged into a single plugin. It now has its own name, and the
|
||||||
|
* class_alias() at the bottom keeps the legacy name working for the
|
||||||
|
* unmodified theme.
|
||||||
|
*
|
||||||
|
* @package Umbra_Bookshelf
|
||||||
*/
|
*/
|
||||||
|
|
||||||
if (!defined('ABSPATH')) {
|
if (!defined('ABSPATH')) {
|
||||||
@@ -16,10 +25,9 @@ if (!defined('ABSPATH')) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* BD_BookShelfHelper class for theme compatibility
|
* Static helper API consumed by the theme.
|
||||||
* Maps theme expectations to our new plugin methods
|
|
||||||
*/
|
*/
|
||||||
class BD_BookShelfHelper {
|
class Umbra_Theme_Compatibility {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get book cover URL
|
* Get book cover URL
|
||||||
@@ -77,7 +85,7 @@ class BD_BookShelfHelper {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
$links = BDM_Book_Manager::get_book_retail_links($book->id, $format);
|
$links = Umbra_Book_Manager::get_book_retail_links($book->id, $format);
|
||||||
$retailers = [];
|
$retailers = [];
|
||||||
|
|
||||||
foreach ($links as $link) {
|
foreach ($links as $link) {
|
||||||
@@ -102,7 +110,7 @@ class BD_BookShelfHelper {
|
|||||||
* Get series book count
|
* Get series book count
|
||||||
*/
|
*/
|
||||||
public static function get_series_book_count($series_id) {
|
public static function get_series_book_count($series_id) {
|
||||||
$books = BDM_Book_Manager::get_books_by_series($series_id);
|
$books = Umbra_Book_Manager::get_books_by_series($series_id);
|
||||||
return count($books);
|
return count($books);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,7 +118,7 @@ class BD_BookShelfHelper {
|
|||||||
* Get series info
|
* Get series info
|
||||||
*/
|
*/
|
||||||
public static function get_series_info($series_id) {
|
public static function get_series_info($series_id) {
|
||||||
$series = BDM_Series::get_series_by_id($series_id);
|
$series = Umbra_Series::get_series_by_id($series_id);
|
||||||
if ($series) {
|
if ($series) {
|
||||||
return [
|
return [
|
||||||
'name' => $series->name,
|
'name' => $series->name,
|
||||||
@@ -189,7 +197,7 @@ class BD_BookShelfHelper {
|
|||||||
global $wpdb;
|
global $wpdb;
|
||||||
|
|
||||||
// Check if post_id is actually a book ID from our database
|
// Check if post_id is actually a book ID from our database
|
||||||
$book = BDM_Book_Manager::get_book($post_id);
|
$book = Umbra_Book_Manager::get_book($post_id);
|
||||||
if ($book) {
|
if ($book) {
|
||||||
return $book;
|
return $book;
|
||||||
}
|
}
|
||||||
@@ -198,13 +206,13 @@ class BD_BookShelfHelper {
|
|||||||
$book_id = get_post_meta($post_id, '_bd_bookshelf_book_id', true);
|
$book_id = get_post_meta($post_id, '_bd_bookshelf_book_id', true);
|
||||||
|
|
||||||
if ($book_id) {
|
if ($book_id) {
|
||||||
return BDM_Book_Manager::get_book($book_id);
|
return Umbra_Book_Manager::get_book($book_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to find by title
|
// Try to find by title
|
||||||
$post = get_post($post_id);
|
$post = get_post($post_id);
|
||||||
if ($post) {
|
if ($post) {
|
||||||
$book = BDM_Book_Manager::get_book_by_title($post->post_title);
|
$book = Umbra_Book_Manager::get_book_by_title($post->post_title);
|
||||||
if ($book) {
|
if ($book) {
|
||||||
// Link post to book
|
// Link post to book
|
||||||
update_post_meta($post_id, '_bd_bookshelf_book_id', $book->id);
|
update_post_meta($post_id, '_bd_bookshelf_book_id', $book->id);
|
||||||
@@ -215,3 +223,14 @@ class BD_BookShelfHelper {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Legacy alias.
|
||||||
|
*
|
||||||
|
* The BD Bookshelf theme (unchanged, and out of scope for this consolidation)
|
||||||
|
* still calls BD_BookShelfHelper:: statically. Keep that working. Guarded
|
||||||
|
* because a theme or add-on may legitimately have declared the name already.
|
||||||
|
*/
|
||||||
|
if (!class_exists('BD_BookShelfHelper', false)) {
|
||||||
|
class_alias('Umbra_Theme_Compatibility', 'BD_BookShelfHelper');
|
||||||
|
}
|
||||||
@@ -5,20 +5,20 @@
|
|||||||
/**
|
/**
|
||||||
* Email Admin Class
|
* Email Admin Class
|
||||||
*
|
*
|
||||||
* @package BD_SimpleEmail
|
* @package Umbra_Simple_Email
|
||||||
*/
|
*/
|
||||||
|
|
||||||
if (!defined('ABSPATH')) {
|
if (!defined('ABSPATH')) {
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
class BD_SimpleEmail_Admin {
|
class Umbra_Simple_Email_Admin {
|
||||||
private static $instance = null;
|
private static $instance = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get instance
|
* Get instance
|
||||||
*
|
*
|
||||||
* @return BD_SimpleEmail_Admin
|
* @return Umbra_Simple_Email_Admin
|
||||||
*/
|
*/
|
||||||
public static function instance() {
|
public static function instance() {
|
||||||
if (null === self::$instance) {
|
if (null === self::$instance) {
|
||||||
@@ -40,19 +40,23 @@ class BD_SimpleEmail_Admin {
|
|||||||
* @param string $hook Current admin page.
|
* @param string $hook Current admin page.
|
||||||
*/
|
*/
|
||||||
public function enqueue_assets($hook) {
|
public function enqueue_assets($hook) {
|
||||||
if (strpos($hook, 'bd-simpleemail') !== false || strpos($hook, 'bd-email') !== false) {
|
// Matches the submenu slugs registered by Umbra_Simple_Email::add_menu_pages().
|
||||||
|
if (strpos($hook, 'umbra-bookshelf-subscribers') !== false
|
||||||
|
|| strpos($hook, 'umbra-bookshelf-newsletter') !== false
|
||||||
|
|| strpos($hook, 'umbra-bookshelf-email-settings') !== false) {
|
||||||
|
|
||||||
wp_enqueue_style(
|
wp_enqueue_style(
|
||||||
'bd-email-admin',
|
'umbra-bookshelf-email-admin',
|
||||||
BD_EMAIL_URI . 'assets/css/admin.css',
|
UMBRA_BOOKSHELF_URL . 'email/assets/css/admin.css',
|
||||||
array(),
|
array(),
|
||||||
BD_EMAIL_VERSION
|
UMBRA_BOOKSHELF_EMAIL_VERSION
|
||||||
);
|
);
|
||||||
|
|
||||||
wp_enqueue_script(
|
wp_enqueue_script(
|
||||||
'bd-email-admin',
|
'umbra-bookshelf-email-admin',
|
||||||
BD_EMAIL_URI . 'assets/js/admin.js',
|
UMBRA_BOOKSHELF_URL . 'email/assets/js/admin.js',
|
||||||
array('jquery'),
|
array('jquery'),
|
||||||
BD_EMAIL_VERSION,
|
UMBRA_BOOKSHELF_EMAIL_VERSION,
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -143,7 +147,7 @@ class BD_SimpleEmail_Admin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($content) {
|
if ($content) {
|
||||||
$result = BD_Email_Sender::send_newsletter($subject, $content);
|
$result = Umbra_Email_Sender::send_newsletter($subject, $content);
|
||||||
|
|
||||||
if ($result['success']) {
|
if ($result['success']) {
|
||||||
self::log_newsletter($subject, $content, $result['count']);
|
self::log_newsletter($subject, $content, $result['count']);
|
||||||
@@ -163,7 +167,7 @@ class BD_SimpleEmail_Admin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$settings = BD_SimpleEmail::get_settings();
|
$settings = Umbra_Simple_Email::get_settings();
|
||||||
$has_ai = !empty($settings['gemini_api_key']);
|
$has_ai = !empty($settings['gemini_api_key']);
|
||||||
|
|
||||||
echo '<div class="wrap">';
|
echo '<div class="wrap">';
|
||||||
@@ -231,7 +235,7 @@ class BD_SimpleEmail_Admin {
|
|||||||
update_option('bdm_reply_to', sanitize_email($_POST['reply_to'] ?? ''));
|
update_option('bdm_reply_to', sanitize_email($_POST['reply_to'] ?? ''));
|
||||||
|
|
||||||
if (!empty($_POST['gmail_password'])) {
|
if (!empty($_POST['gmail_password'])) {
|
||||||
update_option('bdm_gmail_password', BD_Email_Sender::encrypt_password($_POST['gmail_password']));
|
update_option('bdm_gmail_password', Umbra_Email_Sender::encrypt_password($_POST['gmail_password']));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!empty($_POST['gemini_api_key'])) {
|
if (!empty($_POST['gemini_api_key'])) {
|
||||||
@@ -241,8 +245,8 @@ class BD_SimpleEmail_Admin {
|
|||||||
echo '<div class="updated"><p>' . esc_html__('Settings saved.', 'bd-simpleemail') . '</p></div>';
|
echo '<div class="updated"><p>' . esc_html__('Settings saved.', 'bd-simpleemail') . '</p></div>';
|
||||||
}
|
}
|
||||||
|
|
||||||
$settings = BD_SimpleEmail::get_settings();
|
$settings = Umbra_Simple_Email::get_settings();
|
||||||
$business = BD_SimpleEmail::get_business_info();
|
$business = Umbra_Simple_Email::get_business_info();
|
||||||
|
|
||||||
echo '<div class="wrap">';
|
echo '<div class="wrap">';
|
||||||
echo '<h1>' . esc_html__('BD SimpleEmail Settings', 'bd-simpleemail') . '</h1>';
|
echo '<h1>' . esc_html__('BD SimpleEmail Settings', 'bd-simpleemail') . '</h1>';
|
||||||
@@ -305,7 +309,7 @@ class BD_SimpleEmail_Admin {
|
|||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
private static function generate_newsletter($subject, $prompt) {
|
private static function generate_newsletter($subject, $prompt) {
|
||||||
$settings = BD_SimpleEmail::get_settings();
|
$settings = Umbra_Simple_Email::get_settings();
|
||||||
|
|
||||||
if (empty($settings['gemini_api_key'])) {
|
if (empty($settings['gemini_api_key'])) {
|
||||||
return '';
|
return '';
|
||||||
@@ -5,14 +5,14 @@
|
|||||||
/**
|
/**
|
||||||
* Email Installation and Database
|
* Email Installation and Database
|
||||||
*
|
*
|
||||||
* @package BD_SimpleEmail
|
* @package Umbra_Simple_Email
|
||||||
*/
|
*/
|
||||||
|
|
||||||
if (!defined('ABSPATH')) {
|
if (!defined('ABSPATH')) {
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
class BD_SimpleEmail_Install {
|
class Umbra_Simple_Email_Install {
|
||||||
/**
|
/**
|
||||||
* Activate plugin
|
* Activate plugin
|
||||||
*/
|
*/
|
||||||
@@ -5,14 +5,14 @@
|
|||||||
/**
|
/**
|
||||||
* Email Sender Class
|
* Email Sender Class
|
||||||
*
|
*
|
||||||
* @package BD_SimpleEmail
|
* @package Umbra_Simple_Email
|
||||||
*/
|
*/
|
||||||
|
|
||||||
if (!defined('ABSPATH')) {
|
if (!defined('ABSPATH')) {
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
class BD_Email_Sender {
|
class Umbra_Email_Sender {
|
||||||
/**
|
/**
|
||||||
* Send newsletter to all subscribers
|
* Send newsletter to all subscribers
|
||||||
*
|
*
|
||||||
@@ -21,25 +21,25 @@ class BD_Email_Sender {
|
|||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public static function send_newsletter($subject, $content) {
|
public static function send_newsletter($subject, $content) {
|
||||||
$settings = BD_SimpleEmail::get_settings();
|
$settings = Umbra_Simple_Email::get_settings();
|
||||||
|
|
||||||
if (empty($settings['gmail_address']) || empty($settings['gmail_password'])) {
|
if (empty($settings['gmail_address']) || empty($settings['gmail_password'])) {
|
||||||
return array(
|
return array(
|
||||||
'success' => false,
|
'success' => false,
|
||||||
'message' => esc_html__('Gmail not configured', 'bd-simpleemail'),
|
'message' => esc_html__('Gmail not configured', 'umbra-bookshelf'),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
$subscribers = BD_SimpleEmail::get_subscribers();
|
$subscribers = Umbra_Simple_Email::get_subscribers();
|
||||||
|
|
||||||
if (empty($subscribers)) {
|
if (empty($subscribers)) {
|
||||||
return array(
|
return array(
|
||||||
'success' => false,
|
'success' => false,
|
||||||
'message' => esc_html__('No subscribers found', 'bd-simpleemail'),
|
'message' => esc_html__('No subscribers found', 'umbra-bookshelf'),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
$business = BD_SimpleEmail::get_business_info();
|
$business = Umbra_Simple_Email::get_business_info();
|
||||||
$html = self::build_email_html($subject, $content, $business);
|
$html = self::build_email_html($subject, $content, $business);
|
||||||
|
|
||||||
$sent = 0;
|
$sent = 0;
|
||||||
@@ -75,10 +75,10 @@ class BD_Email_Sender {
|
|||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
private static function send_single_email($to, $subject, $html, $first_name = '') {
|
private static function send_single_email($to, $subject, $html, $first_name = '') {
|
||||||
$settings = BD_SimpleEmail::get_settings();
|
$settings = Umbra_Simple_Email::get_settings();
|
||||||
|
|
||||||
// Replace placeholder with actual unsubscribe link
|
// Replace placeholder with actual unsubscribe link
|
||||||
$token = BD_SimpleEmail::generate_unsubscribe_token($to);
|
$token = Umbra_Simple_Email::generate_unsubscribe_token($to);
|
||||||
$unsubscribe_url = add_query_arg(array(
|
$unsubscribe_url = add_query_arg(array(
|
||||||
'email' => rawurlencode($to),
|
'email' => rawurlencode($to),
|
||||||
'token' => $token,
|
'token' => $token,
|
||||||
@@ -5,20 +5,20 @@
|
|||||||
/**
|
/**
|
||||||
* Main SimpleEmail Plugin Class
|
* Main SimpleEmail Plugin Class
|
||||||
*
|
*
|
||||||
* @package BD_SimpleEmail
|
* @package Umbra_Simple_Email
|
||||||
*/
|
*/
|
||||||
|
|
||||||
if (!defined('ABSPATH')) {
|
if (!defined('ABSPATH')) {
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
class BD_SimpleEmail {
|
class Umbra_Simple_Email {
|
||||||
private static $instance = null;
|
private static $instance = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get instance
|
* Get instance
|
||||||
*
|
*
|
||||||
* @return BD_SimpleEmail
|
* @return Umbra_Simple_Email
|
||||||
*/
|
*/
|
||||||
public static function instance() {
|
public static function instance() {
|
||||||
if (null === self::$instance) {
|
if (null === self::$instance) {
|
||||||
@@ -51,41 +51,34 @@ class BD_SimpleEmail {
|
|||||||
* Add admin menu pages
|
* Add admin menu pages
|
||||||
*/
|
*/
|
||||||
public function add_menu_pages() {
|
public function add_menu_pages() {
|
||||||
add_menu_page(
|
// The top-level "Umbra Bookshelf" menu is registered once by the plugin
|
||||||
esc_html__('BD SimpleEmail', 'bd-simpleemail'),
|
// bootstrap (Umbra_Bookshelf::register_admin_menu); this module only
|
||||||
esc_html__('BD SimpleEmail', 'bd-simpleemail'),
|
// contributes submenus beneath it.
|
||||||
|
add_submenu_page(
|
||||||
|
'umbra-bookshelf',
|
||||||
|
esc_html__('Subscribers', 'umbra-bookshelf'),
|
||||||
|
esc_html__('Subscribers', 'umbra-bookshelf'),
|
||||||
'manage_options',
|
'manage_options',
|
||||||
'bd-simpleemail',
|
'umbra-bookshelf-subscribers',
|
||||||
'',
|
array('Umbra_Simple_Email_Admin', 'subscribers_page')
|
||||||
'dashicons-email',
|
|
||||||
32
|
|
||||||
);
|
);
|
||||||
|
|
||||||
add_submenu_page(
|
add_submenu_page(
|
||||||
'bd-simpleemail',
|
'umbra-bookshelf',
|
||||||
esc_html__('Subscribers', 'bd-simpleemail'),
|
esc_html__('New Newsletter', 'umbra-bookshelf'),
|
||||||
esc_html__('Subscribers', 'bd-simpleemail'),
|
esc_html__('New Newsletter', 'umbra-bookshelf'),
|
||||||
'manage_options',
|
'manage_options',
|
||||||
'bd-email-subscribers',
|
'umbra-bookshelf-newsletter',
|
||||||
array('BD_SimpleEmail_Admin', 'subscribers_page')
|
array('Umbra_Simple_Email_Admin', 'new_newsletter_page')
|
||||||
);
|
);
|
||||||
|
|
||||||
add_submenu_page(
|
add_submenu_page(
|
||||||
'bd-simpleemail',
|
'umbra-bookshelf',
|
||||||
esc_html__('New Newsletter', 'bd-simpleemail'),
|
esc_html__('Email Settings', 'umbra-bookshelf'),
|
||||||
esc_html__('New Newsletter', 'bd-simpleemail'),
|
esc_html__('Email Settings', 'umbra-bookshelf'),
|
||||||
'manage_options',
|
'manage_options',
|
||||||
'bd-email-new',
|
'umbra-bookshelf-email-settings',
|
||||||
array('BD_SimpleEmail_Admin', 'new_newsletter_page')
|
array('Umbra_Simple_Email_Admin', 'settings_page')
|
||||||
);
|
|
||||||
|
|
||||||
add_submenu_page(
|
|
||||||
'bd-simpleemail',
|
|
||||||
esc_html__('Settings', 'bd-simpleemail'),
|
|
||||||
esc_html__('Settings', 'bd-simpleemail'),
|
|
||||||
'manage_options',
|
|
||||||
'bd-email-settings',
|
|
||||||
array('BD_SimpleEmail_Admin', 'settings_page')
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,22 +138,22 @@ class BD_SimpleEmail {
|
|||||||
$email = sanitize_email($_POST['email'] ?? '');
|
$email = sanitize_email($_POST['email'] ?? '');
|
||||||
|
|
||||||
if (!$email || !is_email($email)) {
|
if (!$email || !is_email($email)) {
|
||||||
wp_send_json_error(array('message' => esc_html__('Invalid email address', 'bd-simpleemail')));
|
wp_send_json_error(array('message' => esc_html__('Invalid email address', 'umbra-bookshelf')));
|
||||||
}
|
}
|
||||||
|
|
||||||
$first_name = sanitize_text_field($_POST['first_name'] ?? '');
|
$first_name = sanitize_text_field($_POST['first_name'] ?? '');
|
||||||
$opt_in = isset($_POST['opt_in']) && $_POST['opt_in'];
|
$opt_in = isset($_POST['opt_in']) && $_POST['opt_in'];
|
||||||
|
|
||||||
if (!$opt_in) {
|
if (!$opt_in) {
|
||||||
wp_send_json_error(array('message' => esc_html__('Please confirm your subscription', 'bd-simpleemail')));
|
wp_send_json_error(array('message' => esc_html__('Please confirm your subscription', 'umbra-bookshelf')));
|
||||||
}
|
}
|
||||||
|
|
||||||
$result = self::subscribe($email, $first_name);
|
$result = self::subscribe($email, $first_name);
|
||||||
|
|
||||||
if ($result) {
|
if ($result) {
|
||||||
wp_send_json_success(array('message' => esc_html__('Thank you for subscribing!', 'bd-simpleemail')));
|
wp_send_json_success(array('message' => esc_html__('Thank you for subscribing!', 'umbra-bookshelf')));
|
||||||
} else {
|
} else {
|
||||||
wp_send_json_error(array('message' => esc_html__('You are already subscribed or cannot be added', 'bd-simpleemail')));
|
wp_send_json_error(array('message' => esc_html__('You are already subscribed or cannot be added', 'umbra-bookshelf')));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -316,19 +309,19 @@ class BD_SimpleEmail {
|
|||||||
<form id="bdm-signup-form" class="bdm-signup-form">
|
<form id="bdm-signup-form" class="bdm-signup-form">
|
||||||
<?php wp_nonce_field('bdm_ajax_signup', 'bdm_signup_nonce'); ?>
|
<?php wp_nonce_field('bdm_ajax_signup', 'bdm_signup_nonce'); ?>
|
||||||
<p class="form-field">
|
<p class="form-field">
|
||||||
<label for="bdm-email"><?php esc_html_e('Email Address', 'bd-simpleemail'); ?> *</label>
|
<label for="bdm-email"><?php esc_html_e('Email Address', 'umbra-bookshelf'); ?> *</label>
|
||||||
<input type="email" id="bdm-email" name="email" required>
|
<input type="email" id="bdm-email" name="email" required>
|
||||||
</p>
|
</p>
|
||||||
<p class="form-field">
|
<p class="form-field">
|
||||||
<label for="bdm-first-name"><?php esc_html_e('First Name', 'bd-simpleemail'); ?></label>
|
<label for="bdm-first-name"><?php esc_html_e('First Name', 'umbra-bookshelf'); ?></label>
|
||||||
<input type="text" id="bdm-first-name" name="first_name">
|
<input type="text" id="bdm-first-name" name="first_name">
|
||||||
</p>
|
</p>
|
||||||
<p class="form-field checkbox-field">
|
<p class="form-field checkbox-field">
|
||||||
<input type="checkbox" id="bdm-opt-in" name="opt_in" value="1" required>
|
<input type="checkbox" id="bdm-opt-in" name="opt_in" value="1" required>
|
||||||
<label for="bdm-opt-in"><?php esc_html_e('I agree to receive email updates.', 'bd-simpleemail'); ?></label>
|
<label for="bdm-opt-in"><?php esc_html_e('I agree to receive email updates.', 'umbra-bookshelf'); ?></label>
|
||||||
</p>
|
</p>
|
||||||
<p class="form-submit">
|
<p class="form-submit">
|
||||||
<button type="submit" class="submit-btn"><?php esc_html_e('Subscribe', 'bd-simpleemail'); ?></button>
|
<button type="submit" class="submit-btn"><?php esc_html_e('Subscribe', 'umbra-bookshelf'); ?></button>
|
||||||
</p>
|
</p>
|
||||||
<div class="form-response" style="display:none;"></div>
|
<div class="form-response" style="display:none;"></div>
|
||||||
</form>
|
</form>
|
||||||
@@ -360,7 +353,7 @@ class BD_SimpleEmail {
|
|||||||
response.style.display = 'block';
|
response.style.display = 'block';
|
||||||
})
|
})
|
||||||
.catch(function() {
|
.catch(function() {
|
||||||
response.innerHTML = '<p class="error"><?php esc_html_e('An error occurred. Please try again.', 'bd-simpleemail'); ?></p>';
|
response.innerHTML = '<p class="error"><?php esc_html_e('An error occurred. Please try again.', 'umbra-bookshelf'); ?></p>';
|
||||||
response.style.display = 'block';
|
response.style.display = 'block';
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -5,20 +5,20 @@
|
|||||||
/**
|
/**
|
||||||
* Main Plugin Class
|
* Main Plugin Class
|
||||||
*
|
*
|
||||||
* @package BD_BookShelfHelper
|
* @package Umbra_Bookshelf_Helper
|
||||||
*/
|
*/
|
||||||
|
|
||||||
if (!defined('ABSPATH')) {
|
if (!defined('ABSPATH')) {
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
class BD_BookShelfHelper {
|
class Umbra_Bookshelf_Helper {
|
||||||
private static $instance = null;
|
private static $instance = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get instance
|
* Get instance
|
||||||
*
|
*
|
||||||
* @return BD_BookShelfHelper
|
* @return Umbra_Bookshelf_Helper
|
||||||
*/
|
*/
|
||||||
public static function instance() {
|
public static function instance() {
|
||||||
if (null === self::$instance) {
|
if (null === self::$instance) {
|
||||||
@@ -48,7 +48,7 @@ class BD_BookShelfHelper {
|
|||||||
add_filter('bdm_nav_menu_items', array($this, 'filter_series_menu_item'), 10, 2);
|
add_filter('bdm_nav_menu_items', array($this, 'filter_series_menu_item'), 10, 2);
|
||||||
|
|
||||||
if (is_admin()) {
|
if (is_admin()) {
|
||||||
BD_BookShelfHelper_Admin::instance();
|
Umbra_Bookshelf_Helper_Admin::instance();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Schedule cron jobs on init
|
// Schedule cron jobs on init
|
||||||
@@ -65,20 +65,20 @@ class BD_BookShelfHelper {
|
|||||||
public function register_post_types() {
|
public function register_post_types() {
|
||||||
register_post_type('book', array(
|
register_post_type('book', array(
|
||||||
'labels' => array(
|
'labels' => array(
|
||||||
'name' => esc_html__('Books', 'bd-bookshelfhelper'),
|
'name' => esc_html__('Books', 'umbra-bookshelf'),
|
||||||
'singular_name' => esc_html__('Book', 'bd-bookshelfhelper'),
|
'singular_name' => esc_html__('Book', 'umbra-bookshelf'),
|
||||||
'add_new' => esc_html__('Add New', 'bd-bookshelfhelper'),
|
'add_new' => esc_html__('Add New', 'umbra-bookshelf'),
|
||||||
'add_new_item' => esc_html__('Add New Book', 'bd-bookshelfhelper'),
|
'add_new_item' => esc_html__('Add New Book', 'umbra-bookshelf'),
|
||||||
'edit_item' => esc_html__('Edit Book', 'bd-bookshelfhelper'),
|
'edit_item' => esc_html__('Edit Book', 'umbra-bookshelf'),
|
||||||
'new_item' => esc_html__('New Book', 'bd-bookshelfhelper'),
|
'new_item' => esc_html__('New Book', 'umbra-bookshelf'),
|
||||||
'view_item' => esc_html__('View Book', 'bd-bookshelfhelper'),
|
'view_item' => esc_html__('View Book', 'umbra-bookshelf'),
|
||||||
'search_items' => esc_html__('Search Books', 'bd-bookshelfhelper'),
|
'search_items' => esc_html__('Search Books', 'umbra-bookshelf'),
|
||||||
'not_found' => esc_html__('No books found', 'bd-bookshelfhelper'),
|
'not_found' => esc_html__('No books found', 'umbra-bookshelf'),
|
||||||
'not_found_in_trash' => esc_html__('No books found in trash', 'bd-bookshelfhelper'),
|
'not_found_in_trash' => esc_html__('No books found in trash', 'umbra-bookshelf'),
|
||||||
),
|
),
|
||||||
'public' => true,
|
'public' => true,
|
||||||
'has_archive' => true,
|
'has_archive' => true,
|
||||||
'show_in_menu' => 'bd-bookshelf',
|
'show_in_menu' => 'umbra-bookshelf',
|
||||||
'supports' => array('title', 'editor', 'thumbnail', 'excerpt', 'custom-fields'),
|
'supports' => array('title', 'editor', 'thumbnail', 'excerpt', 'custom-fields'),
|
||||||
'rewrite' => array('slug' => 'book'),
|
'rewrite' => array('slug' => 'book'),
|
||||||
'menu_icon' => 'dashicons-book-alt',
|
'menu_icon' => 'dashicons-book-alt',
|
||||||
@@ -94,15 +94,15 @@ class BD_BookShelfHelper {
|
|||||||
public function register_taxonomies() {
|
public function register_taxonomies() {
|
||||||
register_taxonomy('series', 'book', array(
|
register_taxonomy('series', 'book', array(
|
||||||
'labels' => array(
|
'labels' => array(
|
||||||
'name' => esc_html__('Series', 'bd-bookshelfhelper'),
|
'name' => esc_html__('Series', 'umbra-bookshelf'),
|
||||||
'singular_name' => esc_html__('Series', 'bd-bookshelfhelper'),
|
'singular_name' => esc_html__('Series', 'umbra-bookshelf'),
|
||||||
'search_items' => esc_html__('Search Series', 'bd-bookshelfhelper'),
|
'search_items' => esc_html__('Search Series', 'umbra-bookshelf'),
|
||||||
'all_items' => esc_html__('All Series', 'bd-bookshelfhelper'),
|
'all_items' => esc_html__('All Series', 'umbra-bookshelf'),
|
||||||
'edit_item' => esc_html__('Edit Series', 'bd-bookshelfhelper'),
|
'edit_item' => esc_html__('Edit Series', 'umbra-bookshelf'),
|
||||||
'update_item' => esc_html__('Update Series', 'bd-bookshelfhelper'),
|
'update_item' => esc_html__('Update Series', 'umbra-bookshelf'),
|
||||||
'add_new_item' => esc_html__('Add New Series', 'bd-bookshelfhelper'),
|
'add_new_item' => esc_html__('Add New Series', 'umbra-bookshelf'),
|
||||||
'new_item_name' => esc_html__('New Series Name', 'bd-bookshelfhelper'),
|
'new_item_name' => esc_html__('New Series Name', 'umbra-bookshelf'),
|
||||||
'not_found' => esc_html__('No series found', 'bd-bookshelfhelper'),
|
'not_found' => esc_html__('No series found', 'umbra-bookshelf'),
|
||||||
),
|
),
|
||||||
'hierarchical' => true,
|
'hierarchical' => true,
|
||||||
'public' => true,
|
'public' => true,
|
||||||
@@ -157,7 +157,7 @@ class BD_BookShelfHelper {
|
|||||||
|
|
||||||
$series_count = self::series_count();
|
$series_count = self::series_count();
|
||||||
if ($series_count > 0) {
|
if ($series_count > 0) {
|
||||||
$series_item = '<li class="menu-item menu-item-type-taxonomy series-menu-item"><a href="' . home_url('/series/') . '">' . esc_html__('Series', 'bd-bookshelfhelper') . '</a></li>';
|
$series_item = '<li class="menu-item menu-item-type-taxonomy series-menu-item"><a href="' . home_url('/series/') . '">' . esc_html__('Series', 'umbra-bookshelf') . '</a></li>';
|
||||||
|
|
||||||
if (preg_match('/(<li[^>]*>.*?Books.*?<\/li>)/s', $items, $matches)) {
|
if (preg_match('/(<li[^>]*>.*?Books.*?<\/li>)/s', $items, $matches)) {
|
||||||
$books_item = $matches[1];
|
$books_item = $matches[1];
|
||||||
@@ -273,10 +273,10 @@ class BD_BookShelfHelper {
|
|||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
private function render_book_display($book) {
|
private function render_book_display($book) {
|
||||||
$cover_url = BD_BookShelfHelper::get_cover_url($book->ID, 'medium');
|
$cover_url = Umbra_Bookshelf_Helper::get_cover_url($book->ID, 'medium');
|
||||||
$pen_name = BD_BookShelfHelper::get_pen_name($book->ID);
|
$pen_name = Umbra_Bookshelf_Helper::get_pen_name($book->ID);
|
||||||
$blurb = BD_BookShelfHelper::get_localized_blurb($book->ID);
|
$blurb = Umbra_Bookshelf_Helper::get_localized_blurb($book->ID);
|
||||||
$links = BD_BookShelfHelper::get_retailer_links($book->ID);
|
$links = Umbra_Bookshelf_Helper::get_retailer_links($book->ID);
|
||||||
|
|
||||||
ob_start();
|
ob_start();
|
||||||
?>
|
?>
|
||||||
@@ -508,7 +508,7 @@ class BD_BookShelfHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return BD_BHS_URI . 'assets/images/default-cover.png';
|
return UMBRA_BOOKSHELF_URL . 'assets/images/default-cover.png';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -5,20 +5,20 @@
|
|||||||
/**
|
/**
|
||||||
* Admin Class
|
* Admin Class
|
||||||
*
|
*
|
||||||
* @package BD_BookShelfHelper
|
* @package Umbra_Bookshelf_Helper
|
||||||
*/
|
*/
|
||||||
|
|
||||||
if (!defined('ABSPATH')) {
|
if (!defined('ABSPATH')) {
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
class BD_BookShelfHelper_Admin {
|
class Umbra_Bookshelf_Helper_Admin {
|
||||||
private static $instance = null;
|
private static $instance = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get instance
|
* Get instance
|
||||||
*
|
*
|
||||||
* @return BD_BookShelfHelper_Admin
|
* @return Umbra_Bookshelf_Helper_Admin
|
||||||
*/
|
*/
|
||||||
public static function instance() {
|
public static function instance() {
|
||||||
if (null === self::$instance) {
|
if (null === self::$instance) {
|
||||||
@@ -43,73 +43,68 @@ class BD_BookShelfHelper_Admin {
|
|||||||
* Add admin menu pages
|
* Add admin menu pages
|
||||||
*/
|
*/
|
||||||
public function add_menu_pages() {
|
public function add_menu_pages() {
|
||||||
add_menu_page(
|
// The top-level "Umbra Bookshelf" menu is registered once by the plugin
|
||||||
esc_html__('BD BookHelper', 'bd-bookshelfhelper'),
|
// bootstrap (Umbra_Bookshelf::register_admin_menu); this module only
|
||||||
esc_html__('BD BookHelper', 'bd-bookshelfhelper'),
|
// contributes submenus beneath it.
|
||||||
'edit_posts',
|
|
||||||
'bd-bookshelf',
|
|
||||||
'',
|
|
||||||
'dashicons-book-alt',
|
|
||||||
30
|
|
||||||
);
|
|
||||||
|
|
||||||
add_submenu_page(
|
add_submenu_page(
|
||||||
'bd-bookshelf',
|
'umbra-bookshelf',
|
||||||
esc_html__('Books', 'bd-bookshelfhelper'),
|
esc_html__('Books', 'umbra-bookshelf'),
|
||||||
esc_html__('All Books', 'bd-bookshelfhelper'),
|
esc_html__('Book Library', 'umbra-bookshelf'),
|
||||||
'edit_posts',
|
'edit_posts',
|
||||||
'edit.php?post_type=book'
|
'edit.php?post_type=book'
|
||||||
);
|
);
|
||||||
|
|
||||||
add_submenu_page(
|
add_submenu_page(
|
||||||
'bd-bookshelf',
|
'umbra-bookshelf',
|
||||||
esc_html__('Add Book', 'bd-bookshelfhelper'),
|
esc_html__('Add Book', 'umbra-bookshelf'),
|
||||||
esc_html__('Add Book', 'bd-bookshelfhelper'),
|
esc_html__('Add Book', 'umbra-bookshelf'),
|
||||||
'edit_posts',
|
'edit_posts',
|
||||||
'post-new.php?post_type=book'
|
'post-new.php?post_type=book'
|
||||||
);
|
);
|
||||||
|
|
||||||
add_submenu_page(
|
add_submenu_page(
|
||||||
'bd-bookshelf',
|
'umbra-bookshelf',
|
||||||
esc_html__('Book Data Entry', 'bd-bookshelfhelper'),
|
esc_html__('Book Data Entry', 'umbra-bookshelf'),
|
||||||
esc_html__('Data Entry', 'bd-bookshelfhelper'),
|
esc_html__('Data Entry', 'umbra-bookshelf'),
|
||||||
'edit_posts',
|
'edit_posts',
|
||||||
'bd-bookshelf-data-entry',
|
'umbra-bookshelf-data-entry',
|
||||||
array($this, 'data_entry_page')
|
array($this, 'data_entry_page')
|
||||||
);
|
);
|
||||||
|
|
||||||
add_submenu_page(
|
add_submenu_page(
|
||||||
'bd-bookshelf',
|
'umbra-bookshelf',
|
||||||
esc_html__('Series', 'bd-bookshelfhelper'),
|
esc_html__('Series', 'umbra-bookshelf'),
|
||||||
esc_html__('Series', 'bd-bookshelfhelper'),
|
esc_html__('Series Terms', 'umbra-bookshelf'),
|
||||||
'manage_categories',
|
'manage_categories',
|
||||||
'edit-tags.php?taxonomy=series&post_type=book'
|
'edit-tags.php?taxonomy=series&post_type=book'
|
||||||
);
|
);
|
||||||
|
|
||||||
add_submenu_page(
|
add_submenu_page(
|
||||||
'bd-bookshelf',
|
'umbra-bookshelf',
|
||||||
esc_html__('Pen Names', 'bd-bookshelfhelper'),
|
esc_html__('Pen Names', 'umbra-bookshelf'),
|
||||||
esc_html__('Pen Names', 'bd-bookshelfhelper'),
|
esc_html__('Pen Names', 'umbra-bookshelf'),
|
||||||
'manage_options',
|
'manage_options',
|
||||||
'bd-bookshelf-pen-names',
|
'umbra-bookshelf-pen-names',
|
||||||
array($this, 'pen_names_page')
|
array($this, 'pen_names_page')
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Distinct slug from Umbra_Admin's "umbra-bookshelf-settings": these are
|
||||||
|
// two different settings screens (library/publishing vs. catalogue).
|
||||||
add_submenu_page(
|
add_submenu_page(
|
||||||
'bd-bookshelf',
|
'umbra-bookshelf',
|
||||||
esc_html__('Settings', 'bd-bookshelfhelper'),
|
esc_html__('Library Settings', 'umbra-bookshelf'),
|
||||||
esc_html__('Settings', 'bd-bookshelfhelper'),
|
esc_html__('Library Settings', 'umbra-bookshelf'),
|
||||||
'manage_options',
|
'manage_options',
|
||||||
'bd-bookshelf-settings',
|
'umbra-bookshelf-library-settings',
|
||||||
array($this, 'settings_page')
|
array($this, 'settings_page')
|
||||||
);
|
);
|
||||||
|
|
||||||
add_submenu_page(
|
add_submenu_page(
|
||||||
'bd-bookshelf',
|
'umbra-bookshelf',
|
||||||
esc_html__('Migrate BD Books', 'bd-bookshelfhelper'),
|
esc_html__('Migrate Books', 'umbra-bookshelf'),
|
||||||
esc_html__('Migrate', 'bd-bookshelfhelper'),
|
esc_html__('Migrate', 'umbra-bookshelf'),
|
||||||
'manage_options',
|
'manage_options',
|
||||||
'bd-bookshelf-migrate',
|
'umbra-bookshelf-migrate',
|
||||||
array($this, 'migrate_page')
|
array($this, 'migrate_page')
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -126,23 +121,40 @@ class BD_BookShelfHelper_Admin {
|
|||||||
wp_enqueue_media();
|
wp_enqueue_media();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (strpos($hook, 'bd-bookshelf') !== false) {
|
// Scoped to this module's own submenu slugs. A bare strpos('umbra-bookshelf')
|
||||||
|
// would also match every books-module and email-module screen.
|
||||||
|
$library_hooks = array(
|
||||||
|
'umbra-bookshelf-data-entry',
|
||||||
|
'umbra-bookshelf-pen-names',
|
||||||
|
'umbra-bookshelf-library-settings',
|
||||||
|
'umbra-bookshelf-migrate',
|
||||||
|
);
|
||||||
|
|
||||||
|
$is_library_screen = false;
|
||||||
|
foreach ($library_hooks as $library_hook) {
|
||||||
|
if (strpos($hook, $library_hook) !== false) {
|
||||||
|
$is_library_screen = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($is_library_screen) {
|
||||||
wp_enqueue_style(
|
wp_enqueue_style(
|
||||||
'bd-bookshelf-admin',
|
'umbra-bookshelf-library-admin',
|
||||||
BD_BHS_URI . 'assets/css/admin.css',
|
UMBRA_BOOKSHELF_URL . 'library/assets/css/admin.css',
|
||||||
array(),
|
array(),
|
||||||
BD_BHS_VERSION
|
UMBRA_BOOKSHELF_LIBRARY_VERSION
|
||||||
);
|
);
|
||||||
|
|
||||||
wp_enqueue_script(
|
wp_enqueue_script(
|
||||||
'bd-bookshelf-admin',
|
'umbra-bookshelf-library-admin',
|
||||||
BD_BHS_URI . 'assets/js/admin.js',
|
UMBRA_BOOKSHELF_URL . 'library/assets/js/admin.js',
|
||||||
array('jquery'),
|
array('jquery'),
|
||||||
BD_BHS_VERSION,
|
UMBRA_BOOKSHELF_LIBRARY_VERSION,
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
|
|
||||||
wp_localize_script('bd-bookshelf-admin', 'bdmAdmin', array(
|
wp_localize_script('umbra-bookshelf-library-admin', 'umbraBookshelfLibraryAdmin', array(
|
||||||
'ajaxUrl' => admin_url('admin-ajax.php'),
|
'ajaxUrl' => admin_url('admin-ajax.php'),
|
||||||
'nonce' => wp_create_nonce('bd_bookshelf_admin'),
|
'nonce' => wp_create_nonce('bd_bookshelf_admin'),
|
||||||
));
|
));
|
||||||
@@ -155,7 +167,7 @@ class BD_BookShelfHelper_Admin {
|
|||||||
public function add_meta_boxes() {
|
public function add_meta_boxes() {
|
||||||
add_meta_box(
|
add_meta_box(
|
||||||
'bd_book_details',
|
'bd_book_details',
|
||||||
esc_html__('Book Details', 'bd-bookshelfhelper'),
|
esc_html__('Book Details', 'umbra-bookshelf'),
|
||||||
array($this, 'book_details_meta_box'),
|
array($this, 'book_details_meta_box'),
|
||||||
'book',
|
'book',
|
||||||
'normal',
|
'normal',
|
||||||
@@ -164,7 +176,7 @@ class BD_BookShelfHelper_Admin {
|
|||||||
|
|
||||||
add_meta_box(
|
add_meta_box(
|
||||||
'bd_book_seo',
|
'bd_book_seo',
|
||||||
esc_html__('SEO Settings', 'bd-bookshelfhelper'),
|
esc_html__('SEO Settings', 'umbra-bookshelf'),
|
||||||
array($this, 'book_seo_meta_box'),
|
array($this, 'book_seo_meta_box'),
|
||||||
'book',
|
'book',
|
||||||
'side',
|
'side',
|
||||||
@@ -178,19 +190,19 @@ class BD_BookShelfHelper_Admin {
|
|||||||
* @param WP_Post $post Post object.
|
* @param WP_Post $post Post object.
|
||||||
*/
|
*/
|
||||||
public function book_details_meta_box($post) {
|
public function book_details_meta_box($post) {
|
||||||
$languages = BD_BookShelfHelper::get_supported_languages();
|
$languages = Umbra_Bookshelf_Helper::get_supported_languages();
|
||||||
|
|
||||||
wp_nonce_field('bd_book_meta', 'bd_book_nonce');
|
wp_nonce_field('bd_book_meta', 'bd_book_nonce');
|
||||||
|
|
||||||
echo '<div class="bd-book-meta-box">';
|
echo '<div class="bd-book-meta-box">';
|
||||||
|
|
||||||
// Core Fields
|
// Core Fields
|
||||||
echo '<h3>' . esc_html__('Core Fields', 'bd-bookshelfhelper') . '</h3>';
|
echo '<h3>' . esc_html__('Core Fields', 'umbra-bookshelf') . '</h3>';
|
||||||
|
|
||||||
// Pen Name
|
// Pen Name
|
||||||
echo '<p><label>' . esc_html__('Pen Name', 'bd-bookshelfhelper') . '</label>';
|
echo '<p><label>' . esc_html__('Pen Name', 'umbra-bookshelf') . '</label>';
|
||||||
echo '<select name="pen_name_id">';
|
echo '<select name="pen_name_id">';
|
||||||
echo '<option value="">' . esc_html__('B.D. Murphy (Default)', 'bd-bookshelfhelper') . '</option>';
|
echo '<option value="">' . esc_html__('B.D. Murphy (Default)', 'umbra-bookshelf') . '</option>';
|
||||||
|
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
$table_name = $wpdb->prefix . 'bd_pen_names';
|
$table_name = $wpdb->prefix . 'bd_pen_names';
|
||||||
@@ -205,24 +217,24 @@ class BD_BookShelfHelper_Admin {
|
|||||||
echo '</select></p>';
|
echo '</select></p>';
|
||||||
|
|
||||||
// ASIN and ISBN
|
// ASIN and ISBN
|
||||||
echo '<p><label>' . esc_html__('ASIN (Amazon)', 'bd-bookshelfhelper') . '</label>';
|
echo '<p><label>' . esc_html__('ASIN (Amazon)', 'umbra-bookshelf') . '</label>';
|
||||||
echo '<input type="text" name="asin" value="' . esc_attr(get_post_meta($post->ID, 'asin', true)) . '" class="regular-text"></p>';
|
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 '<p><label>' . esc_html__('ISBN-13 (Print)', 'umbra-bookshelf') . '</label>';
|
||||||
echo '<input type="text" name="isbn" value="' . esc_attr(get_post_meta($post->ID, 'isbn', true)) . '" class="regular-text"></p>';
|
echo '<input type="text" name="isbn" value="' . esc_attr(get_post_meta($post->ID, 'isbn', true)) . '" class="regular-text"></p>';
|
||||||
|
|
||||||
// Short Link
|
// Short Link
|
||||||
echo '<p><label>' . esc_html__('Short Link Slug', 'bd-bookshelfhelper') . '</label>';
|
echo '<p><label>' . esc_html__('Short Link Slug', 'umbra-bookshelf') . '</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 '<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>';
|
echo '<p class="description">' . esc_html__('Auto-generated if empty. E.g., "ph" for "Pandemic Hacker"', 'umbra-bookshelf') . '</p></p>';
|
||||||
|
|
||||||
// Series
|
// Series
|
||||||
$series_terms = get_terms(array('taxonomy' => 'series', 'hide_empty' => false));
|
$series_terms = get_terms(array('taxonomy' => 'series', 'hide_empty' => false));
|
||||||
|
|
||||||
if ($series_terms) {
|
if ($series_terms) {
|
||||||
echo '<p><label>' . esc_html__('Series', 'bd-bookshelfhelper') . '</label>';
|
echo '<p><label>' . esc_html__('Series', 'umbra-bookshelf') . '</label>';
|
||||||
echo '<select name="series_id">';
|
echo '<select name="series_id">';
|
||||||
echo '<option value="">' . esc_html__('None / Standalone', 'bd-bookshelfhelper') . '</option>';
|
echo '<option value="">' . esc_html__('None / Standalone', 'umbra-bookshelf') . '</option>';
|
||||||
|
|
||||||
$current_series = get_the_terms($post->ID, 'series');
|
$current_series = get_the_terms($post->ID, 'series');
|
||||||
$current_series_id = ($current_series && !is_wp_error($current_series)) ? $current_series[0]->term_id : 0;
|
$current_series_id = ($current_series && !is_wp_error($current_series)) ? $current_series[0]->term_id : 0;
|
||||||
@@ -233,33 +245,33 @@ class BD_BookShelfHelper_Admin {
|
|||||||
echo '</select></p>';
|
echo '</select></p>';
|
||||||
|
|
||||||
if ($current_series_id) {
|
if ($current_series_id) {
|
||||||
echo '<p><label>' . esc_html__('Volume Number', 'bd-bookshelfhelper') . '</label>';
|
echo '<p><label>' . esc_html__('Volume Number', 'umbra-bookshelf') . '</label>';
|
||||||
echo '<input type="number" name="volume_number" value="' . esc_attr(get_post_meta($post->ID, 'volume_number', true)) . '" min="1"></p>';
|
echo '<input type="number" name="volume_number" value="' . esc_attr(get_post_meta($post->ID, 'volume_number', true)) . '" min="1"></p>';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Language Content
|
// Language Content
|
||||||
echo '<h3>' . esc_html__('Language Content', 'bd-bookshelfhelper') . '</h3>';
|
echo '<h3>' . esc_html__('Language Content', 'umbra-bookshelf') . '</h3>';
|
||||||
|
|
||||||
foreach ($languages as $code => $name) {
|
foreach ($languages as $code => $name) {
|
||||||
echo '<fieldset class="bd-lang-fieldset">';
|
echo '<fieldset class="bd-lang-fieldset">';
|
||||||
echo '<legend>' . esc_html($name) . ' (' . strtoupper($code) . ')</legend>';
|
echo '<legend>' . esc_html($name) . ' (' . strtoupper($code) . ')</legend>';
|
||||||
|
|
||||||
echo '<p><label>' . esc_html__('Title', 'bd-bookshelfhelper') . '</label>';
|
echo '<p><label>' . esc_html__('Title', 'umbra-bookshelf') . '</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 '<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 '<p><label>' . esc_html__('Back Cover Blurb', 'umbra-bookshelf') . '</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 '<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 '<p><label>' . esc_html__('Preview Excerpt', 'umbra-bookshelf') . '</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 '<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>';
|
echo '<p><label>' . esc_html__('Pull Quotes (1-5)', 'umbra-bookshelf') . '</label>';
|
||||||
for ($i = 1; $i <= 5; $i++) {
|
for ($i = 1; $i <= 5; $i++) {
|
||||||
printf('<input type="text" name="pull_quote_%s_%d" value="%s" class="large-text" placeholder="%s">',
|
printf('<input type="text" name="pull_quote_%s_%d" value="%s" class="large-text" placeholder="%s">',
|
||||||
esc_attr($code), $i,
|
esc_attr($code), $i,
|
||||||
esc_attr(get_post_meta($post->ID, 'pull_quote_' . $code . '_' . $i, true)),
|
esc_attr(get_post_meta($post->ID, 'pull_quote_' . $code . '_' . $i, true)),
|
||||||
esc_attr__('Quote ' . $i, 'bd-bookshelfhelper')
|
esc_attr__('Quote ' . $i, 'umbra-bookshelf')
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
echo '</p>';
|
echo '</p>';
|
||||||
@@ -268,7 +280,7 @@ class BD_BookShelfHelper_Admin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Retailer Links
|
// Retailer Links
|
||||||
echo '<h3>' . esc_html__('Retailer Links', 'bd-bookshelfhelper') . '</h3>';
|
echo '<h3>' . esc_html__('Retailer Links', 'umbra-bookshelf') . '</h3>';
|
||||||
|
|
||||||
$retailer_fields = array(
|
$retailer_fields = array(
|
||||||
'ebook_url_amazon' => 'Amazon (Ebook)',
|
'ebook_url_amazon' => 'Amazon (Ebook)',
|
||||||
@@ -287,12 +299,12 @@ class BD_BookShelfHelper_Admin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Ratings
|
// Ratings
|
||||||
echo '<h3>' . esc_html__('Ratings', 'bd-bookshelfhelper') . '</h3>';
|
echo '<h3>' . esc_html__('Ratings', 'umbra-bookshelf') . '</h3>';
|
||||||
|
|
||||||
echo '<p><label>' . esc_html__('Amazon Rating', 'bd-bookshelfhelper') . '</label>';
|
echo '<p><label>' . esc_html__('Amazon Rating', 'umbra-bookshelf') . '</label>';
|
||||||
echo '<input type="text" name="amazon_rating" value="' . esc_attr(get_post_meta($post->ID, 'amazon_rating', true)) . '" placeholder="4.5"></p>';
|
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 '<p><label>' . esc_html__('Goodreads Rating', 'umbra-bookshelf') . '</label>';
|
||||||
echo '<input type="text" name="goodreads_rating" value="' . esc_attr(get_post_meta($post->ID, 'goodreads_rating', true)) . '" placeholder="4.2"></p>';
|
echo '<input type="text" name="goodreads_rating" value="' . esc_attr(get_post_meta($post->ID, 'goodreads_rating', true)) . '" placeholder="4.2"></p>';
|
||||||
|
|
||||||
echo '</div>';
|
echo '</div>';
|
||||||
@@ -304,10 +316,10 @@ class BD_BookShelfHelper_Admin {
|
|||||||
* @param WP_Post $post Post object.
|
* @param WP_Post $post Post object.
|
||||||
*/
|
*/
|
||||||
public function book_seo_meta_box($post) {
|
public function book_seo_meta_box($post) {
|
||||||
echo '<p><label>' . esc_html__('SEO Title', 'bd-bookshelfhelper') . '</label>';
|
echo '<p><label>' . esc_html__('SEO Title', 'umbra-bookshelf') . '</label>';
|
||||||
echo '<input type="text" name="seo_title" value="' . esc_attr(get_post_meta($post->ID, 'seo_title', true)) . '" class="large-text"></p>';
|
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 '<p><label>' . esc_html__('SEO Description', 'umbra-bookshelf') . '</label>';
|
||||||
echo '<textarea name="seo_description" rows="4" class="large-text">' . esc_textarea(get_post_meta($post->ID, 'seo_description', true)) . '</textarea></p>';
|
echo '<textarea name="seo_description" rows="4" class="large-text">' . esc_textarea(get_post_meta($post->ID, 'seo_description', true)) . '</textarea></p>';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -316,7 +328,7 @@ class BD_BookShelfHelper_Admin {
|
|||||||
*/
|
*/
|
||||||
public function data_entry_page() {
|
public function data_entry_page() {
|
||||||
$post_id = isset($_GET['post_id']) ? absint($_GET['post_id']) : 0;
|
$post_id = isset($_GET['post_id']) ? absint($_GET['post_id']) : 0;
|
||||||
$languages = BD_BookShelfHelper::get_supported_languages();
|
$languages = Umbra_Bookshelf_Helper::get_supported_languages();
|
||||||
|
|
||||||
// Get all books for selection
|
// Get all books for selection
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
@@ -327,9 +339,9 @@ class BD_BookShelfHelper_Admin {
|
|||||||
// If no post_id provided and no books exist, show selection
|
// If no post_id provided and no books exist, show selection
|
||||||
if (!$post_id && empty($books)) {
|
if (!$post_id && empty($books)) {
|
||||||
echo '<div class="wrap">';
|
echo '<div class="wrap">';
|
||||||
echo '<h1>' . esc_html__('Book Data Entry', 'bd-bookshelfhelper') . '</h1>';
|
echo '<h1>' . esc_html__('Book Data Entry', 'umbra-bookshelf') . '</h1>';
|
||||||
echo '<div class="bd-status-message warning">' . esc_html__('No books found. Please create a book first.', 'bd-bookshelfhelper') . '</div>';
|
echo '<div class="bd-status-message warning">' . esc_html__('No books found. Please create a book first.', 'umbra-bookshelf') . '</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 '<p><a href="' . esc_url(admin_url('post-new.php?post_type=book')) . '" class="button button-primary">' . esc_html__('Create New Book', 'umbra-bookshelf') . '</a></p>';
|
||||||
echo '</div>';
|
echo '</div>';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -342,18 +354,18 @@ class BD_BookShelfHelper_Admin {
|
|||||||
// If still no post_id, show selection
|
// If still no post_id, show selection
|
||||||
if (!$post_id) {
|
if (!$post_id) {
|
||||||
echo '<div class="wrap">';
|
echo '<div class="wrap">';
|
||||||
echo '<h1>' . esc_html__('Book Data Entry', 'bd-bookshelfhelper') . '</h1>';
|
echo '<h1>' . esc_html__('Book Data Entry', 'umbra-bookshelf') . '</h1>';
|
||||||
echo '<form method="post">';
|
echo '<form method="post">';
|
||||||
echo '<table class="form-table">';
|
echo '<table class="form-table">';
|
||||||
echo '<tr><th>' . esc_html__('Select Book', 'bd-bookshelfhelper') . '</th>';
|
echo '<tr><th>' . esc_html__('Select Book', 'umbra-bookshelf') . '</th>';
|
||||||
echo '<td><select name="selected_book_id">';
|
echo '<td><select name="selected_book_id">';
|
||||||
echo '<option value="">' . esc_html__('Select a book...', 'bd-bookshelfhelper') . '</option>';
|
echo '<option value="">' . esc_html__('Select a book...', 'umbra-bookshelf') . '</option>';
|
||||||
foreach ($books as $book) {
|
foreach ($books as $book) {
|
||||||
echo '<option value="' . esc_attr($book->ID) . '">' . esc_html($book->post_title) . '</option>';
|
echo '<option value="' . esc_attr($book->ID) . '">' . esc_html($book->post_title) . '</option>';
|
||||||
}
|
}
|
||||||
echo '</select></td></tr>';
|
echo '</select></td></tr>';
|
||||||
echo '</table>';
|
echo '</table>';
|
||||||
submit_button(esc_html__('Select Book', 'bd-bookshelfhelper'), 'primary', 'bd_select_book');
|
submit_button(esc_html__('Select Book', 'umbra-bookshelf'), 'primary', 'bd_select_book');
|
||||||
echo '</form>';
|
echo '</form>';
|
||||||
echo '</div>';
|
echo '</div>';
|
||||||
return;
|
return;
|
||||||
@@ -362,7 +374,7 @@ class BD_BookShelfHelper_Admin {
|
|||||||
$post = get_post($post_id);
|
$post = get_post($post_id);
|
||||||
if (!$post || 'book' !== $post->post_type) {
|
if (!$post || 'book' !== $post->post_type) {
|
||||||
echo '<div class="wrap">';
|
echo '<div class="wrap">';
|
||||||
echo '<div class="bd-status-message error">' . esc_html__('Invalid book selected.', 'bd-bookshelfhelper') . '</div>';
|
echo '<div class="bd-status-message error">' . esc_html__('Invalid book selected.', 'umbra-bookshelf') . '</div>';
|
||||||
echo '</div>';
|
echo '</div>';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -370,7 +382,7 @@ class BD_BookShelfHelper_Admin {
|
|||||||
wp_nonce_field('bd_data_entry', 'bd_data_entry_nonce');
|
wp_nonce_field('bd_data_entry', 'bd_data_entry_nonce');
|
||||||
|
|
||||||
echo '<div class="wrap">';
|
echo '<div class="wrap">';
|
||||||
echo '<h1>' . sprintf(esc_html__('Data Entry: %s', 'bd-bookshelfhelper'), esc_html($post->post_title)) . '</h1>';
|
echo '<h1>' . sprintf(esc_html__('Data Entry: %s', 'umbra-bookshelf'), esc_html($post->post_title)) . '</h1>';
|
||||||
|
|
||||||
// Language tabs
|
// Language tabs
|
||||||
echo '<div class="bd-language-tabs">';
|
echo '<div class="bd-language-tabs">';
|
||||||
@@ -389,20 +401,20 @@ class BD_BookShelfHelper_Admin {
|
|||||||
wp_nonce_field('bd_data_entry_save', 'bd_data_entry_save_nonce');
|
wp_nonce_field('bd_data_entry_save', 'bd_data_entry_save_nonce');
|
||||||
|
|
||||||
// Section 1: Basic Information
|
// Section 1: Basic Information
|
||||||
$this->render_section_header('basic', __('Basic Information', 'bd-bookshelfhelper'), true);
|
$this->render_section_header('basic', __('Basic Information', 'umbra-bookshelf'), true);
|
||||||
echo '<div class="bd-section-content" id="bd-section-basic">';
|
echo '<div class="bd-section-content" id="bd-section-basic">';
|
||||||
|
|
||||||
echo '<div class="bd-field-row">';
|
echo '<div class="bd-field-row">';
|
||||||
echo '<div class="bd-field required">';
|
echo '<div class="bd-field required">';
|
||||||
echo '<label for="post_title">' . esc_html__('Book Title', 'bd-bookshelfhelper') . '</label>';
|
echo '<label for="post_title">' . esc_html__('Book Title', 'umbra-bookshelf') . '</label>';
|
||||||
echo '<input type="text" id="post_title" name="post_title" value="' . esc_attr($post->post_title) . '" class="large-text" required>';
|
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 '<span class="bd-hint">' . esc_html__('The full title of the book as it appears on the cover.', 'umbra-bookshelf') . '</span>';
|
||||||
echo '</div>';
|
echo '</div>';
|
||||||
|
|
||||||
echo '<div class="bd-field">';
|
echo '<div class="bd-field">';
|
||||||
echo '<label for="pen_name_id">' . esc_html__('Pen Name', 'bd-bookshelfhelper') . '</label>';
|
echo '<label for="pen_name_id">' . esc_html__('Pen Name', 'umbra-bookshelf') . '</label>';
|
||||||
echo '<select id="pen_name_id" name="pen_name_id">';
|
echo '<select id="pen_name_id" name="pen_name_id">';
|
||||||
echo '<option value="">' . esc_html__('B.D. Murphy (Default)', 'bd-bookshelfhelper') . '</option>';
|
echo '<option value="">' . esc_html__('B.D. Murphy (Default)', 'umbra-bookshelf') . '</option>';
|
||||||
|
|
||||||
$pen_names = $wpdb->get_results("SELECT id, pen_name FROM {$wpdb->prefix}bd_pen_names WHERE active = 1 ORDER BY pen_name");
|
$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);
|
$pen_name_id = get_post_meta($post_id, 'pen_name_id', true);
|
||||||
@@ -413,55 +425,55 @@ class BD_BookShelfHelper_Admin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
echo '</select>';
|
echo '</select>';
|
||||||
echo '<span class="bd-hint">' . esc_html__('The author name to display with this book.', 'bd-bookshelfhelper') . '</span>';
|
echo '<span class="bd-hint">' . esc_html__('The author name to display with this book.', 'umbra-bookshelf') . '</span>';
|
||||||
echo '</div>';
|
echo '</div>';
|
||||||
echo '</div>';
|
echo '</div>';
|
||||||
|
|
||||||
echo '<div class="bd-field-row">';
|
echo '<div class="bd-field-row">';
|
||||||
echo '<div class="bd-field">';
|
echo '<div class="bd-field">';
|
||||||
echo '<label for="publish_date">' . esc_html__('Publication Date', 'bd-bookshelfhelper') . '</label>';
|
echo '<label for="publish_date">' . esc_html__('Publication Date', 'umbra-bookshelf') . '</label>';
|
||||||
echo '<input type="date" id="publish_date" name="publish_date" value="' . esc_attr(get_post_meta($post_id, 'publish_date', true)) . '">';
|
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 '<span class="bd-hint">' . esc_html__('Format: YYYY-MM-DD (e.g., 2024-01-15)', 'umbra-bookshelf') . '</span>';
|
||||||
echo '</div>';
|
echo '</div>';
|
||||||
|
|
||||||
echo '<div class="bd-field">';
|
echo '<div class="bd-field">';
|
||||||
echo '<label for="sort_order">' . esc_html__('Sort Order', 'bd-bookshelfhelper') . '</label>';
|
echo '<label for="sort_order">' . esc_html__('Sort Order', 'umbra-bookshelf') . '</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 '<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 '<span class="bd-hint">' . esc_html__('Manual sort position (lower numbers appear first).', 'umbra-bookshelf') . '</span>';
|
||||||
echo '</div>';
|
echo '</div>';
|
||||||
echo '</div>';
|
echo '</div>';
|
||||||
|
|
||||||
echo '</div>'; // End Basic Information
|
echo '</div>'; // End Basic Information
|
||||||
|
|
||||||
// Section 2: Identifiers
|
// Section 2: Identifiers
|
||||||
$this->render_section_header('identifiers', __('Identifiers', 'bd-bookshelfhelper'), true);
|
$this->render_section_header('identifiers', __('Identifiers', 'umbra-bookshelf'), true);
|
||||||
echo '<div class="bd-section-content" id="bd-section-identifiers">';
|
echo '<div class="bd-section-content" id="bd-section-identifiers">';
|
||||||
|
|
||||||
echo '<div class="bd-field-row">';
|
echo '<div class="bd-field-row">';
|
||||||
echo '<div class="bd-field">';
|
echo '<div class="bd-field">';
|
||||||
echo '<label for="isbn">' . esc_html__('ISBN-13 (Print)', 'bd-bookshelfhelper') . '</label>';
|
echo '<label for="isbn">' . esc_html__('ISBN-13 (Print)', 'umbra-bookshelf') . '</label>';
|
||||||
echo '<input type="text" id="isbn" name="isbn" value="' . esc_attr(get_post_meta($post_id, 'isbn', true)) . '" placeholder="9781234567890">';
|
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 '<span class="bd-hint">' . esc_html__('13 digits, no dashes (e.g., 9781234567890)', 'umbra-bookshelf') . '</span>';
|
||||||
echo '</div>';
|
echo '</div>';
|
||||||
|
|
||||||
echo '<div class="bd-field">';
|
echo '<div class="bd-field">';
|
||||||
echo '<label for="asin">' . esc_html__('ASIN (Amazon)', 'bd-bookshelfhelper') . '</label>';
|
echo '<label for="asin">' . esc_html__('ASIN (Amazon)', 'umbra-bookshelf') . '</label>';
|
||||||
echo '<input type="text" id="asin" name="asin" value="' . esc_attr(get_post_meta($post_id, 'asin', true)) . '" placeholder="B001234567">';
|
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 '<span class="bd-hint">' . esc_html__('10 alphanumeric characters (e.g., B001234567)', 'umbra-bookshelf') . '</span>';
|
||||||
echo '</div>';
|
echo '</div>';
|
||||||
echo '</div>';
|
echo '</div>';
|
||||||
|
|
||||||
echo '</div>'; // End Identifiers
|
echo '</div>'; // End Identifiers
|
||||||
|
|
||||||
// Section 3: Series & Order
|
// Section 3: Series & Order
|
||||||
$this->render_section_header('series', __('Series & Order', 'bd-bookshelfhelper'), true);
|
$this->render_section_header('series', __('Series & Order', 'umbra-bookshelf'), true);
|
||||||
echo '<div class="bd-section-content" id="bd-section-series">';
|
echo '<div class="bd-section-content" id="bd-section-series">';
|
||||||
|
|
||||||
$series_terms = get_terms(array('taxonomy' => 'series', 'hide_empty' => false));
|
$series_terms = get_terms(array('taxonomy' => 'series', 'hide_empty' => false));
|
||||||
echo '<div class="bd-field">';
|
echo '<div class="bd-field">';
|
||||||
echo '<label for="series_id">' . esc_html__('Series', 'bd-bookshelfhelper') . '</label>';
|
echo '<label for="series_id">' . esc_html__('Series', 'umbra-bookshelf') . '</label>';
|
||||||
echo '<select id="series_id" name="series_id">';
|
echo '<select id="series_id" name="series_id">';
|
||||||
echo '<option value="">' . esc_html__('None / Standalone', 'bd-bookshelfhelper') . '</option>';
|
echo '<option value="">' . esc_html__('None / Standalone', 'umbra-bookshelf') . '</option>';
|
||||||
|
|
||||||
$current_series = get_the_terms($post_id, 'series');
|
$current_series = get_the_terms($post_id, 'series');
|
||||||
$current_series_id = ($current_series && !is_wp_error($current_series)) ? $current_series[0]->term_id : 0;
|
$current_series_id = ($current_series && !is_wp_error($current_series)) ? $current_series[0]->term_id : 0;
|
||||||
@@ -472,36 +484,36 @@ class BD_BookShelfHelper_Admin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
echo '</select>';
|
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 '<span class="bd-hint">' . esc_html__('Select the series this book belongs to, or leave blank for standalone.', 'umbra-bookshelf') . '</span>';
|
||||||
echo '</div>';
|
echo '</div>';
|
||||||
|
|
||||||
echo '<div class="bd-field">';
|
echo '<div class="bd-field">';
|
||||||
echo '<label for="volume_number">' . esc_html__('Volume Number', 'bd-bookshelfhelper') . '</label>';
|
echo '<label for="volume_number">' . esc_html__('Volume Number', 'umbra-bookshelf') . '</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 '<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 '<span class="bd-hint">' . esc_html__('Order within the series (e.g., 1 for Book 1).', 'umbra-bookshelf') . '</span>';
|
||||||
echo '</div>';
|
echo '</div>';
|
||||||
|
|
||||||
echo '</div>'; // End Series
|
echo '</div>'; // End Series
|
||||||
|
|
||||||
// Section 4: Short Link
|
// Section 4: Short Link
|
||||||
$this->render_section_header('shortlink', __('Short Link', 'bd-bookshelfhelper'), false);
|
$this->render_section_header('shortlink', __('Short Link', 'umbra-bookshelf'), false);
|
||||||
echo '<div class="bd-section-content" id="bd-section-shortlink">';
|
echo '<div class="bd-section-content" id="bd-section-shortlink">';
|
||||||
|
|
||||||
echo '<div class="bd-field">';
|
echo '<div class="bd-field">';
|
||||||
echo '<label for="short_link_slug">' . esc_html__('Short Link Slug', 'bd-bookshelfhelper') . '</label>';
|
echo '<label for="short_link_slug">' . esc_html__('Short Link Slug', 'umbra-bookshelf') . '</label>';
|
||||||
$current_slug = get_post_meta($post_id, 'short_link_slug', true);
|
$current_slug = get_post_meta($post_id, 'short_link_slug', true);
|
||||||
if (empty($current_slug)) {
|
if (empty($current_slug)) {
|
||||||
$current_slug = $this->generate_short_slug($post->post_title);
|
$current_slug = $this->generate_short_slug($post->post_title);
|
||||||
}
|
}
|
||||||
echo '<code>' . esc_url(home_url('/')) . '</code>';
|
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 '<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 '<span class="bd-hint">' . esc_html__('Auto-generated from title if empty. E.g., "ph" for "Pandemic Hacker"', 'umbra-bookshelf') . '</span>';
|
||||||
echo '</div>';
|
echo '</div>';
|
||||||
|
|
||||||
echo '</div>'; // End Short Link
|
echo '</div>'; // End Short Link
|
||||||
|
|
||||||
// Section 5: Language Content (tabs for each language)
|
// Section 5: Language Content (tabs for each language)
|
||||||
$this->render_section_header('content', __('Content (by Language)', 'bd-bookshelfhelper'), true);
|
$this->render_section_header('content', __('Content (by Language)', 'umbra-bookshelf'), true);
|
||||||
echo '<div class="bd-section-content" id="bd-section-content">';
|
echo '<div class="bd-section-content" id="bd-section-content">';
|
||||||
|
|
||||||
foreach ($languages as $code => $name) {
|
foreach ($languages as $code => $name) {
|
||||||
@@ -509,48 +521,48 @@ class BD_BookShelfHelper_Admin {
|
|||||||
echo '<h3>' . esc_html($name) . ' (' . strtoupper($code) . ')</h3>';
|
echo '<h3>' . esc_html($name) . ' (' . strtoupper($code) . ')</h3>';
|
||||||
|
|
||||||
echo '<div class="bd-field">';
|
echo '<div class="bd-field">';
|
||||||
echo '<label for="title_' . esc_attr($code) . '">' . esc_html__('Title', 'bd-bookshelfhelper') . '</label>';
|
echo '<label for="title_' . esc_attr($code) . '">' . esc_html__('Title', 'umbra-bookshelf') . '</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 '<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 '<span class="bd-hint">' . esc_html__('Title in this language (if different from default).', 'umbra-bookshelf') . '</span>';
|
||||||
echo '</div>';
|
echo '</div>';
|
||||||
|
|
||||||
echo '<div class="bd-field">';
|
echo '<div class="bd-field">';
|
||||||
echo '<label for="blurb_' . esc_attr($code) . '">' . esc_html__('Back Cover Blurb', 'bd-bookshelfhelper') . '</label>';
|
echo '<label for="blurb_' . esc_attr($code) . '">' . esc_html__('Back Cover Blurb', 'umbra-bookshelf') . '</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 '<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 '<span class="bd-hint">' . esc_html__('The book description shown on the back cover and product pages.', 'umbra-bookshelf') . '</span>';
|
||||||
echo '</div>';
|
echo '</div>';
|
||||||
|
|
||||||
echo '<div class="bd-field">';
|
echo '<div class="bd-field">';
|
||||||
echo '<label for="preview_excerpt_' . esc_attr($code) . '">' . esc_html__('Preview Excerpt', 'bd-bookshelfhelper') . '</label>';
|
echo '<label for="preview_excerpt_' . esc_attr($code) . '">' . esc_html__('Preview Excerpt', 'umbra-bookshelf') . '</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 '<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 '<span class="bd-hint">' . esc_html__('A short excerpt (50-100 words) for previews and "Look Inside" feature.', 'umbra-bookshelf') . '</span>';
|
||||||
echo '</div>';
|
echo '</div>';
|
||||||
|
|
||||||
echo '<div class="bd-field">';
|
echo '<div class="bd-field">';
|
||||||
echo '<label>' . esc_html__('Pull Quotes (up to 5)', 'bd-bookshelfhelper') . '</label>';
|
echo '<label>' . esc_html__('Pull Quotes (up to 5)', 'umbra-bookshelf') . '</label>';
|
||||||
echo '<div class="bd-pull-quotes">';
|
echo '<div class="bd-pull-quotes">';
|
||||||
for ($i = 1; $i <= 5; $i++) {
|
for ($i = 1; $i <= 5; $i++) {
|
||||||
printf('<input type="text" name="pull_quote_%s_%d" value="%s" placeholder="%s">',
|
printf('<input type="text" name="pull_quote_%s_%d" value="%s" placeholder="%s">',
|
||||||
esc_attr($code), $i,
|
esc_attr($code), $i,
|
||||||
esc_attr(get_post_meta($post_id, 'pull_quote_' . $code . '_' . $i, true)),
|
esc_attr(get_post_meta($post_id, 'pull_quote_' . $code . '_' . $i, true)),
|
||||||
esc_attr(sprintf(__('Quote %d', 'bd-bookshelfhelper'), $i))
|
esc_attr(sprintf(__('Quote %d', 'umbra-bookshelf'), $i))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
echo '</div>';
|
echo '</div>';
|
||||||
echo '<span class="bd-hint">' . esc_html__('Notable quotes or reviews to display on the book page.', 'bd-bookshelfhelper') . '</span>';
|
echo '<span class="bd-hint">' . esc_html__('Notable quotes or reviews to display on the book page.', 'umbra-bookshelf') . '</span>';
|
||||||
echo '</div>';
|
echo '</div>';
|
||||||
|
|
||||||
echo '<div class="bd-field-row">';
|
echo '<div class="bd-field-row">';
|
||||||
echo '<div class="bd-field">';
|
echo '<div class="bd-field">';
|
||||||
echo '<label for="genre_tags_' . esc_attr($code) . '">' . esc_html__('Genre Tags', 'bd-bookshelfhelper') . '</label>';
|
echo '<label for="genre_tags_' . esc_attr($code) . '">' . esc_html__('Genre Tags', 'umbra-bookshelf') . '</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 '<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 '<span class="bd-hint">' . esc_html__('Comma-separated (e.g., Thriller, Sci-Fi, Mystery) - minimum 3 recommended', 'umbra-bookshelf') . '</span>';
|
||||||
echo '</div>';
|
echo '</div>';
|
||||||
|
|
||||||
echo '<div class="bd-field">';
|
echo '<div class="bd-field">';
|
||||||
echo '<label for="amazon_search_terms_' . esc_attr($code) . '">' . esc_html__('Amazon Search Terms', 'bd-bookshelfhelper') . '</label>';
|
echo '<label for="amazon_search_terms_' . esc_attr($code) . '">' . esc_html__('Amazon Search Terms', 'umbra-bookshelf') . '</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 '<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 '<span class="bd-hint">' . esc_html__('Keywords for Amazon search optimization, comma-separated.', 'umbra-bookshelf') . '</span>';
|
||||||
echo '</div>';
|
echo '</div>';
|
||||||
echo '</div>';
|
echo '</div>';
|
||||||
|
|
||||||
@@ -560,7 +572,7 @@ class BD_BookShelfHelper_Admin {
|
|||||||
echo '</div>'; // End Content
|
echo '</div>'; // End Content
|
||||||
|
|
||||||
// Section 6: Retailer Links
|
// Section 6: Retailer Links
|
||||||
$this->render_section_header('retailers', __('Retailer Links', 'bd-bookshelfhelper'), true);
|
$this->render_section_header('retailers', __('Retailer Links', 'umbra-bookshelf'), true);
|
||||||
echo '<div class="bd-section-content" id="bd-section-retailers">';
|
echo '<div class="bd-section-content" id="bd-section-retailers">';
|
||||||
|
|
||||||
echo '<div class="bd-retailer-grid">';
|
echo '<div class="bd-retailer-grid">';
|
||||||
@@ -578,9 +590,9 @@ class BD_BookShelfHelper_Admin {
|
|||||||
|
|
||||||
foreach ($retailers as $field => $info) {
|
foreach ($retailers as $field => $info) {
|
||||||
echo '<div class="bd-field">';
|
echo '<div class="bd-field">';
|
||||||
echo '<label for="' . esc_attr($field) . '">' . esc_html($info['label'], 'bd-bookshelfhelper') . '</label>';
|
echo '<label for="' . esc_attr($field) . '">' . esc_html($info['label'], 'umbra-bookshelf') . '</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 '<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 '<span class="bd-hint">' . esc_html($info['hint'], 'umbra-bookshelf') . '</span>';
|
||||||
echo '</div>';
|
echo '</div>';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -588,13 +600,13 @@ class BD_BookShelfHelper_Admin {
|
|||||||
echo '</div>'; // End Retailer Links
|
echo '</div>'; // End Retailer Links
|
||||||
|
|
||||||
// Section 7: Reviews
|
// Section 7: Reviews
|
||||||
$this->render_section_header('reviews', __('Reviews', 'bd-bookshelfhelper'), false);
|
$this->render_section_header('reviews', __('Reviews', 'umbra-bookshelf'), false);
|
||||||
echo '<div class="bd-section-content" id="bd-section-reviews">';
|
echo '<div class="bd-section-content" id="bd-section-reviews">';
|
||||||
|
|
||||||
$reviews_table = $wpdb->prefix . 'bd_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));
|
$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>';
|
echo '<p>' . esc_html__('Reviews are managed via the Reviews tab on the book editing screen.', 'umbra-bookshelf') . '</p>';
|
||||||
|
|
||||||
if ($reviews) {
|
if ($reviews) {
|
||||||
echo '<table class="widefat">';
|
echo '<table class="widefat">';
|
||||||
@@ -613,48 +625,48 @@ class BD_BookShelfHelper_Admin {
|
|||||||
echo '</div>'; // End Reviews
|
echo '</div>'; // End Reviews
|
||||||
|
|
||||||
// Section 8: Ratings
|
// Section 8: Ratings
|
||||||
$this->render_section_header('ratings', __('Ratings', 'bd-bookshelfhelper'), false);
|
$this->render_section_header('ratings', __('Ratings', 'umbra-bookshelf'), false);
|
||||||
echo '<div class="bd-section-content" id="bd-section-ratings">';
|
echo '<div class="bd-section-content" id="bd-section-ratings">';
|
||||||
|
|
||||||
echo '<div class="bd-field-row">';
|
echo '<div class="bd-field-row">';
|
||||||
echo '<div class="bd-field">';
|
echo '<div class="bd-field">';
|
||||||
echo '<label for="amazon_rating">' . esc_html__('Amazon Rating', 'bd-bookshelfhelper') . '</label>';
|
echo '<label for="amazon_rating">' . esc_html__('Amazon Rating', 'umbra-bookshelf') . '</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 '<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 '<span class="bd-hint">' . esc_html__('Average rating out of 5 (e.g., 4.5)', 'umbra-bookshelf') . '</span>';
|
||||||
echo '</div>';
|
echo '</div>';
|
||||||
|
|
||||||
echo '<div class="bd-field">';
|
echo '<div class="bd-field">';
|
||||||
echo '<label for="goodreads_rating">' . esc_html__('Goodreads Rating', 'bd-bookshelfhelper') . '</label>';
|
echo '<label for="goodreads_rating">' . esc_html__('Goodreads Rating', 'umbra-bookshelf') . '</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 '<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 '<span class="bd-hint">' . esc_html__('Average rating out of 5 from Goodreads.', 'umbra-bookshelf') . '</span>';
|
||||||
echo '</div>';
|
echo '</div>';
|
||||||
echo '</div>';
|
echo '</div>';
|
||||||
|
|
||||||
echo '</div>'; // End Ratings
|
echo '</div>'; // End Ratings
|
||||||
|
|
||||||
// Section 9: SEO
|
// Section 9: SEO
|
||||||
$this->render_section_header('seo', __('SEO Settings', 'bd-bookshelfhelper'), false);
|
$this->render_section_header('seo', __('SEO Settings', 'umbra-bookshelf'), false);
|
||||||
echo '<div class="bd-section-content" id="bd-section-seo">';
|
echo '<div class="bd-section-content" id="bd-section-seo">';
|
||||||
|
|
||||||
echo '<div class="bd-field">';
|
echo '<div class="bd-field">';
|
||||||
echo '<label for="seo_title">' . esc_html__('SEO Title', 'bd-bookshelfhelper') . '</label>';
|
echo '<label for="seo_title">' . esc_html__('SEO Title', 'umbra-bookshelf') . '</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 '<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 '<span class="bd-hint">' . esc_html__('Custom title for search engines (defaults to book title if empty).', 'umbra-bookshelf') . '</span>';
|
||||||
echo '</div>';
|
echo '</div>';
|
||||||
|
|
||||||
echo '<div class="bd-field">';
|
echo '<div class="bd-field">';
|
||||||
echo '<label for="seo_description">' . esc_html__('SEO Description', 'bd-bookshelfhelper') . '</label>';
|
echo '<label for="seo_description">' . esc_html__('SEO Description', 'umbra-bookshelf') . '</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 '<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 '<span class="bd-hint">' . esc_html__('Meta description for search results (150-160 characters recommended).', 'umbra-bookshelf') . '</span>';
|
||||||
echo '</div>';
|
echo '</div>';
|
||||||
|
|
||||||
echo '</div>'; // End SEO
|
echo '</div>'; // End SEO
|
||||||
|
|
||||||
// Action buttons
|
// Action buttons
|
||||||
echo '<div class="bd-data-entry-actions">';
|
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>';
|
echo '<a href="' . esc_url(admin_url('post.php?post=' . $post_id . '&action=edit')) . '" class="button">' . esc_html__('Edit in Standard Editor', 'umbra-bookshelf') . '</a>';
|
||||||
submit_button(esc_html__('Save Book Data', 'bd-bookshelfhelper'), 'primary large', 'bd_save_data_entry', false);
|
submit_button(esc_html__('Save Book Data', 'umbra-bookshelf'), '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 '<button type="button" id="bd-preview-btn" class="button button-large">' . esc_html__('Preview', 'umbra-bookshelf') . '</button>';
|
||||||
echo '</div>';
|
echo '</div>';
|
||||||
|
|
||||||
echo '</form>';
|
echo '</form>';
|
||||||
@@ -685,12 +697,12 @@ class BD_BookShelfHelper_Admin {
|
|||||||
*/
|
*/
|
||||||
private function save_data_entry($post_id) {
|
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')) {
|
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>';
|
echo '<div class="bd-status-message error">' . esc_html__('Security check failed.', 'umbra-bookshelf') . '</div>';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!current_user_can('edit_post', $post_id)) {
|
if (!current_user_can('edit_post', $post_id)) {
|
||||||
echo '<div class="bd-status-message error">' . esc_html__('Permission denied.', 'bd-bookshelfhelper') . '</div>';
|
echo '<div class="bd-status-message error">' . esc_html__('Permission denied.', 'umbra-bookshelf') . '</div>';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -724,7 +736,7 @@ class BD_BookShelfHelper_Admin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Language content
|
// Language content
|
||||||
$languages = BD_BookShelfHelper::get_supported_languages();
|
$languages = Umbra_Bookshelf_Helper::get_supported_languages();
|
||||||
foreach ($languages as $code => $name) {
|
foreach ($languages as $code => $name) {
|
||||||
$title_key = 'title_' . $code;
|
$title_key = 'title_' . $code;
|
||||||
$blurb_key = 'blurb_' . $code;
|
$blurb_key = 'blurb_' . $code;
|
||||||
@@ -769,7 +781,7 @@ class BD_BookShelfHelper_Admin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
echo '<div class="bd-status-message success">' . esc_html__('Book data saved successfully!', 'bd-bookshelfhelper') . '</div>';
|
echo '<div class="bd-status-message success">' . esc_html__('Book data saved successfully!', 'umbra-bookshelf') . '</div>';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -815,7 +827,7 @@ class BD_BookShelfHelper_Admin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Save language content
|
// Save language content
|
||||||
$languages = BD_BookShelfHelper::get_supported_languages();
|
$languages = Umbra_Bookshelf_Helper::get_supported_languages();
|
||||||
foreach ($languages as $code => $name) {
|
foreach ($languages as $code => $name) {
|
||||||
$title_key = 'title_' . $code;
|
$title_key = 'title_' . $code;
|
||||||
$blurb_key = 'blurb_' . $code;
|
$blurb_key = 'blurb_' . $code;
|
||||||
@@ -882,7 +894,7 @@ class BD_BookShelfHelper_Admin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$languages = BD_BookShelfHelper::get_supported_languages();
|
$languages = Umbra_Bookshelf_Helper::get_supported_languages();
|
||||||
|
|
||||||
foreach ($languages as $code => $name) {
|
foreach ($languages as $code => $name) {
|
||||||
$title_key = 'title_' . $code;
|
$title_key = 'title_' . $code;
|
||||||
@@ -996,7 +1008,7 @@ class BD_BookShelfHelper_Admin {
|
|||||||
'active' => 1,
|
'active' => 1,
|
||||||
'created_at' => current_time('mysql'),
|
'created_at' => current_time('mysql'),
|
||||||
));
|
));
|
||||||
echo '<div class="updated"><p>' . esc_html__('Pen name added.', 'bd-bookshelfhelper') . '</p></div>';
|
echo '<div class="updated"><p>' . esc_html__('Pen name added.', 'umbra-bookshelf') . '</p></div>';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1014,24 +1026,24 @@ class BD_BookShelfHelper_Admin {
|
|||||||
$pen_names = $wpdb->get_results("SELECT * FROM {$table_name} ORDER BY pen_name");
|
$pen_names = $wpdb->get_results("SELECT * FROM {$table_name} ORDER BY pen_name");
|
||||||
|
|
||||||
echo '<div class="wrap">';
|
echo '<div class="wrap">';
|
||||||
echo '<h1>' . esc_html__('Pen Names', 'bd-bookshelfhelper') . '</h1>';
|
echo '<h1>' . esc_html__('Pen Names', 'umbra-bookshelf') . '</h1>';
|
||||||
|
|
||||||
// Add new form
|
// Add new form
|
||||||
echo '<form method="post">';
|
echo '<form method="post">';
|
||||||
wp_nonce_field('bd_add_pen_name');
|
wp_nonce_field('bd_add_pen_name');
|
||||||
echo '<h2>' . esc_html__('Add New Pen Name', 'bd-bookshelfhelper') . '</h2>';
|
echo '<h2>' . esc_html__('Add New Pen Name', 'umbra-bookshelf') . '</h2>';
|
||||||
echo '<table class="form-table">';
|
echo '<table class="form-table">';
|
||||||
echo '<tr><th>' . esc_html__('Pen Name', 'bd-bookshelfhelper') . '</th>';
|
echo '<tr><th>' . esc_html__('Pen Name', 'umbra-bookshelf') . '</th>';
|
||||||
echo '<td><input type="text" name="new_pen_name" class="regular-text" required>';
|
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 '<p class="description">' . esc_html__('e.g., J.K. Murphy', 'umbra-bookshelf') . '</p></td></tr>';
|
||||||
echo '<tr><th>' . esc_html__('Notes', 'bd-bookshelfhelper') . '</th>';
|
echo '<tr><th>' . esc_html__('Notes', 'umbra-bookshelf') . '</th>';
|
||||||
echo '<td><textarea name="new_notes" rows="3" class="regular-text"></textarea></td></tr>';
|
echo '<td><textarea name="new_notes" rows="3" class="regular-text"></textarea></td></tr>';
|
||||||
echo '</table>';
|
echo '</table>';
|
||||||
submit_button(esc_html__('Add Pen Name', 'bd-bookshelfhelper'), 'primary', 'bd_add_pen_name');
|
submit_button(esc_html__('Add Pen Name', 'umbra-bookshelf'), 'primary', 'bd_add_pen_name');
|
||||||
echo '</form>';
|
echo '</form>';
|
||||||
|
|
||||||
// Existing list
|
// Existing list
|
||||||
echo '<h2>' . esc_html__('Existing Pen Names', 'bd-bookshelfhelper') . '</h2>';
|
echo '<h2>' . esc_html__('Existing Pen Names', 'umbra-bookshelf') . '</h2>';
|
||||||
echo '<table class="widefat">';
|
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 '<thead><tr><th>ID</th><th>Pen Name</th><th>Notes</th><th>Status</th><th>Actions</th></tr></thead>';
|
||||||
echo '<tbody>';
|
echo '<tbody>';
|
||||||
@@ -1042,21 +1054,21 @@ class BD_BookShelfHelper_Admin {
|
|||||||
printf('<td>%d</td>', esc_html($pn->id));
|
printf('<td>%d</td>', esc_html($pn->id));
|
||||||
printf('<td>%s</td>', esc_html($pn->pen_name));
|
printf('<td>%s</td>', esc_html($pn->pen_name));
|
||||||
printf('<td>%s</td>', esc_html($pn->notes));
|
printf('<td>%s</td>', esc_html($pn->notes));
|
||||||
printf('<td>%s</td>', $pn->active ? esc_html__('Active', 'bd-bookshelfhelper') : esc_html__('Inactive', 'bd-bookshelfhelper'));
|
printf('<td>%s</td>', $pn->active ? esc_html__('Active', 'umbra-bookshelf') : esc_html__('Inactive', 'umbra-bookshelf'));
|
||||||
echo '<td>';
|
echo '<td>';
|
||||||
printf('<form method="post" style="display:inline;">%s', wp_nonce_field('bd_update_pen_status', '_wpnonce', true, false));
|
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="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));
|
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);
|
submit_button(esc_html__('Update', 'umbra-bookshelf'), 'secondary', 'bd_update_pen_status', false);
|
||||||
echo '</form> ';
|
echo '</form> ';
|
||||||
printf('<form method="post" style="display:inline;">%s', wp_nonce_field('bd_delete_pen_name', '_wpnonce', true, false));
|
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));
|
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?");'));
|
submit_button(esc_html__('Delete', 'umbra-bookshelf'), 'delete', 'bd_delete_pen_name', false, array('onclick' => 'return confirm("Delete this pen name?");'));
|
||||||
echo '</form>';
|
echo '</form>';
|
||||||
echo '</td></tr>';
|
echo '</td></tr>';
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
echo '<tr><td colspan="5">' . esc_html__('No pen names found.', 'bd-bookshelfhelper') . '</td></tr>';
|
echo '<tr><td colspan="5">' . esc_html__('No pen names found.', 'umbra-bookshelf') . '</td></tr>';
|
||||||
}
|
}
|
||||||
|
|
||||||
echo '</tbody></table></div>';
|
echo '</tbody></table></div>';
|
||||||
@@ -1081,20 +1093,20 @@ class BD_BookShelfHelper_Admin {
|
|||||||
$goodreads_id = sanitize_text_field($_POST['goodreads_author_id'] ?? '');
|
$goodreads_id = sanitize_text_field($_POST['goodreads_author_id'] ?? '');
|
||||||
update_option('bdm_goodreads_author_id', $goodreads_id);
|
update_option('bdm_goodreads_author_id', $goodreads_id);
|
||||||
|
|
||||||
echo '<div class="updated"><p>' . esc_html__('Settings saved.', 'bd-bookshelfhelper') . '</p></div>';
|
echo '<div class="updated"><p>' . esc_html__('Settings saved.', 'umbra-bookshelf') . '</p></div>';
|
||||||
}
|
}
|
||||||
|
|
||||||
$affiliates = get_option('bdm_affiliate_codes', array());
|
$affiliates = get_option('bdm_affiliate_codes', array());
|
||||||
$goodreads_id = get_option('bdm_goodreads_author_id', '30469651');
|
$goodreads_id = get_option('bdm_goodreads_author_id', '30469651');
|
||||||
|
|
||||||
echo '<div class="wrap">';
|
echo '<div class="wrap">';
|
||||||
echo '<h1>' . esc_html__('BD BookHelper Settings', 'bd-bookshelfhelper') . '</h1>';
|
echo '<h1>' . esc_html__('BD BookHelper Settings', 'umbra-bookshelf') . '</h1>';
|
||||||
|
|
||||||
echo '<form method="post">';
|
echo '<form method="post">';
|
||||||
wp_nonce_field('bd_settings');
|
wp_nonce_field('bd_settings');
|
||||||
|
|
||||||
echo '<h2>' . esc_html__('Affiliate Codes', 'bd-bookshelfhelper') . '</h2>';
|
echo '<h2>' . esc_html__('Affiliate Codes', 'umbra-bookshelf') . '</h2>';
|
||||||
echo '<p>' . esc_html__('Enter your affiliate IDs for each retailer. Links include these IDs when available.', 'bd-bookshelfhelper') . '</p>';
|
echo '<p>' . esc_html__('Enter your affiliate IDs for each retailer. Links include these IDs when available.', 'umbra-bookshelf') . '</p>';
|
||||||
|
|
||||||
echo '<table class="form-table">';
|
echo '<table class="form-table">';
|
||||||
$affiliate_fields = array(
|
$affiliate_fields = array(
|
||||||
@@ -1113,17 +1125,17 @@ class BD_BookShelfHelper_Admin {
|
|||||||
}
|
}
|
||||||
echo '</table>';
|
echo '</table>';
|
||||||
|
|
||||||
echo '<h2>' . esc_html__('Goodreads Integration', 'bd-bookshelfhelper') . '</h2>';
|
echo '<h2>' . esc_html__('Goodreads Integration', 'umbra-bookshelf') . '</h2>';
|
||||||
echo '<table class="form-table">';
|
echo '<table class="form-table">';
|
||||||
printf('<tr><th>' . esc_html__('Goodreads Author ID', 'bd-bookshelfhelper') . '</th>');
|
printf('<tr><th>' . esc_html__('Goodreads Author ID', 'umbra-bookshelf') . '</th>');
|
||||||
printf('<td><input type="text" name="goodreads_author_id" value="%s" class="regular-text">', esc_attr($goodreads_id));
|
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 '<p class="description">' . esc_html__('Default: 30469651 (B_D_Murphy)', 'umbra-bookshelf') . '</p></td></tr>';
|
||||||
echo '</table>';
|
echo '</table>';
|
||||||
|
|
||||||
echo '<h2>' . esc_html__('Amazon Ratings Warning', 'bd-bookshelfhelper') . '</h2>';
|
echo '<h2>' . esc_html__('Amazon Ratings Warning', 'umbra-bookshelf') . '</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>';
|
echo '<p style="color:#d63638;">' . esc_html__('Warning: Scraping Amazon product pages may conflict with their Terms of Service. Use at your own risk.', 'umbra-bookshelf') . '</p>';
|
||||||
|
|
||||||
submit_button(esc_html__('Save Settings', 'bd-bookshelfhelper'), 'primary', 'bd_save_settings');
|
submit_button(esc_html__('Save Settings', 'umbra-bookshelf'), 'primary', 'bd_save_settings');
|
||||||
echo '</form></div>';
|
echo '</form></div>';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1137,7 +1149,7 @@ class BD_BookShelfHelper_Admin {
|
|||||||
$table_exists = $wpdb->get_var("SHOW TABLES LIKE '$old_table'") === $old_table;
|
$table_exists = $wpdb->get_var("SHOW TABLES LIKE '$old_table'") === $old_table;
|
||||||
|
|
||||||
if (isset($_POST['bd_run_migration']) && check_admin_referer('bd_migrate_nonce')) {
|
if (isset($_POST['bd_run_migration']) && check_admin_referer('bd_migrate_nonce')) {
|
||||||
$result = BD_BookShelfHelper::migrate_from_bd_books();
|
$result = Umbra_Bookshelf_Helper::migrate_from_bd_books();
|
||||||
if ($result['success']) {
|
if ($result['success']) {
|
||||||
echo '<div class="updated"><p>' . esc_html($result['message']) . '</p></div>';
|
echo '<div class="updated"><p>' . esc_html($result['message']) . '</p></div>';
|
||||||
} else {
|
} else {
|
||||||
@@ -1146,13 +1158,13 @@ class BD_BookShelfHelper_Admin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
echo '<div class="wrap">';
|
echo '<div class="wrap">';
|
||||||
echo '<h1>' . esc_html__('Migrate from BD Books', 'bd-bookshelfhelper') . '</h1>';
|
echo '<h1>' . esc_html__('Migrate from BD Books', 'umbra-bookshelf') . '</h1>';
|
||||||
|
|
||||||
if (!$table_exists) {
|
if (!$table_exists) {
|
||||||
echo '<div class="notice notice-warning">';
|
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 '<p>' . esc_html__('The old BD Books table was not found. No migration needed - the plugin data does not exist.', 'umbra-bookshelf') . '</p>';
|
||||||
echo '</div>';
|
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 '<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.', 'umbra-bookshelf') . '</p>';
|
||||||
echo '</div>';
|
echo '</div>';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1167,12 +1179,12 @@ class BD_BookShelfHelper_Admin {
|
|||||||
));
|
));
|
||||||
|
|
||||||
echo '<div class="notice notice-info">';
|
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__('Found %d books in the old BD Books table.', 'umbra-bookshelf'), $old_count) . '</p>';
|
||||||
echo '<p>' . sprintf(esc_html__('Current BookHelper has %d books.', 'bd-bookshelfhelper'), count($new_books)) . '</p>';
|
echo '<p>' . sprintf(esc_html__('Current BookHelper has %d books.', 'umbra-bookshelf'), count($new_books)) . '</p>';
|
||||||
echo '</div>';
|
echo '</div>';
|
||||||
|
|
||||||
if ($old_count > 0) {
|
if ($old_count > 0) {
|
||||||
echo '<h2>' . esc_html__('Books to Migrate', 'bd-bookshelfhelper') . '</h2>';
|
echo '<h2>' . esc_html__('Books to Migrate', 'umbra-bookshelf') . '</h2>';
|
||||||
echo '<table class="widefat">';
|
echo '<table class="widefat">';
|
||||||
echo '<thead><tr><th>Title</th><th>Author</th><th>Cover</th></tr></thead>';
|
echo '<thead><tr><th>Title</th><th>Author</th><th>Cover</th></tr></thead>';
|
||||||
echo '<tbody>';
|
echo '<tbody>';
|
||||||
@@ -1181,25 +1193,25 @@ class BD_BookShelfHelper_Admin {
|
|||||||
echo '<tr>';
|
echo '<tr>';
|
||||||
echo '<td>' . esc_html($book->title) . '</td>';
|
echo '<td>' . esc_html($book->title) . '</td>';
|
||||||
echo '<td>' . esc_html($book->author) . '</td>';
|
echo '<td>' . esc_html($book->author) . '</td>';
|
||||||
echo '<td>' . ($has_cover ? esc_html__('Yes', 'bd-bookshelfhelper') : esc_html__('No', 'bd-bookshelfhelper')) . '</td>';
|
echo '<td>' . ($has_cover ? esc_html__('Yes', 'umbra-bookshelf') : esc_html__('No', 'umbra-bookshelf')) . '</td>';
|
||||||
echo '</tr>';
|
echo '</tr>';
|
||||||
}
|
}
|
||||||
echo '</tbody></table>';
|
echo '</tbody></table>';
|
||||||
|
|
||||||
echo '<p style="margin-top: 20px;"><strong>' . esc_html__('What gets migrated:', 'bd-bookshelfhelper') . '</strong></p>';
|
echo '<p style="margin-top: 20px;"><strong>' . esc_html__('What gets migrated:', 'umbra-bookshelf') . '</strong></p>';
|
||||||
echo '<ul>';
|
echo '<ul>';
|
||||||
echo '<li>' . esc_html__('Book titles and authors (as pen names)', 'bd-bookshelfhelper') . '</li>';
|
echo '<li>' . esc_html__('Book titles and authors (as pen names)', 'umbra-bookshelf') . '</li>';
|
||||||
echo '<li>' . esc_html__('Descriptions become blurbs in English', 'bd-bookshelfhelper') . '</li>';
|
echo '<li>' . esc_html__('Descriptions become blurbs in English', 'umbra-bookshelf') . '</li>';
|
||||||
echo '<li>' . esc_html__('Cover images (WordPress media or external URLs)', 'bd-bookshelfhelper') . '</li>';
|
echo '<li>' . esc_html__('Cover images (WordPress media or external URLs)', 'umbra-bookshelf') . '</li>';
|
||||||
echo '<li>' . esc_html__('Purchase links to retailer fields', 'bd-bookshelfhelper') . '</li>';
|
echo '<li>' . esc_html__('Purchase links to retailer fields', 'umbra-bookshelf') . '</li>';
|
||||||
echo '</ul>';
|
echo '</ul>';
|
||||||
|
|
||||||
echo '<form method="post" style="margin-top: 20px;">';
|
echo '<form method="post" style="margin-top: 20px;">';
|
||||||
wp_nonce_field('bd_migrate_nonce');
|
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 '<input type="submit" name="bd_run_migration" class="button button-primary" value="' . esc_attr__('Run Migration', 'umbra-bookshelf') . '" onclick="return confirm(\'' . esc_js(__('This will create new book entries for any books not already migrated. Continue?', 'umbra-bookshelf')) . '\');">';
|
||||||
echo '</form>';
|
echo '</form>';
|
||||||
} else {
|
} else {
|
||||||
echo '<p>' . esc_html__('No books found in the old table to migrate.', 'bd-bookshelfhelper') . '</p>';
|
echo '<p>' . esc_html__('No books found in the old table to migrate.', 'umbra-bookshelf') . '</p>';
|
||||||
}
|
}
|
||||||
|
|
||||||
echo '</div>';
|
echo '</div>';
|
||||||
@@ -1213,6 +1225,6 @@ class BD_BookShelfHelper_Admin {
|
|||||||
|
|
||||||
$book_id = absint($_POST['book_id']);
|
$book_id = absint($_POST['book_id']);
|
||||||
|
|
||||||
wp_send_json_success(array('message' => esc_html__('Links updated.', 'bd-bookshelfhelper')));
|
wp_send_json_success(array('message' => esc_html__('Links updated.', 'umbra-bookshelf')));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5,14 +5,14 @@
|
|||||||
/**
|
/**
|
||||||
* Installation and Database Setup
|
* Installation and Database Setup
|
||||||
*
|
*
|
||||||
* @package BD_BookShelfHelper
|
* @package Umbra_Bookshelf_Helper
|
||||||
*/
|
*/
|
||||||
|
|
||||||
if (!defined('ABSPATH')) {
|
if (!defined('ABSPATH')) {
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
class BD_BookShelfHelper_Install {
|
class Umbra_Bookshelf_Helper_Install {
|
||||||
/**
|
/**
|
||||||
* Activate plugin
|
* Activate plugin
|
||||||
*/
|
*/
|
||||||
@@ -153,7 +153,7 @@ class BD_BookShelfHelper_Install {
|
|||||||
* Create roles
|
* Create roles
|
||||||
*/
|
*/
|
||||||
private static function create_roles() {
|
private static function create_roles() {
|
||||||
add_role('book_manager', esc_html__('Book Manager', 'bd-bookshelfhelper'), array(
|
add_role('book_manager', esc_html__('Book Manager', 'umbra-bookshelf'), array(
|
||||||
'read' => true,
|
'read' => true,
|
||||||
'edit_books' => true,
|
'edit_books' => true,
|
||||||
'edit_others_books' => true,
|
'edit_others_books' => true,
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* BD Bookshelf - Public Styles
|
* BD Bookshelf - Public Styles
|
||||||
*
|
*
|
||||||
* @package BD_Bookshelf
|
* @package Umbra_Bookshelf
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* Sell Grid */
|
/* Sell Grid */
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
/**
|
/**
|
||||||
* BD Bookshelf - GDPR Consent
|
* BD Bookshelf - GDPR Consent
|
||||||
*
|
*
|
||||||
* @package BD_Bookshelf
|
* @package Umbra_Bookshelf
|
||||||
*/
|
*/
|
||||||
|
|
||||||
(function($) {
|
(function($) {
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
|
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
// Check if consent already given
|
// Check if consent already given
|
||||||
if (bdBookshelfHasConsent()) {
|
if (umbraBookshelfHasConsent()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -22,16 +22,16 @@
|
|||||||
// Handle accept button
|
// Handle accept button
|
||||||
$('#bd-bookshelf-gdpr-accept').on('click', function() {
|
$('#bd-bookshelf-gdpr-accept').on('click', function() {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: bdBookshelfGdpr.ajaxurl,
|
url: umbraBookshelfGdpr.ajaxurl,
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
data: {
|
data: {
|
||||||
action: 'bd_bookshelf_accept_consent',
|
action: 'bd_bookshelf_accept_consent',
|
||||||
nonce: bdBookshelfGdpr.nonce
|
nonce: umbraBookshelfGdpr.nonce
|
||||||
},
|
},
|
||||||
success: function(response) {
|
success: function(response) {
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
// Set cookie
|
// Set cookie
|
||||||
bdBookshelfSetCookie('bd_bookshelf_consent', '1', bdBookshelfGdpr.cookieDays);
|
umbraBookshelfSetCookie('bd_bookshelf_consent', '1', umbraBookshelfGdpr.cookieDays);
|
||||||
|
|
||||||
// Hide banner
|
// Hide banner
|
||||||
$('#bd-bookshelf-gdpr-banner').fadeOut(300);
|
$('#bd-bookshelf-gdpr-banner').fadeOut(300);
|
||||||
@@ -41,18 +41,18 @@
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function bdBookshelfHasConsent() {
|
function umbraBookshelfHasConsent() {
|
||||||
return bdBookshelfGetCookie('bd_bookshelf_consent') === '1';
|
return umbraBookshelfGetCookie('bd_bookshelf_consent') === '1';
|
||||||
}
|
}
|
||||||
|
|
||||||
function bdBookshelfSetCookie(name, value, days) {
|
function umbraBookshelfSetCookie(name, value, days) {
|
||||||
var date = new Date();
|
var date = new Date();
|
||||||
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
|
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
|
||||||
var expires = "expires=" + date.toUTCString();
|
var expires = "expires=" + date.toUTCString();
|
||||||
document.cookie = name + "=" + value + ";" + expires + ";path=/";
|
document.cookie = name + "=" + value + ";" + expires + ";path=/";
|
||||||
}
|
}
|
||||||
|
|
||||||
function bdBookshelfGetCookie(name) {
|
function umbraBookshelfGetCookie(name) {
|
||||||
var nameEQ = name + "=";
|
var nameEQ = name + "=";
|
||||||
var ca = document.cookie.split(';');
|
var ca = document.cookie.split(';');
|
||||||
for (var i = 0; i < ca.length; i++) {
|
for (var i = 0; i < ca.length; i++) {
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
/**
|
/**
|
||||||
* BD Bookshelf - Public JavaScript
|
* BD Bookshelf - Public JavaScript
|
||||||
*
|
*
|
||||||
* @package BD_Bookshelf
|
* @package Umbra_Bookshelf
|
||||||
*/
|
*/
|
||||||
|
|
||||||
(function($) {
|
(function($) {
|
||||||
81
umbra-bookshelf/readme.txt
Normal file
81
umbra-bookshelf/readme.txt
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
=== Umbra Bookshelf ===
|
||||||
|
Contributors: umbratools
|
||||||
|
Tags: books, series, isbn, authors, newsletter, publishing
|
||||||
|
Requires at least: 6.0
|
||||||
|
Tested up to: 6.6
|
||||||
|
Requires PHP: 7.4
|
||||||
|
Stable tag: 1.0.0
|
||||||
|
License: Apache-2.0
|
||||||
|
License URI: https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Complete book management for authors and publishers: catalogue and ISBN tracking, series, pen names, retail links, rating sync, GDPR consent, and newsletter delivery.
|
||||||
|
|
||||||
|
== Description ==
|
||||||
|
|
||||||
|
Umbra Bookshelf is a single plugin covering the full book workflow. It
|
||||||
|
consolidates three previously separate plugins:
|
||||||
|
|
||||||
|
* **Books** (formerly BD Bookshelf) — the catalogue: ISBN/ASIN variants,
|
||||||
|
author records, series, retail sites and per-format buy links, Goodreads and
|
||||||
|
Amazon rating synchronisation, SEO rows, shortlink click tracking, GDPR
|
||||||
|
consent banners, and CSV/post import.
|
||||||
|
* **Library** (formerly BD BookShelfHelper) — the WordPress-native model: the
|
||||||
|
`book` post type and `series` taxonomy, pen names, a focused data-entry
|
||||||
|
screen, shortlinks, reviews and ratings, plus a migration screen.
|
||||||
|
* **Email** (formerly BD SimpleEmail) — newsletter delivery: subscribers and
|
||||||
|
newsletters, a `[bdm_signup]` shortcode, signup and unsubscribe handling via
|
||||||
|
AJAX and `admin_post`, Gemini-assisted composition, and Gmail/SMTP sending.
|
||||||
|
|
||||||
|
Everything now sits under a single **Umbra Bookshelf** admin menu.
|
||||||
|
|
||||||
|
= Upgrading from the old plugins =
|
||||||
|
|
||||||
|
Umbra Bookshelf reads and writes the same database tables, option keys, post
|
||||||
|
meta, cron hooks and shortcodes as the three plugins it replaces. Existing data
|
||||||
|
is picked up as-is; no migration is required.
|
||||||
|
|
||||||
|
Install Umbra Bookshelf, then deactivate BD Bookshelf, BD BookShelfHelper and
|
||||||
|
BD SimpleEmail. **Do not delete** them from the Plugins page first — deleting
|
||||||
|
runs their uninstall routines, which drop your tables and options.
|
||||||
|
|
||||||
|
The shortcodes `[bd-book]`, `[bd-books]` and `[bdm_signup]` are unchanged, so
|
||||||
|
existing posts keep working. The admin menu locations have changed: see the
|
||||||
|
specification in `spec/SPEC_Umbra_Bookshelf_Plugin.md` for the full menu map.
|
||||||
|
|
||||||
|
== Frequently Asked Questions ==
|
||||||
|
|
||||||
|
= Do I need to migrate my books? =
|
||||||
|
|
||||||
|
No. Table names, option keys, post meta keys and shortcodes are deliberately
|
||||||
|
unchanged. Deactivate the old plugins rather than deleting them.
|
||||||
|
|
||||||
|
= The admin menu looks different. =
|
||||||
|
|
||||||
|
It is one menu now instead of three. The catalogue, library and email screens
|
||||||
|
are all still present as submenus, and Settings is split into catalogue
|
||||||
|
settings and library settings.
|
||||||
|
|
||||||
|
= Are there known problems? =
|
||||||
|
|
||||||
|
Yes — see `docs/KNOWN-ISSUES.md`. The most significant is that the catalogue and
|
||||||
|
library modules both create `bd_books` and `bd_series` with different schemas,
|
||||||
|
inherited from the old plugins. If you ran more than one of them together, check
|
||||||
|
that screen before relying on it.
|
||||||
|
|
||||||
|
== Changelog ==
|
||||||
|
|
||||||
|
= 1.0.0 =
|
||||||
|
* Initial release of the consolidated plugin.
|
||||||
|
* Merged BD Bookshelf, BD BookShelfHelper and BD SimpleEmail into one plugin
|
||||||
|
under a single admin menu.
|
||||||
|
* Renamed plugin headers, text domains, classes, constants, asset handles and
|
||||||
|
admin slugs to the Umbra Bookshelf identity.
|
||||||
|
* Preserved all database tables, option keys, post meta, cron hooks, nonces and
|
||||||
|
shortcodes, so existing installs need no migration.
|
||||||
|
* Registered the top-level admin menu once instead of three times.
|
||||||
|
* Fixed a duplicate-class fatal error between the catalogue's theme-compatibility
|
||||||
|
shim and the library module's helper class.
|
||||||
|
* Fixed duplicate AJAX handler, duplicate Settings submenu, and colliding asset
|
||||||
|
handles across modules.
|
||||||
|
* Added `spec/SPEC_Umbra_Bookshelf_Plugin.md`, `docs/KNOWN-ISSUES.md`,
|
||||||
|
`docs/ARCHITECTURE.md`, and the `tests/` verification suite.
|
||||||
224
umbra-bookshelf/spec/SPEC_Umbra_Bookshelf_Plugin.md
Normal file
224
umbra-bookshelf/spec/SPEC_Umbra_Bookshelf_Plugin.md
Normal file
@@ -0,0 +1,224 @@
|
|||||||
|
# Umbra Bookshelf — Plugin Specification
|
||||||
|
|
||||||
|
**Status:** Active
|
||||||
|
**Version:** 1.0.0
|
||||||
|
**Supersedes:** `spec/archive/SPEC_BD_Bookshelf_Plugin.txt`, `spec/archive/SPEC_BD_BookHelper_Plugin.txt`
|
||||||
|
**License:** Apache-2.0
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Identity
|
||||||
|
|
||||||
|
| Field | Value |
|
||||||
|
|---|---|
|
||||||
|
| Plugin name | Umbra Bookshelf |
|
||||||
|
| Slug / folder | `umbra-bookshelf` |
|
||||||
|
| Entry point | `umbra-bookshelf.php` |
|
||||||
|
| Text domain | `umbra-bookshelf` |
|
||||||
|
| Domain path | `/languages` |
|
||||||
|
| Author | Umbra Tools |
|
||||||
|
| Requires | WordPress 6.0, PHP 7.4 |
|
||||||
|
| Version | 1.0.0 |
|
||||||
|
|
||||||
|
The three pre-merge plugins map onto modules as follows:
|
||||||
|
|
||||||
|
| Former plugin | Former version | Module | Code location |
|
||||||
|
|---|---|---|---|
|
||||||
|
| BD Bookshelf | 0.1.0 | **Books** (catalogue) | `includes/books/` |
|
||||||
|
| BD BookShelfHelper | 2.0.0 | **Library** | `includes/library/` |
|
||||||
|
| BD SimpleEmail | 2.0.0 | **Email** | `includes/email/` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Naming rules
|
||||||
|
|
||||||
|
Two categories of identifier, treated differently on purpose.
|
||||||
|
|
||||||
|
### 2.1 Renamed to the Umbra Bookshelf identity
|
||||||
|
|
||||||
|
* Plugin header fields (`Plugin Name`, `Plugin URI`, `Author`, `Author URI`, `License`).
|
||||||
|
* Text domain: `bd-bookshelf`, `bd-bookshelfhelper`, `bd-simpleemail` → `umbra-bookshelf`.
|
||||||
|
* PHP classes: `BD_Bookshelf`, `BDM_*`, `BD_BookShelfHelper*`, `BD_SimpleEmail*`,
|
||||||
|
`BD_Email_Sender` → `Umbra_*`.
|
||||||
|
* Constants: `BD_BOOKSHELF_*` → `UMBRA_BOOKSHELF_*`; `BD_BHS_VERSION` →
|
||||||
|
`UMBRA_BOOKSHELF_LIBRARY_VERSION`; `BD_EMAIL_*` → `UMBRA_BOOKSHELF_EMAIL_VERSION`.
|
||||||
|
`BD_BHS_DIR` / `BD_EMAIL_URI` were removed in favour of the single
|
||||||
|
`UMBRA_BOOKSHELF_DIR` / `UMBRA_BOOKSHELF_URL` pair.
|
||||||
|
* Asset handles, one per module so they cannot shadow each other:
|
||||||
|
`umbra-bookshelf-admin`, `umbra-bookshelf-library-admin`,
|
||||||
|
`umbra-bookshelf-email-admin`, `umbra-bookshelf-gdpr`.
|
||||||
|
* JavaScript globals: `bdBookshelfAdmin` → `umbraBookshelfAdmin`,
|
||||||
|
`bdBookshelfGdpr` → `umbraBookshelfGdpr`, `bdmAdmin` → `umbraBookshelfLibraryAdmin`.
|
||||||
|
* Admin menu slugs (see §5).
|
||||||
|
|
||||||
|
### 2.2 Deliberately **not** renamed
|
||||||
|
|
||||||
|
These are the persisted contract with live installations and with published
|
||||||
|
post content. Renaming any of them silently orphans existing data.
|
||||||
|
|
||||||
|
| Contract | Values |
|
||||||
|
|---|---|
|
||||||
|
| Table names | `bd_books`, `bd_series`, `bd_book_authors`, `bd_authors`, `bd_book_seo`, `bd_retail_links`, `bd_retail_sites`, `bd_shortcodes`, `bd_shortcode_clicks`, `bd_book_meta`, `bd_pen_names`, `bd_reviews`, `bd_ratings`, `bd_review_shortlinks`, `bd_subscribers`, `bd_newsletters` |
|
||||||
|
| Option keys | `bd_bookshelf_db_version`, `bd_bookshelf_activation_time`, `bdm_affiliate_codes`, `bdm_goodreads_author_id`, `bdm_awards`, `bdm_gmail_address`, `bdm_gmail_password`, `bdm_sender_name`, `bdm_reply_to`, `bdm_gemini_api_key` |
|
||||||
|
| Post meta | `_bd_bookshelf_book_id` |
|
||||||
|
| Transient prefix | `_transient_bd_bookshelf_*` |
|
||||||
|
| Nonce actions | `bd_bookshelf_admin_nonce`, `bd_bookshelf_import_posts`, `bd_bookshelf_import_csv`, `bd_bookshelf_admin`, `bd_bookshelf_gdpr_nonce` |
|
||||||
|
| Cron hooks | `bd_bookshelf_sync_ratings`, `bd_bookshelf_goodreads_refresh`, `bd_bookshelf_amazon_scrape` |
|
||||||
|
| AJAX actions | `bd_bookshelf_*`, `bdm_update_links`, `bdm_autosave`, `bdm_ajax_signup`, `bdm_signup_submit`, `bdm_unsubscribe_submit` |
|
||||||
|
| Shortcodes | `[bd-book]`, `[bd-books]`, `[bdm_signup]` |
|
||||||
|
| Content types | post type `book`, taxonomy `series` |
|
||||||
|
| Cookies | `bd_bookshelf_consent` |
|
||||||
|
| CSS classes | `bd-bookshelf-*`, `bd-email-*` (paired HTML/CSS, may be styled by themes) |
|
||||||
|
|
||||||
|
One legacy PHP class name is retained by design: `class-umbra-theme-compatibility.php`
|
||||||
|
declares `Umbra_Theme_Compatibility` and then
|
||||||
|
`class_alias('Umbra_Theme_Compatibility', 'BD_BookShelfHelper')` so the
|
||||||
|
unmodified `bd-bookshelf` theme's static `BD_BookShelfHelper::` calls keep working.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Architecture
|
||||||
|
|
||||||
|
```
|
||||||
|
umbra-bookshelf/
|
||||||
|
├── umbra-bookshelf.php bootstrap: constants, requires, singleton, single top-level menu
|
||||||
|
├── uninstall.php drops every table and option from all three modules
|
||||||
|
├── includes/
|
||||||
|
│ ├── books/ catalogue: books, authors, series, retail, ratings,
|
||||||
|
│ │ shortcodes, SEO, GDPR, scrapers, admin, import
|
||||||
|
│ ├── library/ book post type + taxonomy, pen names, data entry,
|
||||||
|
│ │ shortlinks, reviews, ratings, migrate
|
||||||
|
│ └── email/ subscribers, newsletters, AI-assisted composition, sending
|
||||||
|
├── admin/ css, js catalogue admin assets
|
||||||
|
├── public/ css, js front-end + GDPR consent assets
|
||||||
|
├── library/ assets library admin assets
|
||||||
|
├── email/ assets email admin assets
|
||||||
|
├── assets/ images default-cover.png
|
||||||
|
├── languages/ translation catalogues
|
||||||
|
├── spec/ this document; archive/ holds pre-merge specs
|
||||||
|
├── docs/ architecture and known issues
|
||||||
|
└── tests/ verify_structure.py, smoke_runtime.php
|
||||||
|
```
|
||||||
|
|
||||||
|
**Boot order.** Modules are constructed on `plugins_loaded`, not `init`. Several
|
||||||
|
modules register their own `init` callbacks (post type, taxonomy, shortcodes,
|
||||||
|
rewrite rules); constructing them from inside an `init` callback makes it
|
||||||
|
accidental whether those callbacks run. A `$modules_initialized` flag prevents
|
||||||
|
double construction.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Module responsibilities
|
||||||
|
|
||||||
|
### 4.1 Books (catalogue) — `includes/books/`
|
||||||
|
|
||||||
|
Owns the custom-table catalogue model: books with ISBN-10/13 and ASIN variants,
|
||||||
|
author records and pen-name linkage, series, retail sites and per-format retail
|
||||||
|
links, Goodreads and Amazon rating synchronisation, SEO rows, shortlink
|
||||||
|
click tracking, GDPR consent, and CSV/post import.
|
||||||
|
|
||||||
|
Classes: `Umbra_Database`, `Umbra_Book_Manager`, `Umbra_Authors`, `Umbra_Series`,
|
||||||
|
`Umbra_Retail_Sites`, `Umbra_Rating_Sync`, `Umbra_Shortcodes`, `Umbra_SEO`,
|
||||||
|
`Umbra_GDPR`, `Umbra_Goodreads_Scraper`, `Umbra_Amazon_Scraper`, `Umbra_Admin`,
|
||||||
|
`Umbra_Migration`, `Umbra_Theme_Compatibility`.
|
||||||
|
|
||||||
|
> `Umbra_SEO` is never instantiated by the bootstrap (carried over unchanged from
|
||||||
|
> the original plugin). See `docs/KNOWN-ISSUES.md` item 5.
|
||||||
|
|
||||||
|
### 4.2 Library — `includes/library/`
|
||||||
|
|
||||||
|
Owns the WordPress-native model: registers post type `book` and taxonomy
|
||||||
|
`series`, pen names, a focused data-entry screen, shortlink handling, reviews and
|
||||||
|
ratings, and a migration screen for data created by earlier plugins.
|
||||||
|
|
||||||
|
Classes: `Umbra_Bookshelf_Helper`, `Umbra_Bookshelf_Helper_Admin`,
|
||||||
|
`Umbra_Bookshelf_Helper_Install`.
|
||||||
|
|
||||||
|
### 4.3 Email — `includes/email/`
|
||||||
|
|
||||||
|
Owns newsletter delivery: subscriber and newsletter tables, `[bdm_signup]`,
|
||||||
|
signup and unsubscribe form handling (AJAX and `admin_post`), Gemini-assisted
|
||||||
|
newsletter composition, and SMTP/Gmail sending.
|
||||||
|
|
||||||
|
Classes: `Umbra_Simple_Email`, `Umbra_Simple_Email_Admin`,
|
||||||
|
`Umbra_Simple_Email_Install`, `Umbra_Email_Sender`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Admin menu
|
||||||
|
|
||||||
|
One top-level menu, registered exactly once by the bootstrap at
|
||||||
|
`admin_menu` priority 9. Each module contributes only submenus beneath it.
|
||||||
|
|
||||||
|
**Umbra Bookshelf** (`umbra-bookshelf`, `dashicons-book-alt`, position 30)
|
||||||
|
|
||||||
|
| Submenu slug | Module | Capability |
|
||||||
|
|---|---|---|
|
||||||
|
| `umbra-bookshelf` | bootstrap | `edit_posts` |
|
||||||
|
| `umbra-bookshelf-all` | books | `manage_options` |
|
||||||
|
| `umbra-bookshelf-add` | books | `manage_options` |
|
||||||
|
| `umbra-bookshelf-authors` | books | `manage_options` |
|
||||||
|
| `umbra-bookshelf-series` | books | `manage_options` |
|
||||||
|
| `umbra-bookshelf-sites` | books | `manage_options` |
|
||||||
|
| `umbra-bookshelf-shortcodes` | books | `manage_options` |
|
||||||
|
| `umbra-bookshelf-sync` | books | `manage_options` |
|
||||||
|
| `umbra-bookshelf-settings` | books | `manage_options` |
|
||||||
|
| `umbra-bookshelf-import` | books | `manage_options` |
|
||||||
|
| `edit.php?post_type=book` | library | `edit_posts` |
|
||||||
|
| `post-new.php?post_type=book` | library | `edit_posts` |
|
||||||
|
| `umbra-bookshelf-data-entry` | library | `edit_posts` |
|
||||||
|
| `edit-tags.php?taxonomy=series&post_type=book` | library | `manage_categories` |
|
||||||
|
| `umbra-bookshelf-pen-names` | library | `manage_options` |
|
||||||
|
| `umbra-bookshelf-library-settings` | library | `manage_options` |
|
||||||
|
| `umbra-bookshelf-migrate` | library | `manage_options` |
|
||||||
|
| `umbra-bookshelf-subscribers` | email | `manage_options` |
|
||||||
|
| `umbra-bookshelf-newsletter` | email | `manage_options` |
|
||||||
|
| `umbra-bookshelf-email-settings` | email | `manage_options` |
|
||||||
|
|
||||||
|
Catalogue and library settings are deliberately **separate** screens with
|
||||||
|
separate slugs; the pre-merge plugins both claimed `bd-bookshelf-settings`, so one
|
||||||
|
of the two was unreachable.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Lifecycle
|
||||||
|
|
||||||
|
* **Activate:** create catalogue tables (`Umbra_Database::create_tables()`),
|
||||||
|
library tables + roles (`Umbra_Bookshelf_Helper_Install::activate()`), email
|
||||||
|
tables (`Umbra_Simple_Email_Install::activate()`), flush rewrites, record
|
||||||
|
`bd_bookshelf_activation_time`.
|
||||||
|
* **Deactivate:** clear all three cron hooks, run module deactivators, flush
|
||||||
|
rewrites.
|
||||||
|
* **Uninstall:** drop every table across all three modules (deduplicated so
|
||||||
|
`bd_books`/`bd_series` are dropped once), delete `bd_bookshelf_%` options and
|
||||||
|
transients, delete the `bdm_*` options, clear cron. Guarded by
|
||||||
|
`WP_UNINSTALL_PLUGIN`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Verification
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 umbra-bookshelf/tests/verify_structure.py # static checks
|
||||||
|
php umbra-bookshelf/tests/smoke_runtime.php front # front-end boot
|
||||||
|
php umbra-bookshelf/tests/smoke_runtime.php admin # admin boot + menu shape
|
||||||
|
```
|
||||||
|
|
||||||
|
The static check enforces: valid PHP syntax, no class declared twice, no
|
||||||
|
reference to an undefined class, every `require_once` and enqueued asset
|
||||||
|
resolves on disk, no leftover pre-merge identifiers in shipped source, and all
|
||||||
|
preserved contracts of §2.2 still present.
|
||||||
|
|
||||||
|
The runtime check stubs WordPress, loads the bootstrap, fires `plugins_loaded`,
|
||||||
|
`init` and `admin_menu`, and asserts the post type, taxonomy and shortcodes
|
||||||
|
register, that exactly one top-level menu appears with all 20 expected submenus
|
||||||
|
under it, that no callback is registered twice on a hook, and that asset handles
|
||||||
|
are unique per module.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Open issues
|
||||||
|
|
||||||
|
`bd_books` and `bd_series` are created with two incompatible schemas by the
|
||||||
|
books and library modules. This predates the consolidation and is **not** fixed
|
||||||
|
here; see `docs/KNOWN-ISSUES.md`.
|
||||||
24
umbra-bookshelf/spec/archive/README.md
Normal file
24
umbra-bookshelf/spec/archive/README.md
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# Archived specifications
|
||||||
|
|
||||||
|
These are the design documents for the three plugins as they existed
|
||||||
|
**before** the Umbra Bookshelf consolidation. They are kept for provenance: the
|
||||||
|
reasoning behind the original data models is not captured anywhere else.
|
||||||
|
|
||||||
|
**They describe superseded code and must not be used as an implementation
|
||||||
|
reference.** Where they disagree with `../SPEC_Umbra_Bookshelf_Plugin.md`, the
|
||||||
|
consolidated specification is correct.
|
||||||
|
|
||||||
|
| File | Describes | Status |
|
||||||
|
|---|---|---|
|
||||||
|
| `SPEC_BD_Bookshelf_Plugin.txt` | BD Bookshelf 0.1.0 (catalogue) | superseded by the Books module |
|
||||||
|
| `SPEC_BD_BookHelper_Plugin.txt` | BD BookShelfHelper 2.0.0 (library) | superseded by the Library module |
|
||||||
|
| `bd-bookshelf-plugin-spec-0.0.1.md` | BD Bookshelf design spec | superseded |
|
||||||
|
| `bd-bookshelf-plugin-questions-0.0.1.md` | design Q&A, round 1 | historical record |
|
||||||
|
| `bd-bookshelf-plugin-questions-0.0.2.md` | design Q&A, round 2 | historical record |
|
||||||
|
|
||||||
|
Old plugin names, class names and table names are preserved verbatim in these
|
||||||
|
files on purpose — they are a record of what was, not a description of what is.
|
||||||
|
|
||||||
|
The pre-merge `.docx` specifications (`BDMurphy-Plugin-Specifications-v2.2.docx`,
|
||||||
|
`BDMurphy-Theme-Specifications-v2.2.docx`) are binary and were left in the
|
||||||
|
archived **Bookshelf** repository rather than rewritten here.
|
||||||
307
umbra-bookshelf/tests/smoke_runtime.php
Normal file
307
umbra-bookshelf/tests/smoke_runtime.php
Normal file
@@ -0,0 +1,307 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Runtime smoke test for the merged umbra-bookshelf plugin.
|
||||||
|
*
|
||||||
|
* Stubs just enough of WordPress to load umbra-bookshelf.php, fire the hooks
|
||||||
|
* the plugin registers on, and assert that the merged plugin boots without a
|
||||||
|
* fatal error and registers the consolidated admin menu exactly once.
|
||||||
|
*
|
||||||
|
* Run: php /tmp/opencode/smoke_umbra.php
|
||||||
|
*/
|
||||||
|
|
||||||
|
define('ABSPATH', '/tmp/fake-wp/');
|
||||||
|
define('WP_UNINSTALL_PLUGIN', 'umbra-bookshelf/umbra-bookshelf.php');
|
||||||
|
|
||||||
|
$GLOBALS['__hooks'] = array();
|
||||||
|
$GLOBALS['__menus'] = array();
|
||||||
|
$GLOBALS['__submenus'] = array();
|
||||||
|
$GLOBALS['__shortcodes'] = array();
|
||||||
|
$GLOBALS['__pt'] = array();
|
||||||
|
$GLOBALS['__tax'] = array();
|
||||||
|
$GLOBALS['__enqueued'] = array();
|
||||||
|
$GLOBALS['__options'] = array();
|
||||||
|
$GLOBALS['__cron'] = array();
|
||||||
|
$GLOBALS['__is_admin'] = (($argv[1] ?? 'front') === 'admin');
|
||||||
|
$MODE = ($argv[1] ?? 'front');
|
||||||
|
|
||||||
|
function add_action($h, $cb, $p = 10, $a = 1) { $GLOBALS['__hooks'][$h][] = $cb; }
|
||||||
|
function add_filter($h, $cb, $p = 10, $a = 1) { return true; }
|
||||||
|
function register_activation_hook($f, $cb) {}
|
||||||
|
function register_deactivation_hook($f, $cb) {}
|
||||||
|
function load_plugin_textdomain($d, $x = false, $p = '') { return true; }
|
||||||
|
function plugin_dir_path($f) { return rtrim(dirname($f), '/') . '/'; }
|
||||||
|
function plugin_dir_url($f) { return 'https://example.test/wp-content/plugins/'
|
||||||
|
. basename(dirname($f)) . '/'; }
|
||||||
|
function plugin_basename($f) { return basename(dirname($f)) . '/' . basename($f); }
|
||||||
|
function is_admin() { return $GLOBALS['__is_admin']; }
|
||||||
|
function current_user_can($c) { return true; }
|
||||||
|
function wp_die($m = '') { throw new Exception('wp_die: ' . $m); }
|
||||||
|
function __($t, $d = null) { return $t; }
|
||||||
|
function esc_html__($t, $d = null) { return $t; }
|
||||||
|
function esc_html_e($t, $d = null) { echo $t; }
|
||||||
|
function esc_attr__($t, $d = null) { return $t; }
|
||||||
|
function esc_attr_e($t, $d = null) { echo $t; }
|
||||||
|
function esc_html($t) { return $t; }
|
||||||
|
function esc_url($t) { return $t; }
|
||||||
|
function esc_textarea($t) { return $t; }
|
||||||
|
function number_format_i18n($n) { return number_format($n); }
|
||||||
|
function absint($n) { return abs((int) $n); }
|
||||||
|
function sanitize_text_field($t) { return $t; }
|
||||||
|
function sanitize_key($t) { return preg_replace('/[^a-z0-9_\-]/', '', strtolower($t)); }
|
||||||
|
function wp_create_nonce($a) { return 'nonce'; }
|
||||||
|
function wp_verify_nonce($n, $a) { return 1; }
|
||||||
|
function admin_url($p = '') { return 'https://example.test/wp-admin/' . $p; }
|
||||||
|
function flush_rewrite_rules($h = true) {}
|
||||||
|
function update_option($k, $v, $a = null) { $GLOBALS['__options'][$k] = $v; return true; }
|
||||||
|
function get_option($k, $d = false) { return $GLOBALS['__options'][$k] ?? $d; }
|
||||||
|
function delete_option($k) { unset($GLOBALS['__options'][$k]); return true; }
|
||||||
|
function wp_count_posts($t) { return (object) array('publish' => 7); }
|
||||||
|
function get_terms($a = array()) { return 4; }
|
||||||
|
function is_wp_error($t) { return false; }
|
||||||
|
function get_post_meta($p, $k, $s = true) { return ''; }
|
||||||
|
function update_post_meta($p, $k, $v) { return true; }
|
||||||
|
function get_post($p) { return null; }
|
||||||
|
function get_the_post_thumbnail_url($p, $s = '') { return ''; }
|
||||||
|
function wp_schedule_event($t, $r, $h) { $GLOBALS['__cron'][$h] = true; }
|
||||||
|
function wp_next_scheduled($h) { return false; }
|
||||||
|
function wp_clear_scheduled_hook($h) { unset($GLOBALS['__cron'][$h]); }
|
||||||
|
function wp_unschedule_hook($h) {}
|
||||||
|
function add_shortcode($tag, $cb) { $GLOBALS['__shortcodes'][] = $tag; }
|
||||||
|
function register_post_type($t, $a = array()) { $GLOBALS['__pt'][$t] = $a; return $t; }
|
||||||
|
function register_taxonomy($t, $o, $a = array()) { $GLOBALS['__tax'][$t] = $a; return $t; }
|
||||||
|
function add_menu_page($pt, $mt, $cap, $slug, $cb = '', $icon = '', $pos = null) {
|
||||||
|
$GLOBALS['__menus'][$slug] = array('page_title' => $pt, 'capability' => $cap,
|
||||||
|
'callback' => $cb, 'icon' => $icon, 'position' => $pos);
|
||||||
|
return $slug;
|
||||||
|
}
|
||||||
|
function add_submenu_page($parent, $pt, $mt, $cap, $slug, $cb = '') {
|
||||||
|
$GLOBALS['__submenus'][$slug] = array('parent' => $parent, 'page_title' => $pt,
|
||||||
|
'menu_title' => $mt, 'capability' => $cap,
|
||||||
|
'callback' => $cb);
|
||||||
|
return $slug;
|
||||||
|
}
|
||||||
|
function get_current_screen() { return null; }
|
||||||
|
function wp_enqueue_media() {}
|
||||||
|
function wp_enqueue_style($h, $src = '', $d = array(), $v = false, $m = 'all') {
|
||||||
|
$GLOBALS['__enqueued']['style'][$h] = $src;
|
||||||
|
}
|
||||||
|
function wp_enqueue_script($h, $src = '', $d = array(), $v = false, $f = false) {
|
||||||
|
$GLOBALS['__enqueued']['script'][$h] = $src;
|
||||||
|
}
|
||||||
|
function wp_localize_script($h, $name, $data) {
|
||||||
|
$GLOBALS['__enqueued']['localize'][$h] = $name;
|
||||||
|
}
|
||||||
|
function wp_register_post_type_args($a, $t) { return $a; }
|
||||||
|
function register_post_meta_args($a, $t) { return $a; }
|
||||||
|
function wp_get_current_user() { return null; }
|
||||||
|
function get_userdata($i) { return false; }
|
||||||
|
function wp_roles() { return null; }
|
||||||
|
function add_role($r, $n, $c) {}
|
||||||
|
function remove_role($r) {}
|
||||||
|
function wp_send_json_success($d) {}
|
||||||
|
function wp_send_json_error($d) {}
|
||||||
|
function check_ajax_referer($a, $q = false) { return 1; }
|
||||||
|
function wp_die_ajax($m) {}
|
||||||
|
function sanitize_email($e) { return $e; }
|
||||||
|
function sanitize_key_($k) { return $k; }
|
||||||
|
function get_home_url($p = '') { return 'https://example.test'; }
|
||||||
|
function home_url($p = '') { return 'https://example.test'; }
|
||||||
|
function wp_mail($to, $subj, $msg) { return true; }
|
||||||
|
function wp_rand($a = 0, $b = 0) { return 12345; }
|
||||||
|
function get_bloginfo($s = '') { return 'Example'; }
|
||||||
|
function get_user_meta($i, $k, $s = false) { return ''; }
|
||||||
|
function update_user_meta($i, $k, $v) { return true; }
|
||||||
|
function add_rewrite_rule($r, $q, $after = 'bottom') { return true; }
|
||||||
|
function add_rewrite_tag($r, $q, $a = '') { return true; }
|
||||||
|
function add_query_var($v) { return $v; }
|
||||||
|
function get_query_var($v, $d = '') { return $d; }
|
||||||
|
function get_option_blogname($d = '') { return 'Example'; }
|
||||||
|
function do_action($h) {
|
||||||
|
foreach ($GLOBALS['__hooks'][$h] ?? array() as $cb) {
|
||||||
|
if (is_callable($cb)) { call_user_func($cb); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$fail = 0;
|
||||||
|
function check($label, $cond, $detail = '') {
|
||||||
|
global $fail;
|
||||||
|
if ($cond) {
|
||||||
|
echo " ok $label\n";
|
||||||
|
} else {
|
||||||
|
echo " FAIL $label" . ($detail ? " ($detail)" : '') . "\n";
|
||||||
|
$GLOBALS['fail']++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function section($t) { echo "\n$t\n"; }
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------- load it
|
||||||
|
section('1. Load plugin bootstrap (front-end context)');
|
||||||
|
try {
|
||||||
|
require dirname(__DIR__) . '/umbra-bookshelf.php';
|
||||||
|
echo " ok bootstrap included without fatal error\n";
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
echo " FAIL fatal: " . $e->getMessage() . "\n";
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
section('2. Core class graph');
|
||||||
|
check('Umbra_Bookshelf class exists', class_exists('Umbra_Bookshelf'));
|
||||||
|
check('singleton instance() returns object',
|
||||||
|
umbra_bookshelf() instanceof Umbra_Bookshelf);
|
||||||
|
check('instance() is idempotent',
|
||||||
|
umbra_bookshelf() === umbra_bookshelf());
|
||||||
|
|
||||||
|
section('3. Module classes loaded');
|
||||||
|
foreach (array('Umbra_Database', 'Umbra_Book_Manager', 'Umbra_Series',
|
||||||
|
'Umbra_Authors', 'Umbra_Retail_Sites', 'Umbra_Rating_Sync',
|
||||||
|
'Umbra_Shortcodes', 'Umbra_SEO', 'Umbra_GDPR',
|
||||||
|
'Umbra_Goodreads_Scraper', 'Umbra_Amazon_Scraper',
|
||||||
|
'Umbra_Admin', 'Umbra_Migration', 'Umbra_Theme_Compatibility',
|
||||||
|
'Umbra_Bookshelf_Helper', 'Umbra_Bookshelf_Helper_Admin',
|
||||||
|
'Umbra_Bookshelf_Helper_Install', 'Umbra_Simple_Email',
|
||||||
|
'Umbra_Simple_Email_Admin', 'Umbra_Simple_Email_Install',
|
||||||
|
'Umbra_Email_Sender') as $c) {
|
||||||
|
check("class $c", class_exists($c));
|
||||||
|
}
|
||||||
|
|
||||||
|
section('4. Legacy theme alias');
|
||||||
|
check('class_alias exposes BD_BookShelfHelper for the theme',
|
||||||
|
class_exists('BD_BookShelfHelper', false));
|
||||||
|
check('alias resolves to the compatibility shim',
|
||||||
|
is_a('BD_BookShelfHelper', 'Umbra_Theme_Compatibility', true));
|
||||||
|
check('shim exposes get_cover_url (theme API)',
|
||||||
|
method_exists('BD_BookShelfHelper', 'get_cover_url'));
|
||||||
|
check('real library class still distinct',
|
||||||
|
!is_a('Umbra_Bookshelf_Helper', 'Umbra_Theme_Compatibility', true));
|
||||||
|
|
||||||
|
section('5. Constants');
|
||||||
|
check('UMBRA_BOOKSHELF_VERSION', defined('UMBRA_BOOKSHELF_VERSION')
|
||||||
|
&& UMBRA_BOOKSHELF_VERSION === '1.0.0', UMBRA_BOOKSHELF_VERSION ?? 'undefined');
|
||||||
|
check('UMBRA_BOOKSHELF_DIR', defined('UMBRA_BOOKSHELF_DIR'));
|
||||||
|
check('UMBRA_BOOKSHELF_URL', defined('UMBRA_BOOKSHELF_URL'));
|
||||||
|
check('UMBRA_BOOKSHELF_BASENAME', defined('UMBRA_BOOKSHELF_BASENAME'));
|
||||||
|
check('library module version', defined('UMBRA_BOOKSHELF_LIBRARY_VERSION'));
|
||||||
|
check('email module version', defined('UMBRA_BOOKSHELF_EMAIL_VERSION'));
|
||||||
|
check('old BD_BOOKSHELF_VERSION removed', !defined('BD_BOOKSHELF_VERSION'));
|
||||||
|
check('old BD_BHS_DIR removed', !defined('BD_BHS_DIR'));
|
||||||
|
check('old BD_EMAIL_URI removed', !defined('BD_EMAIL_URI'));
|
||||||
|
|
||||||
|
section('6. Boot the modules (' . $MODE . ' request)');
|
||||||
|
try {
|
||||||
|
do_action('plugins_loaded');
|
||||||
|
do_action('init');
|
||||||
|
echo " ok plugins_loaded + init fired without fatal error\n";
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
echo " FAIL fatal on init: " . $e->getMessage() . "\n";
|
||||||
|
$GLOBALS['fail']++;
|
||||||
|
}
|
||||||
|
check('book post type registered by library module',
|
||||||
|
isset($GLOBALS['__pt']['book']));
|
||||||
|
check('series taxonomy registered by library module',
|
||||||
|
isset($GLOBALS['__tax']['series']));
|
||||||
|
check('[bd-book] shortcode preserved',
|
||||||
|
in_array('bd-book', $GLOBALS['__shortcodes'], true));
|
||||||
|
check('[bd-books] shortcode preserved',
|
||||||
|
in_array('bd-books', $GLOBALS['__shortcodes'], true));
|
||||||
|
check('[bdm_signup] shortcode preserved',
|
||||||
|
in_array('bdm_signup', $GLOBALS['__shortcodes'], true));
|
||||||
|
|
||||||
|
section('7. Admin menu consolidation (admin context)');
|
||||||
|
try {
|
||||||
|
do_action('admin_menu');
|
||||||
|
echo " ok admin_menu hooks fired without fatal error\n";
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
echo " FAIL fatal on admin_menu: " . $e->getMessage() . "\n";
|
||||||
|
$GLOBALS['fail']++;
|
||||||
|
}
|
||||||
|
|
||||||
|
$menus = $GLOBALS['__menus'];
|
||||||
|
if ($MODE !== 'admin') {
|
||||||
|
echo " .. skipped menu assertions (front-end request: no admin_menu)\n";
|
||||||
|
section('RESULT');
|
||||||
|
if (!empty($GLOBALS['fail'])) {
|
||||||
|
echo "FAIL - {$GLOBALS['fail']} check(s) failed\n";
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
echo "PASS - runtime smoke test clean\n";
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
check('exactly ONE top-level menu registered', count($menus) === 1,
|
||||||
|
count($menus) . ': ' . implode(', ', array_keys($menus)));
|
||||||
|
check('top-level slug is umbra-bookshelf',
|
||||||
|
isset($menus['umbra-bookshelf']), implode(', ', array_keys($menus)));
|
||||||
|
check('top-level titled "Umbra Bookshelf"',
|
||||||
|
($menus['umbra-bookshelf']['page_title'] ?? '') === 'Umbra Bookshelf',
|
||||||
|
$menus['umbra-bookshelf']['page_title'] ?? '');
|
||||||
|
|
||||||
|
$subs = $GLOBALS['__submenus'];
|
||||||
|
$badParent = array();
|
||||||
|
foreach ($subs as $slug => $s) {
|
||||||
|
if ($s['parent'] !== 'umbra-bookshelf') { $badParent[$slug] = $s['parent']; }
|
||||||
|
}
|
||||||
|
check('every submenu hangs off umbra-bookshelf', count($badParent) === 0,
|
||||||
|
json_encode($badParent));
|
||||||
|
|
||||||
|
$expected = array(
|
||||||
|
'umbra-bookshelf', 'umbra-bookshelf-all', 'umbra-bookshelf-add',
|
||||||
|
'umbra-bookshelf-authors', 'umbra-bookshelf-series', 'umbra-bookshelf-sites',
|
||||||
|
'umbra-bookshelf-shortcodes', 'umbra-bookshelf-sync', 'umbra-bookshelf-settings',
|
||||||
|
'umbra-bookshelf-import',
|
||||||
|
'edit.php?post_type=book', 'post-new.php?post_type=book',
|
||||||
|
'umbra-bookshelf-data-entry', 'edit-tags.php?taxonomy=series&post_type=book',
|
||||||
|
'umbra-bookshelf-pen-names', 'umbra-bookshelf-library-settings',
|
||||||
|
'umbra-bookshelf-migrate',
|
||||||
|
'umbra-bookshelf-subscribers', 'umbra-bookshelf-newsletter',
|
||||||
|
'umbra-bookshelf-email-settings',
|
||||||
|
);
|
||||||
|
$missing = array_values(array_diff($expected, array_keys($subs)));
|
||||||
|
check('all ' . count($expected) . ' expected submenus present', count($missing) === 0,
|
||||||
|
'missing: ' . implode(', ', $missing));
|
||||||
|
|
||||||
|
check('no duplicate submenu slug (settings screens distinct)',
|
||||||
|
!isset($subs['bd-bookshelf-settings']),
|
||||||
|
'legacy duplicate slug still registered');
|
||||||
|
check('catalogue settings present', isset($subs['umbra-bookshelf-settings']));
|
||||||
|
check('library settings present', isset($subs['umbra-bookshelf-library-settings']));
|
||||||
|
check('email settings present', isset($subs['umbra-bookshelf-email-settings']));
|
||||||
|
|
||||||
|
section('8. Duplicate hook / action registrations');
|
||||||
|
$dupes = array();
|
||||||
|
foreach ($GLOBALS['__hooks'] as $hook => $cbs) {
|
||||||
|
$seen = array();
|
||||||
|
foreach ($cbs as $cb) {
|
||||||
|
$k = is_string($cb) ? $cb : (is_array($cb) ? (is_object($cb[0]) ? get_class($cb[0]) . '::' . $cb[1] : $cb[0] . '::' . $cb[1]) : 'closure');
|
||||||
|
$seen[$k] = ($seen[$k] ?? 0) + 1;
|
||||||
|
}
|
||||||
|
foreach ($seen as $k => $n) {
|
||||||
|
if ($n > 1) { $dupes[] = "$hook => $k ($n)"; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
check('no callback registered twice on one hook', count($dupes) === 0,
|
||||||
|
implode('; ', $dupes));
|
||||||
|
|
||||||
|
section('9. Asset handles are unique per module');
|
||||||
|
$styles = array_keys($GLOBALS['__enqueued']['style'] ?? array());
|
||||||
|
$scripts = array_keys($GLOBALS['__enqueued']['script'] ?? array());
|
||||||
|
check('no duplicated style handle', count($styles) === count(array_unique($styles)),
|
||||||
|
implode(', ', $styles));
|
||||||
|
check('no duplicated script handle', count($scripts) === count(array_unique($scripts)),
|
||||||
|
implode(', ', $scripts));
|
||||||
|
|
||||||
|
section('10. Uninstall routine parses and targets real tables');
|
||||||
|
$src = file_get_contents(dirname(__DIR__) . '/uninstall.php');
|
||||||
|
check('uninstall references catalogue drop_tables', strpos($src, 'drop_tables()') !== false);
|
||||||
|
foreach (array('bd_book_meta', 'bd_pen_names', 'bd_reviews', 'bd_ratings',
|
||||||
|
'bd_review_shortlinks', 'bd_subscribers', 'bd_newsletters') as $t) {
|
||||||
|
check("uninstall drops $t", strpos($src, $t) !== false);
|
||||||
|
}
|
||||||
|
check('uninstall clears all three cron hooks',
|
||||||
|
substr_count($src, 'wp_clear_scheduled_hook') === 3,
|
||||||
|
substr_count($src, 'wp_clear_scheduled_hook') . ' calls');
|
||||||
|
|
||||||
|
section('RESULT');
|
||||||
|
if (!empty($GLOBALS['fail'])) {
|
||||||
|
echo "FAIL - {$GLOBALS['fail']} check(s) failed\n";
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
echo "PASS - runtime smoke test clean\n";
|
||||||
180
umbra-bookshelf/tests/verify_structure.py
Normal file
180
umbra-bookshelf/tests/verify_structure.py
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Static verification for the merged umbra-bookshelf plugin.
|
||||||
|
|
||||||
|
Checks:
|
||||||
|
1. PHP syntax of every file
|
||||||
|
2. No class declared twice (fatal error in PHP)
|
||||||
|
3. Every class referenced by new X / X:: actually exists
|
||||||
|
4. Every require_once path resolves on disk
|
||||||
|
5. Every wp_enqueue_* asset path resolves on disk (no 404s)
|
||||||
|
6. No leftover pre-rename identifiers
|
||||||
|
7. Preserved storage/API contracts still present
|
||||||
|
"""
|
||||||
|
import io
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
failures = []
|
||||||
|
notes = []
|
||||||
|
|
||||||
|
|
||||||
|
def rel(p):
|
||||||
|
return os.path.relpath(p, ROOT)
|
||||||
|
|
||||||
|
|
||||||
|
def walk(exts):
|
||||||
|
for dirpath, _, files in os.walk(ROOT):
|
||||||
|
for f in files:
|
||||||
|
if f.endswith(exts):
|
||||||
|
yield os.path.join(dirpath, f)
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------- 1. php -l
|
||||||
|
php = [f for f in walk('.php')]
|
||||||
|
for f in php:
|
||||||
|
r = subprocess.run(['php', '-l', f], capture_output=True, text=True)
|
||||||
|
if 'No syntax errors' not in r.stdout:
|
||||||
|
failures.append('PHP SYNTAX: %s\n%s' % (rel(f), r.stdout + r.stderr))
|
||||||
|
print('1. php -l: %d files checked' % len(php))
|
||||||
|
|
||||||
|
# ------------------------------------------------- 2. duplicate class decls
|
||||||
|
decls = {}
|
||||||
|
for f in php:
|
||||||
|
src = io.open(f, encoding='utf-8', errors='replace').read()
|
||||||
|
for m in re.finditer(r'^\s*(?:final\s+|abstract\s+)?class\s+([A-Za-z_][A-Za-z0-9_]*)',
|
||||||
|
src, re.M):
|
||||||
|
decls.setdefault(m.group(1), []).append(rel(f))
|
||||||
|
dupes = {c: fs for c, fs in decls.items() if len(fs) > 1}
|
||||||
|
for c, fs in sorted(dupes.items()):
|
||||||
|
failures.append('DUPLICATE CLASS: %s declared in %s' % (c, ', '.join(fs)))
|
||||||
|
print('2. class declarations: %d unique, %d duplicated'
|
||||||
|
% (len(decls), len(dupes)))
|
||||||
|
|
||||||
|
# --------------------------------------------- 3. referenced classes exist
|
||||||
|
refs = set()
|
||||||
|
for f in php:
|
||||||
|
src = io.open(f, encoding='utf-8', errors='replace').read()
|
||||||
|
for m in re.finditer(r'\bnew\s+([A-Z][A-Za-z0-9_]*)\s*\(', src):
|
||||||
|
refs.add(m.group(1))
|
||||||
|
for m in re.finditer(r"\b([A-Z][A-Za-z0-9_]*)::", src):
|
||||||
|
refs.add(m.group(1))
|
||||||
|
|
||||||
|
# names defined by this plugin, or provided by WordPress
|
||||||
|
wp_provided = {'BD_BookShelfHelper', # created via class_alias()
|
||||||
|
'WP_Error', 'WP_Query', 'WP_Post', 'WP_REST_Response',
|
||||||
|
'WP_REST_Request', 'WP_REST_Server', 'WP_Term', 'wpdb',
|
||||||
|
'ReflectionClass', 'DateTime', 'Exception', 'ArrayObject'}
|
||||||
|
known_external = {'GeminiClient',
|
||||||
|
# browser JS globals, matched by the `new X(` heuristic
|
||||||
|
# when scanning inline <script> blocks inside PHP files
|
||||||
|
'FormData', 'URL', 'Image', 'Audio'}
|
||||||
|
missing = sorted(r for r in refs
|
||||||
|
if r not in decls
|
||||||
|
and r not in wp_provided
|
||||||
|
and r not in known_external)
|
||||||
|
for r in missing:
|
||||||
|
failures.append('UNDEFINED CLASS REFERENCED: %s' % r)
|
||||||
|
print('3. class references: %d referenced, %d undefined' % (len(refs), len(missing)))
|
||||||
|
|
||||||
|
# --------------------------------------------------- 4. require_once paths
|
||||||
|
for f in php:
|
||||||
|
src = io.open(f, encoding='utf-8', errors='replace').read()
|
||||||
|
for m in re.finditer(r"require(?:_once)?\s+([^;]+);", src):
|
||||||
|
expr = m.group(1)
|
||||||
|
pm = re.search(r"UMBRA_BOOKSHELF_DIR\s*\.\s*'([^']+)'", expr)
|
||||||
|
if pm:
|
||||||
|
target = os.path.join(ROOT, pm.group(1).lstrip('/'))
|
||||||
|
if not os.path.isfile(target):
|
||||||
|
failures.append('MISSING REQUIRE: %s -> %s' % (rel(f), pm.group(1)))
|
||||||
|
elif "__DIR__" in expr:
|
||||||
|
pm = re.search(r"__DIR__\s*\.\s*'([^']+)'", expr)
|
||||||
|
if pm:
|
||||||
|
target = os.path.join(os.path.dirname(f), pm.group(1).lstrip('/'))
|
||||||
|
if not os.path.isfile(target):
|
||||||
|
failures.append('MISSING REQUIRE: %s -> %s'
|
||||||
|
% (rel(f), pm.group(1)))
|
||||||
|
print('4. require_once targets verified')
|
||||||
|
|
||||||
|
# ------------------------------------------------------- 5. asset 404 check
|
||||||
|
assets = 0
|
||||||
|
for f in list(walk('.php')):
|
||||||
|
src = io.open(f, encoding='utf-8', errors='replace').read()
|
||||||
|
for m in re.finditer(
|
||||||
|
r"UMBRA_BOOKSHELF_URL\s*\.\s*'([^']+\.(?:css|js|png|jpg|svg))'",
|
||||||
|
src):
|
||||||
|
target = os.path.join(ROOT, m.group(1))
|
||||||
|
assets += 1
|
||||||
|
if not os.path.isfile(target):
|
||||||
|
failures.append('ASSET 404: %s -> %s' % (rel(f), m.group(1)))
|
||||||
|
print('5. enqueued assets: %d references, all resolved' % assets)
|
||||||
|
|
||||||
|
# -------------------------------------------- 6. leftover pre-rename tokens
|
||||||
|
stale_pat = re.compile(r'\b(?:BD_[A-Z][A-Za-z0-9_]*|BDM_[A-Za-z0-9_]*)\b')
|
||||||
|
stale = []
|
||||||
|
# Scan shipped plugin source only. Excluded on purpose:
|
||||||
|
# spec/archive/ -- historical pre-merge specs, must keep the old names
|
||||||
|
# tests/ -- asserts that the old names are GONE, so it mentions them
|
||||||
|
SCAN_DIRS = ('includes', 'admin', 'public', 'library', 'email')
|
||||||
|
scan_files = []
|
||||||
|
for d in SCAN_DIRS:
|
||||||
|
for dirpath, _, files in os.walk(os.path.join(ROOT, d)):
|
||||||
|
for f in files:
|
||||||
|
if f.endswith(('.php', '.js', '.css')):
|
||||||
|
scan_files.append(os.path.join(dirpath, f))
|
||||||
|
for f in ['umbra-bookshelf.php', 'uninstall.php']:
|
||||||
|
scan_files.append(os.path.join(ROOT, f))
|
||||||
|
scan_files = [f for f in scan_files if os.path.isfile(f)]
|
||||||
|
for f in scan_files:
|
||||||
|
src = io.open(f, encoding='utf-8', errors='replace').read()
|
||||||
|
for i, line in enumerate(src.splitlines(), 1):
|
||||||
|
for m in stale_pat.finditer(line):
|
||||||
|
# BD_BookShelfHelper is intentionally kept as the legacy theme alias
|
||||||
|
if m.group(0) == 'BD_BookShelfHelper':
|
||||||
|
notes.append('%s:%d keeps legacy alias %s'
|
||||||
|
% (rel(f), i, m.group(0)))
|
||||||
|
continue
|
||||||
|
stale.append('%s:%d %s' % (rel(f), i, m.group(0)))
|
||||||
|
for s in sorted(set(stale)):
|
||||||
|
failures.append('STALE IDENTIFIER: %s' % s)
|
||||||
|
print('6. stale identifiers: %d (excluding documented legacy alias)'
|
||||||
|
% len(set(stale)))
|
||||||
|
|
||||||
|
# ------------------------------------- 7. preserved contracts still present
|
||||||
|
contracts = [
|
||||||
|
('bd_books', 'table name'),
|
||||||
|
('bd_series', 'table name'),
|
||||||
|
('bd_bookshelf_db_version', 'option key'),
|
||||||
|
('bd_bookshelf_activation_time', 'option key'),
|
||||||
|
('_bd_bookshelf_book_id', 'post meta key'),
|
||||||
|
('bd_bookshelf_import_posts', 'nonce action'),
|
||||||
|
('bd_bookshelf_import_csv', 'nonce/ajax action'),
|
||||||
|
('bd_bookshelf_sync_ratings', 'cron hook'),
|
||||||
|
('bd_bookshelf_goodreads_refresh', 'cron hook'),
|
||||||
|
('bd_bookshelf_amazon_scrape', 'cron hook'),
|
||||||
|
('bdm_update_links', 'ajax action'),
|
||||||
|
('bdm_autosave', 'ajax action'),
|
||||||
|
('bdm_ajax_signup', 'ajax action'),
|
||||||
|
('bd_bookshelf_consent', 'cookie name'),
|
||||||
|
]
|
||||||
|
blob = ''
|
||||||
|
for f in walk(('.php', '.js')):
|
||||||
|
blob += io.open(f, encoding='utf-8', errors='replace').read()
|
||||||
|
for tok, kind in contracts:
|
||||||
|
if tok not in blob:
|
||||||
|
failures.append('LOST CONTRACT (%s): %s' % (kind, tok))
|
||||||
|
print('7. preserved contracts: %d checked' % len(contracts))
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------- report
|
||||||
|
print()
|
||||||
|
for n in sorted(set(notes)):
|
||||||
|
print('note: %s' % n)
|
||||||
|
print()
|
||||||
|
if failures:
|
||||||
|
print('FAIL (%d)' % len(failures))
|
||||||
|
for f in failures:
|
||||||
|
print(' - %s' % f)
|
||||||
|
sys.exit(1)
|
||||||
|
print('PASS - all static checks clean')
|
||||||
318
umbra-bookshelf/umbra-bookshelf.php
Normal file
318
umbra-bookshelf/umbra-bookshelf.php
Normal file
@@ -0,0 +1,318 @@
|
|||||||
|
<?php
|
||||||
|
// Copyright 2026 Umbra Tools (umbratools.com)
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plugin Name: Umbra Bookshelf
|
||||||
|
* Plugin URI: https://umbratools.com/plugins/umbra-bookshelf
|
||||||
|
* Description: Complete book management for authors and publishers: catalogue and ISBN tracking, series and pen names, retail links, rating synchronisation, GDPR consent, data entry, migration tooling, and newsletter delivery.
|
||||||
|
* Version: 1.0.0
|
||||||
|
* Requires at least: 6.0
|
||||||
|
* Requires PHP: 7.4
|
||||||
|
* Author: Umbra Tools
|
||||||
|
* Author URI: https://umbratools.com
|
||||||
|
* License: Apache-2.0
|
||||||
|
* License URI: https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* Text Domain: umbra-bookshelf
|
||||||
|
* Domain Path: /languages
|
||||||
|
*
|
||||||
|
* Umbra Bookshelf is the consolidation of three previously separate plugins:
|
||||||
|
* - BD Bookshelf -> includes/books
|
||||||
|
* - BD BookShelfHelper -> includes/library
|
||||||
|
* - BD SimpleEmail -> includes/email
|
||||||
|
*
|
||||||
|
* The top-level admin menu is registered exactly once, here. Each module
|
||||||
|
* contributes only submenus beneath the shared "umbra-bookshelf" parent.
|
||||||
|
*
|
||||||
|
* @package Umbra_Bookshelf
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!defined('ABSPATH')) {
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plugin constants
|
||||||
|
*/
|
||||||
|
define('UMBRA_BOOKSHELF_VERSION', '1.0.0');
|
||||||
|
define('UMBRA_BOOKSHELF_FILE', __FILE__);
|
||||||
|
define('UMBRA_BOOKSHELF_DIR', plugin_dir_path(__FILE__));
|
||||||
|
define('UMBRA_BOOKSHELF_URL', plugin_dir_url(__FILE__));
|
||||||
|
define('UMBRA_BOOKSHELF_BASENAME', plugin_basename(__FILE__));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Module version constants
|
||||||
|
*
|
||||||
|
* Storage keys (table names, option names, meta keys, cron hooks, nonces and
|
||||||
|
* shortcodes) are deliberately NOT renamed. They are the persisted contract
|
||||||
|
* with existing installations and with published post content.
|
||||||
|
*/
|
||||||
|
define('UMBRA_BOOKSHELF_LIBRARY_VERSION', '2.0.0');
|
||||||
|
define('UMBRA_BOOKSHELF_EMAIL_VERSION', '2.0.0');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load module files
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Books / catalogue module (ex-BD Bookshelf)
|
||||||
|
require_once UMBRA_BOOKSHELF_DIR . 'includes/books/class-umbra-database.php';
|
||||||
|
require_once UMBRA_BOOKSHELF_DIR . 'includes/books/class-umbra-book-manager.php';
|
||||||
|
require_once UMBRA_BOOKSHELF_DIR . 'includes/books/class-umbra-authors.php';
|
||||||
|
require_once UMBRA_BOOKSHELF_DIR . 'includes/books/class-umbra-series.php';
|
||||||
|
require_once UMBRA_BOOKSHELF_DIR . 'includes/books/class-umbra-retail-sites.php';
|
||||||
|
require_once UMBRA_BOOKSHELF_DIR . 'includes/books/class-umbra-rating-sync.php';
|
||||||
|
require_once UMBRA_BOOKSHELF_DIR . 'includes/books/class-umbra-shortcodes.php';
|
||||||
|
require_once UMBRA_BOOKSHELF_DIR . 'includes/books/class-umbra-seo.php';
|
||||||
|
require_once UMBRA_BOOKSHELF_DIR . 'includes/books/class-umbra-gdpr.php';
|
||||||
|
require_once UMBRA_BOOKSHELF_DIR . 'includes/books/class-umbra-goodreads-scraper.php';
|
||||||
|
require_once UMBRA_BOOKSHELF_DIR . 'includes/books/class-umbra-amazon-scraper.php';
|
||||||
|
require_once UMBRA_BOOKSHELF_DIR . 'includes/books/class-umbra-admin.php';
|
||||||
|
require_once UMBRA_BOOKSHELF_DIR . 'includes/books/class-umbra-migration.php';
|
||||||
|
require_once UMBRA_BOOKSHELF_DIR . 'includes/books/class-umbra-theme-compatibility.php';
|
||||||
|
|
||||||
|
// Library module (ex-BD BookShelfHelper)
|
||||||
|
require_once UMBRA_BOOKSHELF_DIR . 'includes/library/class-umbra-bookshelf-helper.php';
|
||||||
|
require_once UMBRA_BOOKSHELF_DIR . 'includes/library/class-umbra-library-admin.php';
|
||||||
|
require_once UMBRA_BOOKSHELF_DIR . 'includes/library/class-umbra-library-install.php';
|
||||||
|
|
||||||
|
// Email module (ex-BD SimpleEmail)
|
||||||
|
require_once UMBRA_BOOKSHELF_DIR . 'includes/email/class-umbra-simple-email.php';
|
||||||
|
require_once UMBRA_BOOKSHELF_DIR . 'includes/email/class-umbra-email-admin.php';
|
||||||
|
require_once UMBRA_BOOKSHELF_DIR . 'includes/email/class-umbra-email-install.php';
|
||||||
|
require_once UMBRA_BOOKSHELF_DIR . 'includes/email/class-umbra-email-sender.php';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main plugin class
|
||||||
|
*/
|
||||||
|
final class Umbra_Bookshelf {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Single instance of the class
|
||||||
|
*
|
||||||
|
* @var Umbra_Bookshelf|null
|
||||||
|
*/
|
||||||
|
private static $instance = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Top-level admin menu slug. Shared by every module.
|
||||||
|
*/
|
||||||
|
const MENU_SLUG = 'umbra-bookshelf';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Guard so modules are never constructed twice in one request.
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
private $modules_initialized = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get single instance
|
||||||
|
*
|
||||||
|
* @return Umbra_Bookshelf
|
||||||
|
*/
|
||||||
|
public static function instance() {
|
||||||
|
if (is_null(self::$instance)) {
|
||||||
|
self::$instance = new self();
|
||||||
|
}
|
||||||
|
return self::$instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*/
|
||||||
|
private function __construct() {
|
||||||
|
$this->init_hooks();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize hooks
|
||||||
|
*/
|
||||||
|
private function init_hooks() {
|
||||||
|
register_activation_hook(UMBRA_BOOKSHELF_FILE, [$this, 'activate']);
|
||||||
|
register_deactivation_hook(UMBRA_BOOKSHELF_FILE, [$this, 'deactivate']);
|
||||||
|
|
||||||
|
add_action('init', [$this, 'load_textdomain']);
|
||||||
|
add_action('admin_menu', [$this, 'register_admin_menu'], 9);
|
||||||
|
|
||||||
|
// Modules MUST be constructed on plugins_loaded, not init. Several of
|
||||||
|
// them register their own 'init' callbacks (post type, taxonomy,
|
||||||
|
// shortcodes, rewrite rules). Constructing them from an init callback
|
||||||
|
// means those init callbacks are added while 'init' is already being
|
||||||
|
// dispatched, so whether they run becomes an ordering accident.
|
||||||
|
add_action('plugins_loaded', [$this, 'init_modules']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load translations
|
||||||
|
*/
|
||||||
|
public function load_textdomain() {
|
||||||
|
load_plugin_textdomain(
|
||||||
|
'umbra-bookshelf',
|
||||||
|
false,
|
||||||
|
dirname(UMBRA_BOOKSHELF_BASENAME) . '/languages'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register the single top-level admin menu.
|
||||||
|
*
|
||||||
|
* Runs at priority 9 so the parent exists before any module appends its
|
||||||
|
* submenus at the default priority 10.
|
||||||
|
*/
|
||||||
|
public function register_admin_menu() {
|
||||||
|
add_menu_page(
|
||||||
|
__('Umbra Bookshelf', 'umbra-bookshelf'),
|
||||||
|
__('Umbra Bookshelf', 'umbra-bookshelf'),
|
||||||
|
'edit_posts',
|
||||||
|
self::MENU_SLUG,
|
||||||
|
[$this, 'render_dashboard'],
|
||||||
|
'dashicons-book-alt',
|
||||||
|
30
|
||||||
|
);
|
||||||
|
|
||||||
|
// First entry is conventionally a copy of the parent, pointing at the
|
||||||
|
// catalogue overview.
|
||||||
|
add_submenu_page(
|
||||||
|
self::MENU_SLUG,
|
||||||
|
__('Dashboard', 'umbra-bookshelf'),
|
||||||
|
__('Dashboard', 'umbra-bookshelf'),
|
||||||
|
'edit_posts',
|
||||||
|
self::MENU_SLUG,
|
||||||
|
[$this, 'render_dashboard']
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render the dashboard / catalogue overview.
|
||||||
|
*/
|
||||||
|
public function render_dashboard() {
|
||||||
|
if (!current_user_can('edit_posts')) {
|
||||||
|
wp_die(esc_html__('You do not have sufficient permissions to access this page.', 'umbra-bookshelf'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$counts = array(
|
||||||
|
'books' => $this->count_books(),
|
||||||
|
'series' => $this->count_series(),
|
||||||
|
);
|
||||||
|
|
||||||
|
?>
|
||||||
|
<div class="wrap">
|
||||||
|
<h1><?php esc_html_e('Umbra Bookshelf', 'umbra-bookshelf'); ?></h1>
|
||||||
|
|
||||||
|
<div class="umbra-bookshelf-dashboard">
|
||||||
|
<div class="umbra-bookshelf-dashboard-card">
|
||||||
|
<h2><?php esc_html_e('Books', 'umbra-bookshelf'); ?></h2>
|
||||||
|
<p class="umbra-bookshelf-dashboard-count"><?php echo esc_html(number_format_i18n($counts['books'])); ?></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="umbra-bookshelf-dashboard-card">
|
||||||
|
<h2><?php esc_html_e('Series', 'umbra-bookshelf'); ?></h2>
|
||||||
|
<p class="umbra-bookshelf-dashboard-count"><?php echo esc_html(number_format_i18n($counts['series'])); ?></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number of books visible to the current user.
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
private function count_books() {
|
||||||
|
$counts = wp_count_posts('book');
|
||||||
|
|
||||||
|
return isset($counts->publish) ? (int) $counts->publish : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number of series terms.
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
private function count_series() {
|
||||||
|
$terms = get_terms(array(
|
||||||
|
'taxonomy' => 'series',
|
||||||
|
'hide_empty' => false,
|
||||||
|
'fields' => 'count',
|
||||||
|
));
|
||||||
|
|
||||||
|
return is_wp_error($terms) ? 0 : (int) $terms;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize modules
|
||||||
|
*/
|
||||||
|
public function init_modules() {
|
||||||
|
if ($this->modules_initialized) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$this->modules_initialized = true;
|
||||||
|
|
||||||
|
// Catalogue module
|
||||||
|
new Umbra_Shortcodes();
|
||||||
|
new Umbra_GDPR();
|
||||||
|
new Umbra_Rating_Sync();
|
||||||
|
|
||||||
|
// Library module (post type, taxonomy, shortcodes, pen names)
|
||||||
|
Umbra_Bookshelf_Helper::instance();
|
||||||
|
|
||||||
|
// Email module (signup, unsubscribe, newsletter delivery)
|
||||||
|
Umbra_Simple_Email::instance();
|
||||||
|
|
||||||
|
if (is_admin()) {
|
||||||
|
new Umbra_Admin();
|
||||||
|
new Umbra_Migration();
|
||||||
|
Umbra_Bookshelf_Helper_Admin::instance();
|
||||||
|
Umbra_Simple_Email_Admin::instance();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plugin activation
|
||||||
|
*
|
||||||
|
* Runs each module's installer. See docs/KNOWN-ISSUES.md item 1: the
|
||||||
|
* catalogue and library modules both create bd_books / bd_series with
|
||||||
|
* different schemas, which is a pre-existing conflict carried over from
|
||||||
|
* the separate plugins and deliberately not resolved in this release.
|
||||||
|
*/
|
||||||
|
public function activate() {
|
||||||
|
$database = new Umbra_Database();
|
||||||
|
$database->create_tables();
|
||||||
|
|
||||||
|
Umbra_Bookshelf_Helper_Install::activate();
|
||||||
|
Umbra_Simple_Email_Install::activate();
|
||||||
|
|
||||||
|
flush_rewrite_rules();
|
||||||
|
|
||||||
|
// Preserved option key (existing installations already have this).
|
||||||
|
update_option('bd_bookshelf_activation_time', current_time('timestamp'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plugin deactivation
|
||||||
|
*/
|
||||||
|
public function deactivate() {
|
||||||
|
// Preserved cron hook names.
|
||||||
|
wp_clear_scheduled_hook('bd_bookshelf_sync_ratings');
|
||||||
|
wp_clear_scheduled_hook('bd_bookshelf_goodreads_refresh');
|
||||||
|
wp_clear_scheduled_hook('bd_bookshelf_amazon_scrape');
|
||||||
|
|
||||||
|
Umbra_Bookshelf_Helper_Install::deactivate();
|
||||||
|
Umbra_Simple_Email_Install::deactivate();
|
||||||
|
|
||||||
|
flush_rewrite_rules();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize plugin
|
||||||
|
*
|
||||||
|
* @return Umbra_Bookshelf
|
||||||
|
*/
|
||||||
|
function umbra_bookshelf() {
|
||||||
|
return Umbra_Bookshelf::instance();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start the plugin
|
||||||
|
umbra_bookshelf();
|
||||||
71
umbra-bookshelf/uninstall.php
Normal file
71
umbra-bookshelf/uninstall.php
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
<?php
|
||||||
|
// Copyright 2026 Umbra Tools (umbratools.com)
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Umbra Bookshelf Uninstall
|
||||||
|
*
|
||||||
|
* Fired when the plugin is deleted from the Plugins page. Removes every table
|
||||||
|
* and option owned by all three consolidated modules (books, library, email).
|
||||||
|
*
|
||||||
|
* Table names and option keys are the historical bd_* keys and are preserved
|
||||||
|
* verbatim -- that is what the data on disk is actually called.
|
||||||
|
*
|
||||||
|
* @package Umbra_Bookshelf
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!defined('WP_UNINSTALL_PLUGIN')) {
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Include database class
|
||||||
|
require_once __DIR__ . '/includes/books/class-umbra-database.php';
|
||||||
|
|
||||||
|
// Drop catalogue tables (bd_books, bd_series, bd_book_authors, ...)
|
||||||
|
$database = new Umbra_Database();
|
||||||
|
$database->drop_tables();
|
||||||
|
|
||||||
|
// Drop library and email tables.
|
||||||
|
// bd_books and bd_series are intentionally NOT repeated here: the catalogue
|
||||||
|
// module's drop_tables() above already covers them, and dropping the same table
|
||||||
|
// twice is only a wasted query.
|
||||||
|
global $wpdb;
|
||||||
|
|
||||||
|
$additional_tables = array(
|
||||||
|
// Library module
|
||||||
|
$wpdb->prefix . 'bd_book_meta',
|
||||||
|
$wpdb->prefix . 'bd_pen_names',
|
||||||
|
$wpdb->prefix . 'bd_reviews',
|
||||||
|
$wpdb->prefix . 'bd_ratings',
|
||||||
|
$wpdb->prefix . 'bd_review_shortlinks',
|
||||||
|
|
||||||
|
// Email module
|
||||||
|
$wpdb->prefix . 'bd_subscribers',
|
||||||
|
$wpdb->prefix . 'bd_newsletters',
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach ($additional_tables as $table) {
|
||||||
|
$wpdb->query("DROP TABLE IF EXISTS {$table}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove catalogue options and transients (preserved bd_bookshelf_* keys)
|
||||||
|
$wpdb->query("DELETE FROM {$wpdb->options} WHERE option_name LIKE 'bd_bookshelf_%'");
|
||||||
|
$wpdb->query("DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_bd_bookshelf_%'");
|
||||||
|
$wpdb->query("DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_timeout_bd_bookshelf_%'");
|
||||||
|
|
||||||
|
// Remove library options
|
||||||
|
delete_option('bdm_affiliate_codes');
|
||||||
|
delete_option('bdm_goodreads_author_id');
|
||||||
|
delete_option('bdm_awards');
|
||||||
|
|
||||||
|
// Remove email options
|
||||||
|
delete_option('bdm_gmail_address');
|
||||||
|
delete_option('bdm_gmail_password');
|
||||||
|
delete_option('bdm_sender_name');
|
||||||
|
delete_option('bdm_reply_to');
|
||||||
|
delete_option('bdm_gemini_api_key');
|
||||||
|
|
||||||
|
// Clear scheduled events (preserved hook names)
|
||||||
|
wp_clear_scheduled_hook('bd_bookshelf_sync_ratings');
|
||||||
|
wp_clear_scheduled_hook('bd_bookshelf_goodreads_refresh');
|
||||||
|
wp_clear_scheduled_hook('bd_bookshelf_amazon_scrape');
|
||||||
Reference in New Issue
Block a user