get_var( $wpdb->prepare( 'SHOW INDEX FROM ' . $table . ' WHERE Key_name = %s', $index ) // phpcs:ignore ); if ( $exists ) { $wpdb->query( 'ALTER TABLE ' . $table . ' DROP INDEX ' . $index ); // phpcs:ignore } } public static function table( $name ) { global $wpdb; return $wpdb->prefix . 'argus_' . $name; } public static function install() { global $wpdb; require_once ABSPATH . 'wp-admin/includes/upgrade.php'; $charset_collate = $wpdb->get_charset_collate(); $events = self::table( 'events' ); $findings = self::table( 'findings' ); $bans = self::table( 'bans' ); $policy_decisions = self::table( 'policy_decisions' ); $integrity_baseline = self::table( 'integrity_baseline' ); $login_attempts = self::table( 'login_attempts' ); $vuln_cache = self::table( 'vuln_cache' ); $quarantine = self::table( 'quarantine' ); $cache_stats_daily = self::table( 'cache_stats_daily' ); $cache_discovered_urls = self::table( 'cache_discovered_urls' ); $cache_warm_queue = self::table( 'cache_warm_queue' ); $anis_reputation = self::table( 'anis_reputation' ); $vuln_status = self::table( 'vuln_status' ); $scan_history = self::table( 'scan_history' ); $backups = self::table( 'backups' ); $sql = "CREATE TABLE {$events} ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, event_type VARCHAR(40) NOT NULL, severity VARCHAR(20) NOT NULL DEFAULT 'info', ip VARCHAR(45) NULL, summary VARCHAR(255) NOT NULL, payload LONGTEXT NULL, created_at DATETIME NOT NULL, PRIMARY KEY (id), KEY event_type (event_type), KEY created_at (created_at), KEY ip (ip) ) {$charset_collate};"; dbDelta( $sql ); $sql = "CREATE TABLE {$findings} ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, category VARCHAR(40) NOT NULL, severity VARCHAR(20) NOT NULL, title VARCHAR(255) NOT NULL, detail LONGTEXT NULL, remediation_hint LONGTEXT NULL, evidence LONGTEXT NULL, status VARCHAR(20) NOT NULL DEFAULT 'open', first_seen_at DATETIME NOT NULL, last_seen_at DATETIME NOT NULL, PRIMARY KEY (id), KEY category (category), KEY status (status), KEY severity (severity) ) {$charset_collate};"; dbDelta( $sql ); $sql = "CREATE TABLE {$bans} ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, ip VARCHAR(45) NOT NULL, source VARCHAR(40) NOT NULL, reason VARCHAR(255) NOT NULL, evidence LONGTEXT NULL, ban_level VARCHAR(20) NOT NULL DEFAULT 'temporary', created_at DATETIME NOT NULL, expires_at DATETIME NULL, recovery_eligible_at DATETIME NULL, lifted_at DATETIME NULL, PRIMARY KEY (id), KEY ip (ip), KEY expires_at (expires_at) ) {$charset_collate};"; dbDelta( $sql ); self::drop_index_if_exists( $bans, 'ip_active' ); $sql = "CREATE TABLE {$policy_decisions} ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, ip VARCHAR(45) NOT NULL, trigger_type VARCHAR(40) NOT NULL, action VARCHAR(20) NOT NULL, rule_name VARCHAR(100) NULL, reason VARCHAR(255) NOT NULL, observation_only TINYINT(1) NOT NULL DEFAULT 0, mode VARCHAR(20) NOT NULL, created_at DATETIME NOT NULL, PRIMARY KEY (id), KEY ip (ip), KEY created_at (created_at) ) {$charset_collate};"; dbDelta( $sql ); $sql = "CREATE TABLE {$integrity_baseline} ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, file_path VARCHAR(500) NOT NULL, file_hash CHAR(64) NOT NULL, file_size BIGINT UNSIGNED NOT NULL, file_mtime DATETIME NOT NULL, category VARCHAR(20) NOT NULL, first_seen_at DATETIME NOT NULL, last_checked_at DATETIME NOT NULL, PRIMARY KEY (id), UNIQUE KEY file_path (file_path(191)), KEY category (category) ) {$charset_collate};"; dbDelta( $sql ); $sql = "CREATE TABLE {$login_attempts} ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, ip VARCHAR(45) NOT NULL, username VARCHAR(60) NOT NULL, success TINYINT(1) NOT NULL DEFAULT 0, created_at DATETIME NOT NULL, PRIMARY KEY (id), KEY ip_created (ip, created_at), KEY username_created (username, created_at) ) {$charset_collate};"; dbDelta( $sql ); $sql = "CREATE TABLE {$vuln_cache} ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, component_type VARCHAR(20) NOT NULL, slug VARCHAR(191) NOT NULL, vulnerable_below VARCHAR(40) NULL, fixed_in VARCHAR(40) NULL, severity VARCHAR(20) NOT NULL, cve VARCHAR(20) NULL, description LONGTEXT NULL, updated_at DATETIME NOT NULL, PRIMARY KEY (id), KEY component_slug (component_type, slug(120)) ) {$charset_collate};"; dbDelta( $sql ); $sql = "CREATE TABLE {$quarantine} ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, original_path VARCHAR(500) NOT NULL, quarantine_filename VARCHAR(60) NOT NULL, original_filename VARCHAR(255) NOT NULL, file_size BIGINT UNSIGNED NOT NULL, file_hash CHAR(64) NOT NULL, original_mtime DATETIME NULL, original_perms VARCHAR(4) NULL, detection_engine VARCHAR(60) NOT NULL, detection_rule VARCHAR(100) NULL, detection_type VARCHAR(20) NOT NULL, severity VARCHAR(20) NOT NULL, confidence_score INT NULL, matched_rules LONGTEXT NULL, status VARCHAR(20) NOT NULL DEFAULT 'quarantined', finding_id BIGINT UNSIGNED NULL, quarantined_at DATETIME NOT NULL, analysed_at DATETIME NULL, resolved_at DATETIME NULL, restore_attempts INT NOT NULL DEFAULT 0, PRIMARY KEY (id), UNIQUE KEY quarantine_filename (quarantine_filename), KEY status (status), KEY finding_id (finding_id) ) {$charset_collate};"; dbDelta( $sql ); $sql = "CREATE TABLE {$cache_stats_daily} ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, stat_date DATE NOT NULL, status VARCHAR(20) NOT NULL, request_count BIGINT UNSIGNED NOT NULL DEFAULT 0, total_response_time_ms BIGINT UNSIGNED NOT NULL DEFAULT 0, total_bytes BIGINT UNSIGNED NOT NULL DEFAULT 0, PRIMARY KEY (id), UNIQUE KEY date_status (stat_date, status) ) {$charset_collate};"; dbDelta( $sql ); $sql = "CREATE TABLE {$cache_discovered_urls} ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, url VARCHAR(500) NOT NULL, source VARCHAR(40) NOT NULL, eligibility VARCHAR(20) NOT NULL DEFAULT 'needs_review', eligibility_reason VARCHAR(255) NULL, priority_hint DECIMAL(3,2) NULL, lastmod DATETIME NULL, last_checked_at DATETIME NULL, last_warmed_at DATETIME NULL, discovered_at DATETIME NOT NULL, PRIMARY KEY (id), UNIQUE KEY url (url(191)), KEY eligibility (eligibility) ) {$charset_collate};"; dbDelta( $sql ); $sql = "CREATE TABLE {$cache_warm_queue} ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, url VARCHAR(500) NOT NULL, priority INT NOT NULL DEFAULT 100, status VARCHAR(20) NOT NULL DEFAULT 'queued', attempts INT NOT NULL DEFAULT 0, last_attempt_at DATETIME NULL, last_result VARCHAR(255) NULL, queued_at DATETIME NOT NULL, PRIMARY KEY (id), KEY status_priority (status, priority), KEY url (url(191)) ) {$charset_collate};"; dbDelta( $sql ); $sql = "CREATE TABLE {$anis_reputation} ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, ip VARCHAR(45) NOT NULL, action VARCHAR(20) NOT NULL, confidence INT NOT NULL DEFAULT 0, reports INT NOT NULL DEFAULT 0, source VARCHAR(60) NULL, reason VARCHAR(255) NULL, decided_at DATETIME NULL, expires_at DATETIME NULL, cached_at DATETIME NOT NULL, PRIMARY KEY (id), UNIQUE KEY ip (ip), KEY action (action) ) {$charset_collate};"; dbDelta( $sql ); $sql = "CREATE TABLE {$vuln_status} ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, component_type VARCHAR(20) NOT NULL, slug VARCHAR(191) NOT NULL, name VARCHAR(255) NOT NULL, installed_version VARCHAR(40) NULL, checked_version VARCHAR(40) NULL, status VARCHAR(20) NOT NULL DEFAULT 'pending', severity VARCHAR(20) NULL, cve VARCHAR(20) NULL, fixed_in VARCHAR(40) NULL, description LONGTEXT NULL, last_checked_at DATETIME NULL, updated_at DATETIME NOT NULL, PRIMARY KEY (id), UNIQUE KEY component (component_type, slug), KEY status (status) ) {$charset_collate};"; dbDelta( $sql ); $sql = "CREATE TABLE {$scan_history} ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, scan_type VARCHAR(30) NOT NULL, started_at DATETIME NOT NULL, finished_at DATETIME NOT NULL, files_scanned INT UNSIGNED NOT NULL DEFAULT 0, findings_count INT UNSIGNED NOT NULL DEFAULT 0, result VARCHAR(20) NOT NULL, PRIMARY KEY (id), KEY started_at (started_at) ) {$charset_collate};"; dbDelta( $sql ); $sql = "CREATE TABLE {$backups} ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, filename VARCHAR(191) NOT NULL, type VARCHAR(20) NOT NULL, size_bytes BIGINT UNSIGNED NOT NULL DEFAULT 0, status VARCHAR(20) NOT NULL DEFAULT 'complete', error_message TEXT NULL, created_at DATETIME NOT NULL, PRIMARY KEY (id), UNIQUE KEY filename (filename), KEY created_at (created_at) ) {$charset_collate};"; dbDelta( $sql ); update_option( self::SCHEMA_OPTION, self::SCHEMA_VERSION, false ); } public static function maybe_upgrade() { if ( get_option( self::SCHEMA_OPTION ) !== self::SCHEMA_VERSION ) { self::install(); } } public static function uninstall() { global $wpdb; foreach ( array( 'events', 'findings', 'bans', 'policy_decisions', 'integrity_baseline', 'login_attempts', 'vuln_cache', 'quarantine', 'cache_stats_daily', 'cache_discovered_urls', 'cache_warm_queue', 'anis_reputation', 'vuln_status', 'scan_history', 'backups' ) as $t ) { $wpdb->query( 'DROP TABLE IF EXISTS ' . self::table( $t ) ); // phpcs:ignore } delete_option( self::SCHEMA_OPTION ); } }