$exporters Accumulated exporter list. * * @return array */ public static function register( array $exporters ): array { $exporters['robotstxt-mediaaudit'] = array( 'exporter_friendly_name' => __( 'Media Audit', 'robotstxt-mediaaudit' ), 'callback' => array( self::class, 'export' ), ); return $exporters; } /** * Exports Media Audit data for the user identified by $email_address. * * @param string $email_address User e-mail address. * @param int $page Current pagination page (1-based). * * @return array{data: array, done: bool} */ public static function export( string $email_address, int $page = 1 ): array { $user = get_user_by( 'email', $email_address ); if ( ! $user instanceof \WP_User ) { return array( 'data' => array(), 'done' => true, ); } $offset = ( $page - 1 ) * self::PER_PAGE; $attachment_ids = self::get_attachment_ids( $user->ID, $offset, self::PER_PAGE ); if ( empty( $attachment_ids ) ) { return array( 'data' => array(), 'done' => true, ); } $data = array(); foreach ( self::get_index_rows( $attachment_ids ) as $row ) { if ( ! is_array( $row ) ) { continue; } $aid_val = $row['attachment_id'] ?? null; $aid = is_numeric( $aid_val ) ? (int) $aid_val : 0; $data[] = array( 'group_id' => self::GROUP_INDEX, 'group_label' => __( 'Media Audit – Indexed Files', 'robotstxt-mediaaudit' ), 'item_id' => 'mra-index-' . $aid, 'data' => self::format_index_row( $row, $aid ), ); } foreach ( self::get_result_rows( $attachment_ids ) as $row ) { if ( ! is_array( $row ) ) { continue; } $aid_val = $row['attachment_id'] ?? null; $aid = is_numeric( $aid_val ) ? (int) $aid_val : 0; $provider_val = $row['provider'] ?? null; $provider = is_string( $provider_val ) ? $provider_val : ''; $data[] = array( 'group_id' => self::GROUP_RESULTS, 'group_label' => __( 'Media Audit – External Scan Results', 'robotstxt-mediaaudit' ), 'item_id' => 'mra-result-' . $aid . '-' . $provider, 'data' => self::format_result_row( $row, $aid, $provider ), ); } $total = self::count_attachments( $user->ID ); $done = ( $page * self::PER_PAGE ) >= $total; return array( 'data' => $data, 'done' => $done, ); } // ------------------------------------------------------------------------- // Private helpers // ------------------------------------------------------------------------- /** * Returns attachment IDs from the plugin index for a given user, paginated. * * @param int $user_id WordPress user ID. * @param int $offset Number of rows to skip. * @param int $limit Maximum number of rows to return. * * @return array */ private static function get_attachment_ids( int $user_id, int $offset, int $limit ): 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 LIMIT %d OFFSET %d", $user_id, $limit, $offset ) ); // 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 ); } /** * Counts all indexed attachments for a given user. * * @param int $user_id WordPress user ID. * * @return int */ private static function count_attachments( int $user_id ): int { global $wpdb; // phpcs:disable WordPress.DB.DirectDatabaseQuery,WordPress.DB.PreparedSQL.InterpolatedNotPrepared $result = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) 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", $user_id ) ); // phpcs:enable WordPress.DB.DirectDatabaseQuery,WordPress.DB.PreparedSQL.InterpolatedNotPrepared return is_numeric( $result ) ? (int) $result : 0; } /** * Fetches index rows for the given attachment IDs. * * @param array $ids Attachment IDs. * * @return array */ private static function get_index_rows( array $ids ): array { global $wpdb; $placeholders = implode( ',', array_fill( 0, count( $ids ), '%d' ) ); // phpcs:disable WordPress.DB.DirectDatabaseQuery,WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare $rows = $wpdb->get_results( $wpdb->prepare( "SELECT attachment_id, file_name, file_url, mime_type, external_status, internal_scanned_at, external_scanned_at FROM {$wpdb->prefix}mra_media_index WHERE attachment_id IN ({$placeholders})", ...$ids ), ARRAY_A ); // phpcs:enable WordPress.DB.DirectDatabaseQuery,WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare return is_array( $rows ) ? $rows : array(); } /** * Fetches external result rows for the given attachment IDs. * * @param array $ids Attachment IDs. * * @return array */ private static function get_result_rows( array $ids ): array { global $wpdb; $placeholders = implode( ',', array_fill( 0, count( $ids ), '%d' ) ); // phpcs:disable WordPress.DB.DirectDatabaseQuery,WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare $rows = $wpdb->get_results( $wpdb->prepare( "SELECT attachment_id, provider, match_count, created_at FROM {$wpdb->prefix}mra_external_results WHERE attachment_id IN ({$placeholders})", ...$ids ), ARRAY_A ); // phpcs:enable WordPress.DB.DirectDatabaseQuery,WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare return is_array( $rows ) ? $rows : array(); } /** * Formats a mra_media_index row into WordPress privacy export data items. * * @param array $row DB row. * @param int $aid Attachment ID. * * @return array> */ private static function format_index_row( array $row, int $aid ): array { $file_name = is_string( $row['file_name'] ?? null ) ? $row['file_name'] : ''; $file_url = is_string( $row['file_url'] ?? null ) ? $row['file_url'] : ''; $status = is_string( $row['external_status'] ?? null ) ? $row['external_status'] : ''; $int_at = is_string( $row['internal_scanned_at'] ?? null ) ? $row['internal_scanned_at'] : ''; $ext_at = is_string( $row['external_scanned_at'] ?? null ) ? $row['external_scanned_at'] : ''; return array( array( 'name' => __( 'Attachment ID', 'robotstxt-mediaaudit' ), 'value' => (string) $aid, ), array( 'name' => __( 'Filename', 'robotstxt-mediaaudit' ), 'value' => $file_name, ), array( 'name' => __( 'File URL', 'robotstxt-mediaaudit' ), 'value' => $file_url, ), array( 'name' => __( 'External Scan Status', 'robotstxt-mediaaudit' ), 'value' => $status, ), array( 'name' => __( 'Last Scanned (internal)', 'robotstxt-mediaaudit' ), 'value' => $int_at, ), array( 'name' => __( 'Last Scanned (external)', 'robotstxt-mediaaudit' ), 'value' => $ext_at, ), ); } /** * Formats a mra_external_results row into WordPress privacy export data items. * * @param array $row DB row. * @param int $aid Attachment ID. * @param string $provider Provider slug. * * @return array> */ private static function format_result_row( array $row, int $aid, string $provider ): array { $mc_val = $row['match_count'] ?? null; $match_count = is_numeric( $mc_val ) ? (int) $mc_val : 0; $created_at = is_string( $row['created_at'] ?? null ) ? $row['created_at'] : ''; return array( array( 'name' => __( 'Attachment ID', 'robotstxt-mediaaudit' ), 'value' => (string) $aid, ), array( 'name' => __( 'Provider', 'robotstxt-mediaaudit' ), 'value' => $provider, ), array( 'name' => __( 'Match Count', 'robotstxt-mediaaudit' ), 'value' => (string) $match_count, ), array( 'name' => __( 'Scanned At', 'robotstxt-mediaaudit' ), 'value' => $created_at, ), ); } }