309 lines
10 KiB
PHP
309 lines
10 KiB
PHP
<?php
|
|
// Copyright 2026 Umbra Tools (umbratools.com)
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
/**
|
|
* Database management class
|
|
*
|
|
* @package BD_Bookshelf
|
|
*/
|
|
|
|
if (!defined('ABSPATH')) {
|
|
exit;
|
|
}
|
|
|
|
class BDM_Database {
|
|
|
|
/**
|
|
* Get table names with prefix
|
|
*/
|
|
public static function table($name) {
|
|
global $wpdb;
|
|
return $wpdb->prefix . 'bd_' . $name;
|
|
}
|
|
|
|
/**
|
|
* Create all database tables
|
|
*/
|
|
public function create_tables() {
|
|
global $wpdb;
|
|
|
|
$charset_collate = $wpdb->get_charset_collate();
|
|
|
|
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
|
|
|
|
// Books table
|
|
$sql_books = "CREATE TABLE " . self::table('books') . " (
|
|
id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
|
title varchar(255) NOT NULL,
|
|
slug varchar(255) NOT NULL,
|
|
description longtext,
|
|
cover_image varchar(500),
|
|
isbn_ebook varchar(20),
|
|
isbn_paperback varchar(20),
|
|
isbn_hardcover varchar(20),
|
|
isbn_audio varchar(20),
|
|
asin_ebook varchar(20),
|
|
asin_paperback varchar(20),
|
|
asin_hardcover varchar(20),
|
|
asin_audio varchar(20),
|
|
amazon_asin varchar(20),
|
|
goodreads_id varchar(20),
|
|
rating_amazon decimal(3,2) DEFAULT 0.00,
|
|
rating_goodreads decimal(3,2) DEFAULT 0.00,
|
|
last_sync datetime,
|
|
series_id bigint(20) unsigned,
|
|
created_at datetime DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
PRIMARY KEY (id),
|
|
UNIQUE KEY uk_slug (slug),
|
|
KEY idx_series_id (series_id)
|
|
) $charset_collate;";
|
|
|
|
// Book authors table
|
|
$sql_authors = "CREATE TABLE " . self::table('book_authors') . " (
|
|
id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
|
book_id bigint(20) unsigned NOT NULL,
|
|
author_name varchar(255) NOT NULL,
|
|
author_id bigint(20) unsigned,
|
|
goodreads_author_id varchar(20),
|
|
is_primary tinyint(1) DEFAULT 0,
|
|
sort_order int(11) DEFAULT 0,
|
|
PRIMARY KEY (id),
|
|
KEY idx_book_id (book_id),
|
|
KEY idx_author_id (author_id)
|
|
) $charset_collate;";
|
|
|
|
// Series table
|
|
$sql_series = "CREATE TABLE " . self::table('series') . " (
|
|
id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
|
name varchar(255) NOT NULL,
|
|
description longtext,
|
|
cover_image varchar(500),
|
|
created_at datetime DEFAULT CURRENT_TIMESTAMP,
|
|
PRIMARY KEY (id)
|
|
) $charset_collate;";
|
|
|
|
// Retail sites table
|
|
$sql_sites = "CREATE TABLE " . self::table('retail_sites') . " (
|
|
id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
|
name varchar(100) NOT NULL,
|
|
url_pattern text NOT NULL,
|
|
icon varchar(255),
|
|
affiliate_param varchar(50),
|
|
affiliate_code varchar(100),
|
|
is_custom tinyint(1) DEFAULT 0,
|
|
is_active tinyint(1) DEFAULT 1,
|
|
created_at datetime DEFAULT CURRENT_TIMESTAMP,
|
|
PRIMARY KEY (id)
|
|
) $charset_collate;";
|
|
|
|
// Retail links table
|
|
$sql_links = "CREATE TABLE " . self::table('retail_links') . " (
|
|
id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
|
book_id bigint(20) unsigned NOT NULL,
|
|
site_id bigint(20) unsigned NOT NULL,
|
|
format varchar(20) NOT NULL,
|
|
url text NOT NULL,
|
|
is_custom tinyint(1) DEFAULT 0,
|
|
created_at datetime DEFAULT CURRENT_TIMESTAMP,
|
|
PRIMARY KEY (id),
|
|
KEY idx_book_id (book_id),
|
|
KEY idx_site_id (site_id),
|
|
KEY idx_format (format)
|
|
) $charset_collate;";
|
|
|
|
// Custom shortcodes table
|
|
$sql_shortcodes = "CREATE TABLE " . self::table('shortcodes') . " (
|
|
id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
|
slug varchar(100) NOT NULL,
|
|
target_url text NOT NULL,
|
|
redirect_type varchar(10) DEFAULT '301',
|
|
description text,
|
|
click_count bigint(20) DEFAULT 0,
|
|
is_active tinyint(1) DEFAULT 1,
|
|
created_at datetime DEFAULT CURRENT_TIMESTAMP,
|
|
PRIMARY KEY (id),
|
|
UNIQUE KEY uk_slug (slug)
|
|
) $charset_collate;";
|
|
|
|
// Shortcode clicks table
|
|
$sql_clicks = "CREATE TABLE " . self::table('shortcode_clicks') . " (
|
|
id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
|
shortcode_id bigint(20) unsigned NOT NULL,
|
|
clicked_at datetime DEFAULT CURRENT_TIMESTAMP,
|
|
ip_address varchar(45) NOT NULL,
|
|
user_agent text,
|
|
referer varchar(500),
|
|
PRIMARY KEY (id),
|
|
KEY idx_shortcode_id (shortcode_id),
|
|
KEY idx_clicked_at (clicked_at)
|
|
) $charset_collate;";
|
|
|
|
// Book SEO table
|
|
$sql_seo = "CREATE TABLE " . self::table('book_seo') . " (
|
|
id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
|
book_id bigint(20) unsigned NOT NULL,
|
|
seo_title varchar(255),
|
|
seo_description text,
|
|
seo_keywords varchar(500),
|
|
og_title varchar(255),
|
|
og_description text,
|
|
og_image varchar(500),
|
|
created_at datetime DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
PRIMARY KEY (id),
|
|
UNIQUE KEY uk_book_id (book_id)
|
|
) $charset_collate;";
|
|
|
|
// Authors table
|
|
$sql_authors_table = "CREATE TABLE " . self::table('authors') . " (
|
|
id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
|
display_name varchar(255) NOT NULL,
|
|
email varchar(100),
|
|
website varchar(500),
|
|
bio text,
|
|
goodreads_author_id varchar(20),
|
|
photo_url varchar(500),
|
|
created_at datetime DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
PRIMARY KEY (id)
|
|
) $charset_collate;";
|
|
|
|
dbDelta($sql_books);
|
|
dbDelta($sql_authors);
|
|
dbDelta($sql_series);
|
|
dbDelta($sql_sites);
|
|
dbDelta($sql_links);
|
|
dbDelta($sql_shortcodes);
|
|
dbDelta($sql_clicks);
|
|
dbDelta($sql_seo);
|
|
dbDelta($sql_authors_table);
|
|
|
|
// Migration: add asin columns if missing
|
|
$this->maybe_add_columns();
|
|
|
|
// Insert default retail sites
|
|
$this->insert_default_sites();
|
|
|
|
// Store schema version
|
|
update_option('bd_bookshelf_db_version', BD_BOOKSHELF_VERSION);
|
|
}
|
|
|
|
/**
|
|
* Add missing columns to existing tables
|
|
*/
|
|
private function maybe_add_columns() {
|
|
global $wpdb;
|
|
|
|
$table = self::table('books');
|
|
$columns = $wpdb->get_col("DESCRIBE {$table}", 0);
|
|
|
|
$missing = [
|
|
'asin_ebook' => "ALTER TABLE {$table} ADD COLUMN asin_ebook varchar(20) AFTER isbn_audio",
|
|
'asin_paperback' => "ALTER TABLE {$table} ADD COLUMN asin_paperback varchar(20) AFTER asin_ebook",
|
|
'asin_hardcover' => "ALTER TABLE {$table} ADD COLUMN asin_hardcover varchar(20) AFTER asin_paperback",
|
|
'asin_audio' => "ALTER TABLE {$table} ADD COLUMN asin_audio varchar(20) AFTER asin_hardcover",
|
|
];
|
|
|
|
foreach ($missing as $col => $sql) {
|
|
if (!in_array($col, $columns)) {
|
|
$wpdb->query($sql);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Insert default retail sites
|
|
*/
|
|
private function insert_default_sites() {
|
|
global $wpdb;
|
|
|
|
$table = self::table('retail_sites');
|
|
|
|
$default_sites = [
|
|
[
|
|
'name' => 'Amazon',
|
|
'url_pattern' => 'https://www.amazon.com/dp/{ISBN}',
|
|
'icon' => 'amazon',
|
|
'affiliate_param' => 'tag',
|
|
'affiliate_code' => '',
|
|
'is_custom' => 0,
|
|
'is_active' => 1,
|
|
],
|
|
[
|
|
'name' => 'Barnes & Noble',
|
|
'url_pattern' => 'https://www.barnesandnoble.com/w/?ean={ISBN}',
|
|
'icon' => 'barnes-noble',
|
|
'affiliate_param' => '',
|
|
'affiliate_code' => '',
|
|
'is_custom' => 0,
|
|
'is_active' => 1,
|
|
],
|
|
[
|
|
'name' => 'Apple Books',
|
|
'url_pattern' => 'https://books.apple.com/book/{ISBN}',
|
|
'icon' => 'apple',
|
|
'affiliate_param' => '',
|
|
'affiliate_code' => '',
|
|
'is_custom' => 0,
|
|
'is_active' => 1,
|
|
],
|
|
[
|
|
'name' => 'Google Play',
|
|
'url_pattern' => 'https://play.google.com/store/search?q={ISBN}&c=books',
|
|
'icon' => 'google',
|
|
'affiliate_param' => '',
|
|
'affiliate_code' => '',
|
|
'is_custom' => 0,
|
|
'is_active' => 1,
|
|
],
|
|
[
|
|
'name' => 'Kobo',
|
|
'url_pattern' => 'https://www.kobo.com/us/en/ebook/{ISBN}',
|
|
'icon' => 'kobo',
|
|
'affiliate_param' => '',
|
|
'affiliate_code' => '',
|
|
'is_custom' => 0,
|
|
'is_active' => 1,
|
|
],
|
|
];
|
|
|
|
foreach ($default_sites as $site) {
|
|
$exists = $wpdb->get_var(
|
|
$wpdb->prepare(
|
|
"SELECT COUNT(*) FROM {$table} WHERE name = %s",
|
|
$site['name']
|
|
)
|
|
);
|
|
|
|
if (!$exists) {
|
|
$wpdb->insert($table, $site);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Drop all plugin tables
|
|
*/
|
|
public function drop_tables() {
|
|
global $wpdb;
|
|
|
|
$tables = [
|
|
'books',
|
|
'book_authors',
|
|
'authors',
|
|
'series',
|
|
'retail_sites',
|
|
'retail_links',
|
|
'shortcodes',
|
|
'shortcode_clicks',
|
|
'book_seo',
|
|
];
|
|
|
|
foreach ($tables as $table) {
|
|
$wpdb->query("DROP TABLE IF EXISTS " . self::table($table));
|
|
}
|
|
}
|
|
}
|