$erasers Accumulated eraser list. * * @return array */ public static function register( array $erasers ): array { $erasers['robotstxt-mediaaudit'] = array( 'eraser_friendly_name' => __( 'Media Audit', 'robotstxt-mediaaudit' ), 'callback' => array( self::class, 'erase' ), ); return $erasers; } /** * Erases Media Audit data for the user identified by $email_address. * * @param string $email_address User e-mail address. * @param int $page Pagination page (unused; all data erased in one pass). * * @return array{items_removed: int, items_retained: int, messages: array, done: bool} */ public static function erase( string $email_address, int $page = 1 ): array { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed -- required by WP privacy API signature $user = get_user_by( 'email', $email_address ); if ( ! $user instanceof \WP_User ) { return self::done_response( 0 ); } $attachment_ids = self::get_attachment_ids( $user->ID ); if ( empty( $attachment_ids ) ) { return self::done_response( 0 ); } $removed = self::delete_plugin_data( $attachment_ids ); return self::done_response( $removed ); } // ------------------------------------------------------------------------- // Private helpers // ------------------------------------------------------------------------- /** * Builds a standard "done" response array. * * @param int $removed Number of records removed. * * @return array{items_removed: int, items_retained: int, messages: array, done: bool} */ private static function done_response( int $removed ): array { return array( 'items_removed' => $removed, 'items_retained' => 0, 'messages' => array(), 'done' => true, ); } /** * Returns all indexed attachment IDs for a given user (no pagination). * * @param int $user_id WordPress user ID. * * @return array */ private static function get_attachment_ids( int $user_id ): array { global $wpdb; // phpcs:disable WordPress.DB.DirectDatabaseQuery,WordPress.DB.PreparedSQL.InterpolatedNotPrepared $raw = $wpdb->get_col( $wpdb->prepare( "SELECT p.ID FROM {$wpdb->posts} p INNER JOIN {$wpdb->prefix}mra_media_index mi ON p.ID = mi.attachment_id WHERE p.post_type = 'attachment' AND p.post_author = %d ORDER BY p.ID ASC", $user_id ) ); // phpcs:enable WordPress.DB.DirectDatabaseQuery,WordPress.DB.PreparedSQL.InterpolatedNotPrepared if ( ! is_array( $raw ) ) { return array(); } return array_map( static fn ( mixed $v ): int => is_numeric( $v ) ? (int) $v : 0, $raw ); } /** * Deletes all plugin data rows for the given attachment IDs. * * Removes records from mra_external_results, mra_media_usage, and * mra_media_index. Returns the total number of affected rows. * * @param array $ids Attachment IDs to delete. * * @return int Total rows deleted across all three tables. */ private static function delete_plugin_data( array $ids ): int { global $wpdb; $placeholders = implode( ',', array_fill( 0, count( $ids ), '%d' ) ); $removed = 0; // phpcs:disable WordPress.DB.DirectDatabaseQuery,WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}mra_external_results WHERE attachment_id IN ({$placeholders})", ...$ids ) ); $removed += (int) $wpdb->rows_affected; $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}mra_media_usage WHERE attachment_id IN ({$placeholders})", ...$ids ) ); $removed += (int) $wpdb->rows_affected; $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}mra_media_index WHERE attachment_id IN ({$placeholders})", ...$ids ) ); $removed += (int) $wpdb->rows_affected; // phpcs:enable WordPress.DB.DirectDatabaseQuery,WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare return $removed; } }