prefix . 'codebold_redirects'; } public static function not_found_table(): string { global $wpdb; return $wpdb->prefix . 'codebold_redirect_404s'; } public static function maybe_upgrade(): void { if ( get_option( self::OPTION_DB_VERSION ) === self::DB_VERSION ) { return; } self::install(); } public static function install(): void { global $wpdb; require_once ABSPATH . 'wp-admin/includes/upgrade.php'; $charset = $wpdb->get_charset_collate(); $redirects = self::redirects_table(); $not_found = self::not_found_table(); // dbDelta is particular: two spaces after PRIMARY KEY, one column per line, and // KEY rather than INDEX. Getting any of that wrong makes it silently re-add the // index on every run instead of failing. // // source is VARCHAR(2000) and never indexed itself — utf8mb4 caps an index at 768 // characters, and real addresses with long query strings are longer than that. // source_hash is what the lookup uses. dbDelta( "CREATE TABLE {$redirects} ( id bigint(20) unsigned NOT NULL AUTO_INCREMENT, source varchar(2000) NOT NULL, source_hash char(32) NOT NULL, match_type varchar(10) NOT NULL DEFAULT 'exact', target text NOT NULL, target_type varchar(10) NOT NULL DEFAULT 'url', target_id bigint(20) unsigned NOT NULL DEFAULT 0, status_code smallint(5) unsigned NOT NULL DEFAULT 301, case_sensitive tinyint(1) NOT NULL DEFAULT 0, pass_query tinyint(1) NOT NULL DEFAULT 1, conditions text NULL, starts_at datetime NULL, ends_at datetime NULL, enabled tinyint(1) NOT NULL DEFAULT 1, priority int(11) NOT NULL DEFAULT 0, group_name varchar(100) NOT NULL DEFAULT '', note varchar(255) NOT NULL DEFAULT '', origin varchar(20) NOT NULL DEFAULT 'manual', post_id bigint(20) unsigned NOT NULL DEFAULT 0, hits bigint(20) unsigned NOT NULL DEFAULT 0, last_hit datetime NULL, created_at datetime NOT NULL, updated_at datetime NOT NULL, PRIMARY KEY (id), KEY source_hash (source_hash), KEY match_type (match_type,enabled), KEY post_id (post_id), KEY target (target_type,target_id) ) {$charset};" ); // One row per address, not per visit. A bot asking for /wp-login.php four thousand // times is one row with a count of four thousand, which is what anybody reading // the list actually wants to know — and the table stays small enough to keep. // // No IP address column, deliberately. Knowing that an address is broken does not // need knowing who found it, and a log of visitor IPs is personal data the site // owner would then have to account for. dbDelta( "CREATE TABLE {$not_found} ( id bigint(20) unsigned NOT NULL AUTO_INCREMENT, url varchar(2000) NOT NULL, url_hash char(32) NOT NULL, hits bigint(20) unsigned NOT NULL DEFAULT 1, first_seen datetime NOT NULL, last_seen datetime NOT NULL, referrer varchar(1000) NOT NULL DEFAULT '', user_agent varchar(255) NOT NULL DEFAULT '', is_bot tinyint(1) NOT NULL DEFAULT 0, ignored tinyint(1) NOT NULL DEFAULT 0, PRIMARY KEY (id), UNIQUE KEY url_hash (url_hash), KEY last_seen (last_seen), KEY first_seen (ignored,first_seen) ) {$charset};" ); update_option( self::OPTION_DB_VERSION, self::DB_VERSION, true ); Cleanup::schedule(); } }