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)); } } }