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.
32 KiB
BD Bookshelf Plugin - Technical Specification v0.0.1
1. Overview
The bd-bookshelf plugin is a WordPress plugin designed to work alongside the bd-bookshelf theme. It provides book management via custom database tables, automated retail link generation via ISBN/ASIN, rating synchronization from Amazon and Goodreads via web scraping, marketing shortcodes, GDPR compliance, SEO metadata management, and book series management.
Plugin Slug: bd-bookshelf
Text Domain: bd-bookshelf
Requires WordPress: 6.0+
Requires PHP: 7.4+
License: GPLv2 or later
2. WordPress Requirements & Best Practices
This plugin MUST comply with all WordPress.org Plugin Directory guidelines and security best practices.
2.1 Licensing & Copyright
- All code must be compatible with GPLv2 or later
- Include copyright information in plugin header
- Include license file (
license.txtorLICENSE) - Third-party libraries must be GPL-compatible
- Provide attribution for bundled assets in
readme.txt
2.2 Plugin Header Requirements
/**
* Plugin Name: BD Bookshelf
* Plugin URI: https://authorbdmurphy.com/plugins/bd-bookshelf
* Description: Book management plugin for authors with ISBN tracking, retail links, and rating synchronization.
* Version: 0.0.1
* 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
*/
2.3 Security Requirements
Data Validation & Sanitization
- Never trust user input - validate all data before processing
- Use
sanitize_text_field(),sanitize_email(),absint(), etc. - Validate data types and ranges before database insertion
- Use
wp_unslash()to remove magic quotes from user input
Data Escaping
- Escape as late as possible - escape all output
- Use
esc_html()for HTML output - Use
esc_attr()for HTML attributes - Use
esc_url()for URLs - Use
esc_js()for inline JavaScript - Use
wp_kses()orwp_kses_post()for allowed HTML
Nonce Verification
- Always verify nonces on form submissions and AJAX requests
- Use
wp_nonce_field()in forms - Use
wp_create_nonce()andwp_verify_nonce()in AJAX - Name nonces with pattern:
bd-bookshelf_{action}_{object}_{id}
Capability Checks
- Check user capabilities before any privileged action
- Use
current_user_can()with appropriate capability - Never rely on user roles alone - use capabilities
- Minimum capability for most actions:
manage_options
SQL Injection Prevention
- Never concatenate user input into SQL queries
- Always use
$wpdb->prepare()with parameterized queries - Use
%sfor strings,%dfor integers,%ffor floats - Example:
$wpdb->prepare("SELECT * FROM {$table} WHERE id = %d", $id)
2.4 Database Best Practices
Table Creation
- Use
dbDelta()for table creation and updates - Include
utf8mb4charset for full Unicode support - Use
bigint(20) unsignedfor primary keys and foreign keys - Include
created_atandupdated_attimestamps - Add proper indexes for frequently queried columns
Table Naming
- Prefix all tables with
$wpdb->prefix(e.g.,wp_bd_books) - Use lowercase with underscores for table names
- Use singular nouns for table names (e.g.,
wp_bd_booknotwp_bd_books)
Query Optimization
- Use
$wpdb->get_results()for multiple rows - Use
$wpdb->get_row()for single row - Use
$wpdb->get_var()for single value - Use
$wpdb->query()for INSERT/UPDATE/DELETE - Always use
$wpdb->prepare()for parameterized queries
2.5 Code Standards
PHP Coding Standards
- Follow WordPress Coding Standards (WPCS)
- Use K&R bracing style (opening brace on same line)
- Use spaces, not tabs (4 spaces per indent)
- Use
snake_casefor function names and variables - Use
PascalCasefor class names - Prefix all functions with plugin slug (e.g.,
bd_bookshelf_) - Prefix all classes with
BDM_(e.g.,BDM_Book_Manager)
File Organization
bd-bookshelf/
├── bd-bookshelf.php (main plugin file)
├── readme.txt (WordPress.org format)
├── license.txt (GPLv2 license)
├── uninstall.php (cleanup on uninstall)
├── includes/
│ ├── class-bdm-book-manager.php
│ ├── class-bdm-rating-sync.php
│ ├── class-bdm-shortcodes.php
│ └── class-bdm-admin.php
├── admin/
│ ├── css/
│ ├── js/
│ └── partials/
├── public/
│ ├── css/
│ └── js/
├── languages/
│ └── bd-bookshelf.pot
└── assets/
├── icons/
└── images/
2.6 Hook Best Practices
Actions
- Use
add_action()with specific priority when order matters - Avoid hooks that fire too frequently (e.g.,
initwithout priority) - Prefix hook callback functions with plugin slug
- Example:
add_action('admin_menu', 'bd_bookshelf_admin_menu', 10);
Filters
- Always return a value from filter callbacks
- Use
has_filter()to check if filter exists before removing - Example:
add_filter('the_content', 'bd_bookshelf_shortcode_output', 20);
2.7 AJAX Security
// Admin AJAX
add_action('wp_ajax_bd_bookshelf_save_book', 'bd_bookshelf_ajax_save_book');
add_action('wp_ajax_nopriv_bd_bookshelf_save_book', 'bd_bookshelf_ajax_save_book');
function bd_bookshelf_ajax_save_book() {
// 1. Verify nonce
if (!wp_verify_nonce($_POST['nonce'], 'bd_bookshelf_save_book_' . $_POST['book_id'])) {
wp_send_json_error('Invalid nonce');
}
// 2. Check capabilities
if (!current_user_can('manage_options')) {
wp_send_json_error('Insufficient permissions');
}
// 3. Sanitize input
$title = sanitize_text_field($_POST['title']);
// 4. Process and respond
wp_send_json_success(['id' => $book_id]);
}
2.8 Internationalization
- Use
bd-bookshelftext domain throughout plugin - Wrap all user-facing strings in translation functions
- Use
esc_html_e()for escaped translated output - Use
esc_html__()for escaped translated return values - Include
.potfile in/languagesdirectory - Use
load_plugin_textdomain()in main plugin file
2.9 Uninstall Cleanup
- Implement
uninstall.phpfor cleanup when plugin is deleted - Remove all custom tables (prefix:
wp_bd_) - Remove all options (prefix:
bd_bookshelf_) - Remove all transients (prefix:
bd_bookshelf_) - Check
WP_UNINSTALL_PLUGINconstant before executing
2.10 Performance Best Practices
- Use transients for cached data (e.g., ratings)
- Implement proper pagination for large datasets
- Use background processing for heavy operations (WP-Cron)
- Minimize database queries in loops
- Use
$wpdb->get_results()withOBJECT_Kfor keyed results - Implement proper caching strategies
2.11 Plugin Check Compliance
The plugin MUST pass the WordPress Plugin Check tool with no errors:
Required Checks
- No PHP errors, warnings, or notices
- No security vulnerabilities flagged
- Proper data sanitization and escaping
- Nonce verification on all forms
- Capability checks on privileged actions
- Proper SQL query parameterization
Recommended Checks
- No deprecated function usage
- Proper hook priority settings
- Efficient database queries
- Proper error handling
2.12 Readme Requirements
=== BD Bookshelf ===
Contributors: bdmurphy
Requires at least: 6.0
Tested up to: 6.5
Requires PHP: 7.4
Stable tag: 0.0.1
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 ==
[Detailed description]
== Installation ==
[Step-by-step instructions]
== Frequently Asked Questions ==
[Common questions and answers]
== Changelog ==
[Version history]
== Upgrade Notice ==
[Upgrade information]
== Resources ==
[Third-party asset attributions]
2.13 Version Management
- Increment version number for each release
- Update
Stable taginreadme.txt - Update
Versionin plugin header - Update
Tested up tofor WordPress compatibility - Document changes in
Changelogsection
2.14 Privacy & GDPR Compliance
- Do not track users without explicit consent
- Provide privacy policy text for plugin data collection
- Implement cookie consent mechanism
- Allow users to opt-out of tracking
- Document all data collection in privacy policy
2.15 Third-Party Libraries
- Include only GPL-compatible libraries
- Bundle libraries locally (no CDN without consent)
- Include original source files
- Provide attribution in
readme.txt - Keep libraries updated for security
3. Core Entities
2.1 Book
The primary entity managed by the plugin. Stored in custom database tables for performance and future plugin integration.
| Field | Type | Description |
|---|---|---|
id |
bigint | Primary key (auto-increment) |
title |
varchar(255) | The book title |
description |
text | Book description/blurb |
cover_image |
varchar(500) | URL to book cover image |
isbn_ebook |
varchar(20) | ISBN for eBook format |
isbn_paperback |
varchar(20) | ISBN for Paperback format |
isbn_hardcover |
varchar(20) | ISBN for Hard Cover format |
isbn_audio |
varchar(20) | ISBN/Audio ASIN for Audio format |
amazon_asin |
varchar(20) | Amazon ASIN (may differ from ISBN) |
goodreads_id |
varchar(20) | Goodreads book ID |
rating_amazon |
decimal(3,2) | Latest Amazon rating (synced) |
rating_goodreads |
decimal(3,2) | Latest Goodreads rating (synced) |
last_sync |
datetime | Last rating synchronization timestamp |
series_id |
bigint | Reference to Series (optional) |
series_order |
int | Order within series (optional) |
created_at |
datetime | Creation timestamp |
updated_at |
datetime | Last update timestamp |
2.2 Book Author
Supports up to three authors per book.
| Field | Type | Description |
|---|---|---|
id |
bigint | Primary key |
book_id |
bigint | Reference to Book |
author_name |
varchar(255) | Author display name |
author_id |
bigint | WordPress user ID (if applicable) |
goodreads_author_id |
varchar(20) | Goodreads Author ID |
is_primary |
tinyint(1) | 1 if primary author |
sort_order |
int | Display order (1-3) |
2.3 Series
Book series management.
| Field | Type | Description |
|---|---|---|
id |
bigint | Primary key |
name |
varchar(255) | Series name |
description |
text | Series description |
cover_image |
varchar(500) | Series cover image URL |
created_at |
datetime | Creation timestamp |
2.4 Retail Site
Predefined and custom retail sites where books are sold.
| Field | Type | Description |
|---|---|---|
id |
bigint | Primary key |
name |
string | Site name (e.g., "Amazon") |
url_pattern |
text | URL pattern with {ISBN} placeholder |
icon |
varchar(255) | Icon identifier or URL |
affiliate_param |
varchar(50) | Query parameter for affiliate code |
affiliate_code |
varchar(100) | Affiliate code for this site |
is_custom |
tinyint(1) | 1 if user-created site |
is_active |
tinyint(1) | 1 if site is enabled |
created_at |
datetime | Creation timestamp |
Default Sites:
- Amazon (
amazon.com/dp/{ISBN}) -?tag= - Barnes & Noble (
barnesandnoble.com/w/{ISBN}) - TBD - Apple Books (
books.apple.com/book/{ISBN}) - TBD - Google Play Books (
play.google.com/store/books/details?id={ISBN}) - TBD - Kobo (
kobo.com/us/en/ebook/{ISBN}) - TBD
Note: Affiliate parameter names for B&N, Apple, Google, Kobo will be researched and provided in a specification update.
2.5 Book Retail Link
Maps books to retail sites with format-specific links.
| Field | Type | Description |
|---|---|---|
id |
bigint | Primary key |
book_id |
bigint | Reference to Book |
site_id |
bigint | Reference to Retail Site |
format |
varchar(20) | ebook, paperback, hardcover, audio |
url |
text | Generated or manual URL |
is_custom |
tinyint(1) | 1 if manually entered |
created_at |
datetime | Creation timestamp |
2.6 Custom Shortcode
User-defined shortcodes for URL redirection. Primary use case: books cannot link directly to competitor sites, so authors use shortcodes on their author site that redirect to retail/review pages.
| Field | Type | Description |
|---|---|---|
id |
bigint | Primary key |
slug |
varchar(100) | The shortcode identifier (unique) |
target_url |
text | Destination URL |
redirect_type |
varchar(10) | 301, 302, 307 |
description |
text | Internal note |
click_count |
bigint | Total clicks |
is_active |
tinyint(1) | 1 if live |
created_at |
datetime | Creation timestamp |
2.7 Click Analytics
Detailed click tracking for custom shortcodes.
| Field | Type | Description |
|---|---|---|
id |
bigint | Primary key |
shortcode_id |
bigint | Reference to Custom Shortcode |
clicked_at |
datetime | Click timestamp |
ip_address |
varchar(45) | User IP (anonymized for GDPR) |
user_agent |
text | Browser user agent |
referer |
varchar(500) | HTTP referer |
4. Core Modules
3.1 Book Management Module
Admin Page: Books > Add New Book
- Form to enter book title, description, cover image
- Four ISBN input fields (eBook, Paperback, Hard Cover, Audio)
- Amazon ASIN field (optional, auto-populated from ISBN if possible)
- Goodreads Book ID field (optional, auto-populated via search)
- Author selection (up to 3 authors per book)
- Series assignment (optional)
- Retail link grid showing generated and custom links
- SEO metadata fields (Title, Description, Keywords)
Data Storage: Custom database tables (not CPT)
3.2 Author Management
Admin Page: Books > Authors
- Manage author entries
- Link to WordPress user accounts
- Store Goodreads Author IDs
- Auto-detect author ID from theme configuration (fallback to manual entry in settings)
- Show all authors equally (no primary author highlighting)
- Primary author: uploaded photo; other authors: WordPress avatar
3.3 Series Management
Admin Page: Books > Series
- Create/edit/delete book series
- Assign books to series (one series per book)
- Books ordered by publication date within series
- Series description and cover image
- No frontend series pages (admin only, future plugin can expand)
3.4 Link Generation Engine
When an ISBN is entered or updated, the plugin automatically generates retail links using URL patterns.
Pattern Format:
https://www.amazon.com/dp/{ISBN}?tag={AFFILIATE}
Link Generation Rules:
- If ISBN is present for a format, generate links for all active retail sites
- If Amazon ASIN is provided, use it instead of ISBN for Amazon links
- Affiliate codes are appended using the site's query parameter format
- Custom retail sites (book/quill icon) do NOT support affiliate codes
- User can click on any generated link to edit the URL manually
URL Patterns: Will be researched and provided in a specification update.
3.5 Rating Synchronization Engine
Cron Job: Runs every 12 hours via WP-Cron.
Synchronization Process:
- Query all books with valid ISBNs or ASINs
- For each book:
- Fetch Amazon rating via robust web scraping of product page
- Fetch Goodreads rating via robust web scraping of book page
- Update
rating_amazonandrating_goodreadsfields - Update
last_synctimestamp - Log any errors or failures
Rating Fetch Failure Handling:
- If fetch fails, keep the last known rating indefinitely
- If no previously saved rating exists, show zero (0.0)
- Log failures for admin review
Web Scraping Implementation:
- Use
wp_remote_get()with realistic browser User-Agent (e.g., Chrome on Windows) - No proxy (direct requests only)
- Implement delays between requests (1-2 seconds) to avoid rate limiting
- Handle HTTP 429 (rate limiting) with longer delays
- Parse HTML with
DOMDocumentor regex for rating extraction - Amazon: Scrape main product page (
/dp/{ASIN}) - Goodreads: Scrape book page (
/book/show/{id})
Rating Display:
- Unicode stars (★☆) for star ratings
- Support half-star ratings (e.g., 4.5)
- Both star and numeric display (configurable)
3.6 Shortcode Suite
Format-Specific Retail Grids
[sell_ebook] // Renders eBook retail links grid
[sell_pb] // Renders Paperback retail links grid
[sell_hc] // Renders Hard Cover retail links grid
[sell_audio] // Renders Audio retail links grid
Parameters:
| Parameter | Default | Description |
|---|---|---|
id |
(required) | Book ID to display |
class |
(empty) | Additional CSS class |
columns |
auto | Number of columns (2, 3, 4, auto) |
show_icons |
true | Show site icons |
show_names |
true | Show site names |
icon_style |
simple |
simple (letter/abbreviation), uploaded (user images) |
Display:
- Grid layout (configurable columns), use as much screen as possible
- Option for uploaded images/icons, fallback to simple icons
- Responsive design for mobile devices
Note: The destination for shortcodes can be any website, including external sites. The local site is not required.
Rating Shortcode
[rating title="Pandemic Hacker"] // Shows rating for specific book by title
[rating title="Pandemic Hacker" format="stars"] // Star display format (★☆)
[rating title="Pandemic Hacker" format="numeric"] // Numeric display format (4.5/5)
[rating title="Pandemic Hacker" format="both"] // Both stars and numeric
Parameters:
| Parameter | Default | Description |
|---|---|---|
title |
(required) | Book title to display rating for |
format |
both |
stars (Unicode ★☆), numeric, both |
Note: Half-star ratings are supported (e.g., 4.5 stars).
Custom Shortcodes
[my-promo] // Redirects to target URL (301, 302, or 307)
3.7 SEO Metadata Module
Per-Book SEO Fields:
- Custom meta title (overrides default)
- Meta description
- Meta keywords
- Open Graph tags
- Twitter Card tags
Implementation:
- Stored in custom table (
bd_book_seo) - Injected into
<head>viawp_headaction - Compatible with Yoast/RankMath (does not override if present)
- Generates sitemap entries for books (does not rely on other plugins)
- Sitemap priority: Based on rating (higher rating = higher priority)
- Sitemap change frequency: Monthly
Stretch Goal: Links to social references (Facebook, etc.) for enhanced search engine results.
3.8 GDPR Compliance Module
Features:
- Privacy consent banner at bottom of page (not blocking modal)
- Must accept to continue browsing (not dismissible)
- Cookie tracking for affiliate links only
- Example privacy policy text provided (common internet text)
- Opt-in/opt-out for tracking
Implementation:
- JavaScript-based consent banner
bd_bookshelf_gdpr_consentuser meta for consent status- Cookie:
bd_bookshelf_consent(30-day expiry, reappears after expiry) - Cookie for affiliate tracking:
bd_bookshelf_affiliate(30-day expiry, standard) - Cookie stores: Just the affiliate code
Data Collected:
- Affiliate cookie for tracking (not click tracking)
3.9 Custom Shortcode Manager
Admin Page: Books > Shortcodes
Primary Use Case: URL shortening for books. Book sites do not allow links to competitor sites. Having a link for a review needs to be to the author's site, with a short code that links to the seller review page.
Features:
- Create/edit/delete custom shortcodes
- Define slug, target URL, and redirect type (301/302/307)
- View click statistics with IP/user agent tracking
- Bulk import/export functionality
Analytics Tracking:
- Total click count
- Click count with timestamp
- Click count with full IP address (stored for security tracking)
- User agent tracking
- Referrer tracking
- Data retention: 1 year
5. Database Schema
4.1 wp_bd_books
| Column | Type | Description |
|---|---|---|
id |
bigint(20) unsigned | Primary key (auto-increment) |
title |
varchar(255) | Book title |
description |
text | Book description |
cover_image |
varchar(500) | Cover image URL |
isbn_ebook |
varchar(20) | eBook ISBN |
isbn_paperback |
varchar(20) | Paperback ISBN |
isbn_hardcover |
varchar(20) | Hard Cover ISBN |
isbn_audio |
varchar(20) | Audio ISBN |
amazon_asin |
varchar(20) | Amazon ASIN |
goodreads_id |
varchar(20) | Goodreads Book ID |
rating_amazon |
decimal(3,2) | Amazon rating |
rating_goodreads |
decimal(3,2) | Goodreads rating |
last_sync |
datetime | Last sync timestamp |
series_id |
bigint(20) unsigned | Series ID (nullable) |
series_order |
int(11) | Order within series |
created_at |
datetime | Creation timestamp |
updated_at |
datetime | Update timestamp |
4.2 wp_bd_book_authors
| Column | Type | Description |
|---|---|---|
id |
bigint(20) unsigned | Primary key |
book_id |
bigint(20) unsigned | Book ID |
author_name |
varchar(255) | Author display name |
author_id |
bigint(20) unsigned | WordPress user ID |
goodreads_author_id |
varchar(20) | Goodreads Author ID |
is_primary |
tinyint(1) | 1 if primary author |
sort_order |
int(11) | Display order (1-3) |
4.3 wp_bd_series
| Column | Type | Description |
|---|---|---|
id |
bigint(20) unsigned | Primary key |
name |
varchar(255) | Series name |
description |
text | Series description |
cover_image |
varchar(500) | Series cover image |
created_at |
datetime | Creation timestamp |
4.4 wp_bd_retail_sites
| Column | Type | Description |
|---|---|---|
id |
bigint(20) unsigned | Primary key |
name |
varchar(100) | Site name |
url_pattern |
text | URL pattern with {ISBN} |
icon |
varchar(255) | Icon file or URL |
affiliate_param |
varchar(50) | Query param for affiliate code |
affiliate_code |
varchar(100) | Affiliate code |
is_custom |
tinyint(1) | 1 if user-created |
is_active |
tinyint(1) | 1 if enabled |
created_at |
datetime | Creation timestamp |
4.5 wp_bd_retail_links
| Column | Type | Description |
|---|---|---|
id |
bigint(20) unsigned | Primary key |
book_id |
bigint(20) unsigned | Book ID |
site_id |
bigint(20) unsigned | Retail Site ID |
format |
varchar(20) | eBook, paperback, hardcover, audio |
url |
text | Generated or custom URL |
is_custom |
tinyint(1) | 1 if manually created |
created_at |
datetime | Creation timestamp |
4.6 wp_bd_shortcodes
| Column | Type | Description |
|---|---|---|
id |
bigint(20) unsigned | Primary key |
slug |
varchar(100) | Shortcode identifier (unique) |
target_url |
text | Destination URL |
redirect_type |
varchar(10) | 301, 302, 307 |
description |
text | Internal note |
click_count |
bigint(20) | Total clicks |
is_active |
tinyint(1) | 1 if live |
created_at |
datetime | Creation timestamp |
4.7 wp_bd_shortcode_clicks
| Column | Type | Description |
|---|---|---|
id |
bigint(20) unsigned | Primary key |
shortcode_id |
bigint(20) unsigned | Shortcode ID |
clicked_at |
datetime | Click timestamp |
ip_address |
varchar(45) | User IP (stored in full for security) |
user_agent |
text | Browser user agent |
referer |
varchar(500) | HTTP referer |
4.8 wp_bd_book_seo
| Column | Type | Description |
|---|---|---|
id |
bigint(20) unsigned | Primary key |
book_id |
bigint(20) unsigned | Book ID (unique) |
seo_title |
varchar(255) | Custom SEO title |
seo_description |
text | Custom SEO description |
seo_keywords |
varchar(500) | Custom SEO keywords |
og_title |
varchar(255) | Open Graph title |
og_description |
text | Open Graph description |
og_image |
varchar(500) | Open Graph image |
created_at |
datetime | Creation timestamp |
updated_at |
datetime | Update timestamp |
6. Cron Job Specification
5.1 Rating Sync Job
Hook Name: bd_bookshelf_sync_ratings
Schedule: Every 12 hours (twicedaily)
Process:
1. Get all books with valid ISBNs or ASINs
2. For each book:
a. If Amazon ASIN or ISBN exists:
- Scrape Amazon product page with robust implementation
- Parse rating value from HTML
- Update wp_bd_books.rating_amazon
b. If Goodreads ID exists:
- Scrape Goodreads book page with robust implementation
- Parse rating value from HTML
- Update wp_bd_books.rating_goodreads
c. Update wp_bd_books.last_sync timestamp
3. Log results for admin display
Error Handling:
- Retry failed fetches up to 3 times with exponential backoff
- If fetch fails, keep last known rating
- If no previous rating, show 0.0
- Log errors for admin review
Web Scraping Implementation:
- Use
wp_remote_get()with appropriate headers - Set realistic User-Agent string
- Implement delays between requests (1-2 seconds)
- Handle HTTP 429 (rate limiting) with longer delays
- Parse HTML with
DOMDocumentor regex for rating extraction
7. Admin Interface
6.1 Books Menu
- All Books: List view with columns for Title, ISBNs, Ratings, Last Sync
- Add New Book: Book entry form
- Authors: Manage author entries
- Series: Manage book series
- Rating Sync: Manual sync trigger and status display
- Settings: Affiliate codes, sync interval, GDPR options
6.2 Shortcodes Menu
- All Shortcodes: List of custom shortcodes with click stats
- Add New Shortcode: Create new redirect shortcode
- Analytics: Detailed click analytics with IP/user agent
- Import/Export: Bulk shortcode management
6.3 Settings Page
- Affiliate Codes: Per-site affiliate code configuration
- Author IDs: Manual Goodreads Author ID entry (fallback)
- Sync Settings: Cron interval configuration
- GDPR: Consent banner text, cookie duration, privacy page
- SEO: Default meta tag patterns, sitemap settings
8. Theme Integration
7.1 Functions Available
// Get book data
bd_bookshelf_get_book( $book_id );
// Get books by series
bd_bookshelf_get_books_by_series( $series_id );
// Get retail links for a book
bd_bookshelf_get_retail_links( $book_id, $format );
// Get book rating
bd_bookshelf_get_rating( $book_id );
// Get author information
bd_bookshelf_get_book_authors( $book_id );
// Get series information
bd_bookshelf_get_series( $series_id );
// Check if GDPR consent given
bd_bookshelf_has_consent();
7.2 Template Tags
// Display book rating
bd_bookshelf_the_rating( $book_id, $format );
// Display retail grid
bd_bookshelf_the_retail_grid( $book_id, $format );
// Display series books
bd_bookshelf_the_series_books( $series_id );
9. Security Implementation
9.1 Data Validation & Sanitization
All user input must be validated and sanitized before processing:
// Example: Book form processing
$title = sanitize_text_field($_POST['title']);
$description = wp_kses_post($_POST['description']);
$isbn = preg_replace('/[^0-9X]/i', '', $_POST['isbn']);
$email = sanitize_email($_POST['author_email']);
$amount = absint($_POST['rating']);
9.2 Data Escaping
All output must be escaped at the point of display:
// HTML output
echo esc_html($book->title);
echo esc_attr($book->isbn);
echo esc_url($book->affiliate_url);
echo wp_kses_post($book->description);
9.3 Nonce Verification
All forms and AJAX requests must include nonce verification:
// In form
wp_nonce_field('bd_bookshelf_save_book_' . $book_id, 'bd_bookshelf_nonce');
// In processing
if (!wp_verify_nonce($_POST['bd_bookshelf_nonce'], 'bd_bookshelf_save_book_' . $book_id)) {
wp_die('Security check failed');
}
// AJAX
wp_localize_script('bd-bookshelf-admin', 'bdBookshelfAjax', [
'ajaxurl' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('bd_bookshelf_ajax_nonce'),
]);
9.4 Capability Checks
All privileged actions must verify user capabilities:
function bd_bookshelf_save_book() {
if (!current_user_can('manage_options')) {
wp_send_json_error(['message' => 'Insufficient permissions']);
}
// Process save...
}
9.5 SQL Injection Prevention
All database queries must use prepared statements:
// Correct
$book = $wpdb->get_row(
$wpdb->prepare(
"SELECT * FROM {$wpdb->prefix}bd_books WHERE id = %d",
$book_id
)
);
// Incorrect - NEVER do this
$book = $wpdb->get_row("SELECT * FROM {$wpdb->prefix}bd_books WHERE id = $book_id");
9.6 AJAX Security
// Register AJAX handlers
add_action('wp_ajax_bd_bookshelf_save_book', 'bd_bookshelf_ajax_save_book');
add_action('wp_ajax_bd_bookshelf_delete_book', 'bd_bookshelf_ajax_delete_book');
function bd_bookshelf_ajax_save_book() {
// 1. Verify nonce
if (!wp_verify_nonce($_POST['nonce'], 'bd_bookshelf_save_book')) {
wp_send_json_error('Invalid nonce');
}
// 2. Check capabilities
if (!current_user_can('manage_options')) {
wp_send_json_error('Insufficient permissions');
}
// 3. Sanitize all input
$book_id = absint($_POST['book_id']);
$title = sanitize_text_field($_POST['title']);
$isbn = preg_replace('/[^0-9X]/i', '', $_POST['isbn']);
// 4. Validate data
if (empty($title)) {
wp_send_json_error('Title is required');
}
// 5. Process with prepared statements
$result = $wpdb->update(
$wpdb->prefix . 'bd_books',
['title' => $title, 'isbn_ebook' => $isbn],
['id' => $book_id],
['%s', '%s'],
['%d']
);
wp_send_json_success(['message' => 'Book saved', 'id' => $book_id]);
}
9.7 File Upload Security
If plugin allows file uploads (e.g., book covers):
function bd_bookshelf_handle_upload() {
// Check capability
if (!current_user_can('upload_files')) {
wp_die('Unauthorized');
}
// Validate file type
$allowed_types = ['image/jpeg', 'image/png', 'image/webp'];
if (!in_array($_FILES['file']['type'], $allowed_types)) {
wp_die('Invalid file type');
}
// Validate file size (max 5MB)
if ($_FILES['file']['size'] > 5 * 1024 * 1024) {
wp_die('File too large');
}
// Use WordPress upload handler
$upload = wp_handle_upload($_FILES['file'], ['test_form' => false]);
if (isset($upload['url'])) {
// Store $upload['url'] in database
}
}
9.8 Error Handling
// Log errors for debugging (not exposed to users)
error_log('BD Bookshelf Error: ' . $error_message);
// Display generic error to users
wp_die('An error occurred. Please try again later.');
// For AJAX, send JSON error
wp_send_json_error(['message' => 'An error occurred']);
10. Data Migration
9.1 Migration Tool
Admin Page: Books > Import
Features:
- Import books from WordPress posts/CPTs
- CSV import functionality
- Map existing post fields to plugin fields
- Preserve ISBNs, ratings, and metadata
- Batch processing for large catalogs
Migration Process:
- Select source (WordPress posts or CSV file)
- Map fields (title, description, ISBNs, etc.)
- Preview import data
- Execute import with progress bar
- Report results (success/failure counts)
11. Version History
v0.0.1 (Current)
- Initial specification
- Core entity definitions
- Module architecture
- Database schema
- Shortcode specifications
- Custom database tables (not CPT)
- Web scraping for ratings (robust implementation)
- Up to 3 authors per book
- Series management (one series per book, ordered by publication date)
- Detailed click analytics with full IP storage
- Unicode stars for rating display with half-star support
- Data migration tool from existing WordPress books
- GDPR banner (must accept, 30-day cookie)
- Sitemap with rating-based priority
- WordPress Requirements & Best Practices section added
- Security implementation details (validation, escaping, nonces, capability checks)
- Plugin Check compliance requirements
- Internationalization guidelines
- File organization structure
- Uninstall cleanup procedures