query( "DROP TABLE IF EXISTS `{$wpdb->prefix}mra_dismissed_alerts`" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery $wpdb->query( "DROP TABLE IF EXISTS `{$wpdb->prefix}mra_provider_status`" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery $wpdb->query( "DROP TABLE IF EXISTS `{$wpdb->prefix}mra_external_results`" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery $wpdb->query( "DROP TABLE IF EXISTS `{$wpdb->prefix}mra_media_usage`" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery $wpdb->query( "DROP TABLE IF EXISTS `{$wpdb->prefix}mra_media_index`" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery } /** * Truncates all plugin tables, removing all data while preserving the table structure. * * Used by the Tools page "Reset all data" operation. * * @return void */ public static function truncate_all(): void { global $wpdb; // phpcs:disable WordPress.DB.DirectDatabaseQuery $wpdb->query( "TRUNCATE TABLE `{$wpdb->prefix}mra_dismissed_alerts`" ); $wpdb->query( "TRUNCATE TABLE `{$wpdb->prefix}mra_provider_status`" ); $wpdb->query( "TRUNCATE TABLE `{$wpdb->prefix}mra_external_results`" ); $wpdb->query( "TRUNCATE TABLE `{$wpdb->prefix}mra_media_usage`" ); $wpdb->query( "TRUNCATE TABLE `{$wpdb->prefix}mra_media_index`" ); // phpcs:enable WordPress.DB.DirectDatabaseQuery } // ------------------------------------------------------------------------- // Migrations // ------------------------------------------------------------------------- /** * Migration 1.0.0 — creates the initial three tables. * * @return void */ private static function migration_100(): void { global $wpdb; $charset_collate = $wpdb->get_charset_collate(); require_once ABSPATH . 'wp-admin/includes/upgrade.php'; dbDelta( "CREATE TABLE {$wpdb->prefix}mra_media_index ( attachment_id bigint(20) unsigned NOT NULL, file_url text NOT NULL, file_name varchar(255) NOT NULL DEFAULT '', mime_type varchar(100) NOT NULL DEFAULT '', file_size bigint(20) unsigned NOT NULL DEFAULT 0, internal_scanned_at datetime DEFAULT NULL, external_status enum('pending','queued','scanned','matches','error') NOT NULL DEFAULT 'pending', external_scanned_at datetime DEFAULT NULL, created_at datetime NOT NULL, PRIMARY KEY (attachment_id), KEY external_status (external_status) ) {$charset_collate};" ); dbDelta( "CREATE TABLE {$wpdb->prefix}mra_media_usage ( usage_id bigint(20) unsigned NOT NULL auto_increment, attachment_id bigint(20) unsigned NOT NULL, post_id bigint(20) unsigned NOT NULL, post_type varchar(20) NOT NULL DEFAULT '', context enum('featured','content','meta') NOT NULL DEFAULT 'featured', meta_key varchar(255) DEFAULT NULL, created_at datetime NOT NULL, PRIMARY KEY (usage_id), KEY attachment_id (attachment_id), KEY post_id (post_id) ) {$charset_collate};" ); dbDelta( "CREATE TABLE {$wpdb->prefix}mra_external_results ( result_id bigint(20) unsigned NOT NULL auto_increment, attachment_id bigint(20) unsigned NOT NULL, provider varchar(100) NOT NULL, raw_response json DEFAULT NULL, match_count int(10) unsigned NOT NULL DEFAULT 0, top_domains json DEFAULT NULL, created_at datetime NOT NULL, PRIMARY KEY (result_id), UNIQUE KEY attachment_provider (attachment_id,provider) ) {$charset_collate};" ); } /** * Migration 1.0.1 — adds 'queued' to the external_status ENUM. * * Uses ALTER TABLE directly because dbDelta cannot modify ENUM column definitions. * Safe to re-run: MariaDB/MySQL treats an identical MODIFY as a no-op. * * @return void */ private static function migration_101(): void { global $wpdb; // phpcs:disable WordPress.DB.DirectDatabaseQuery,WordPress.DB.DirectDatabaseQuery.SchemaChange $wpdb->query( "ALTER TABLE `{$wpdb->prefix}mra_media_index` MODIFY COLUMN external_status enum('pending','queued','scanned','matches','error') NOT NULL DEFAULT 'pending'" ); // phpcs:enable WordPress.DB.DirectDatabaseQuery,WordPress.DB.DirectDatabaseQuery.SchemaChange } /** * Migration 1.1.0 — widens provider column and adds mra_provider_status table. * * Changes: * - ALTER mra_external_results.provider from ENUM to varchar(100) for extensibility. * - CREATE mra_provider_status for per-provider scan tracking. * - Backfill mra_provider_status from existing mra_external_results data. * * Safe to re-run: ALTER TABLE is a no-op if already varchar(100); * dbDelta skips existing tables; INSERT IGNORE skips duplicate rows. * * @return void */ private static function migration_110(): void { global $wpdb; $charset_collate = $wpdb->get_charset_collate(); // phpcs:disable WordPress.DB.DirectDatabaseQuery,WordPress.DB.DirectDatabaseQuery.SchemaChange $wpdb->query( "ALTER TABLE `{$wpdb->prefix}mra_external_results` MODIFY COLUMN provider varchar(100) NOT NULL" ); // phpcs:enable WordPress.DB.DirectDatabaseQuery,WordPress.DB.DirectDatabaseQuery.SchemaChange require_once ABSPATH . 'wp-admin/includes/upgrade.php'; dbDelta( "CREATE TABLE {$wpdb->prefix}mra_provider_status ( attachment_id bigint(20) unsigned NOT NULL, provider varchar(100) NOT NULL, status enum('pending','queued','scanned','error') NOT NULL DEFAULT 'pending', scanned_at datetime DEFAULT NULL, PRIMARY KEY (attachment_id,provider), KEY status (status) ) {$charset_collate};" ); // Backfill from existing data. Idempotent: INSERT IGNORE skips duplicates. // phpcs:disable WordPress.DB.DirectDatabaseQuery,WordPress.DB.PreparedSQL.InterpolatedNotPrepared $wpdb->query( "INSERT IGNORE INTO {$wpdb->prefix}mra_provider_status (attachment_id, provider, status, scanned_at) SELECT er.attachment_id, er.provider, CASE WHEN mi.external_status = 'error' THEN 'error' WHEN mi.external_status = 'queued' THEN 'queued' ELSE 'scanned' END, mi.external_scanned_at FROM {$wpdb->prefix}mra_external_results er JOIN {$wpdb->prefix}mra_media_index mi ON mi.attachment_id = er.attachment_id" ); // phpcs:enable WordPress.DB.DirectDatabaseQuery,WordPress.DB.PreparedSQL.InterpolatedNotPrepared } /** * Migration 1.2.0 — creates the mra_dismissed_alerts table. * * Stores per-attachment alert dismissal records with dismisser, timestamp, * and optional notes. Primary key on attachment_id (one dismissal per image). * * Safe to re-run: dbDelta skips existing tables. * * @return void */ private static function migration_120(): void { global $wpdb; $charset_collate = $wpdb->get_charset_collate(); require_once ABSPATH . 'wp-admin/includes/upgrade.php'; dbDelta( "CREATE TABLE {$wpdb->prefix}mra_dismissed_alerts ( attachment_id bigint(20) unsigned NOT NULL, dismissed_by bigint(20) unsigned NOT NULL DEFAULT 0, dismissed_at datetime NOT NULL, notes text NOT NULL DEFAULT '', PRIMARY KEY (attachment_id) ) {$charset_collate};" ); } }