This commit is contained in:
Javier Casares 2026-06-03 06:25:27 +00:00
commit 650e69349f
21 changed files with 2919 additions and 268 deletions

View file

@ -65,6 +65,8 @@ class AuditPage {
if ( 'export_csv' === $action ) {
$this->handle_export_csv();
} elseif ( 'export_external_csv' === $action ) {
$this->handle_export_external_csv();
} elseif ( 'run_external_scan' === $action ) {
$this->handle_run_external_scan();
} elseif ( 'purge_external_data' === $action ) {
@ -498,6 +500,142 @@ class AuditPage {
exit;
}
/**
* Outputs an external-results CSV for selected attachment IDs and terminates.
*
* One row per attachment × provider pair. Attachments with no results still
* appear with empty provider columns.
*
* @return void
*/
private function handle_export_external_csv(): void {
check_admin_referer( 'bulk-mra-attachments' );
if ( ! current_user_can( 'edit_posts' ) ) {
wp_die( esc_html__( 'Insufficient permissions.', 'robotstxt-mediaaudit' ) );
}
$ids = $this->collect_attachment_ids();
$rows = $this->build_external_csv_rows( $ids );
$filename = 'media-audit-external-' . gmdate( 'Y-m-d' ) . '.csv';
header( 'Content-Type: text/csv; charset=utf-8' );
header( 'Content-Disposition: attachment; filename="' . $filename . '"' );
header( 'Pragma: no-cache' );
$out = fopen( 'php://output', 'w' );
if ( false === $out ) {
wp_die( esc_html__( 'Could not open output stream.', 'robotstxt-mediaaudit' ) );
}
fwrite( $out, "\xEF\xBB\xBF" ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fwrite
fputcsv(
$out,
array(
__( 'Attachment ID', 'robotstxt-mediaaudit' ),
__( 'Filename', 'robotstxt-mediaaudit' ),
__( 'File URL', 'robotstxt-mediaaudit' ),
__( 'External Status', 'robotstxt-mediaaudit' ),
__( 'Last Scanned', 'robotstxt-mediaaudit' ),
__( 'Provider', 'robotstxt-mediaaudit' ),
__( 'Match Count', 'robotstxt-mediaaudit' ),
__( 'Top Domains', 'robotstxt-mediaaudit' ),
)
);
foreach ( $rows as $row ) {
fputcsv( $out, $row );
}
fclose( $out ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose
exit;
}
/**
* Builds external-results CSV rows for the given attachment IDs.
*
* Each row represents one attachment × provider combination. Attachments with
* no provider results are included with empty provider columns. If $ids is
* empty, all indexed attachments are exported.
*
* @param array<int, int> $ids Attachment IDs to export (empty = all).
*
* @return array<int, array<int, string>>
*/
private function build_external_csv_rows( array $ids ): array {
global $wpdb;
// phpcs:disable WordPress.DB.DirectDatabaseQuery,WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
if ( empty( $ids ) ) {
$rows = $wpdb->get_results(
"SELECT i.attachment_id, i.file_name, i.file_url, i.external_status,
i.external_scanned_at, er.provider, er.match_count, er.top_domains
FROM {$wpdb->prefix}mra_media_index i
LEFT JOIN {$wpdb->prefix}mra_external_results er ON er.attachment_id = i.attachment_id
ORDER BY i.attachment_id ASC, er.provider ASC",
ARRAY_A
);
} else {
$placeholders = implode( ',', array_fill( 0, count( $ids ), '%d' ) );
$rows = $wpdb->get_results(
$wpdb->prepare(
"SELECT i.attachment_id, i.file_name, i.file_url, i.external_status,
i.external_scanned_at, er.provider, er.match_count, er.top_domains
FROM {$wpdb->prefix}mra_media_index i
LEFT JOIN {$wpdb->prefix}mra_external_results er ON er.attachment_id = i.attachment_id
WHERE i.attachment_id IN ({$placeholders})
ORDER BY i.attachment_id ASC, er.provider ASC",
...$ids
),
ARRAY_A
);
}
// phpcs:enable WordPress.DB.DirectDatabaseQuery,WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
if ( ! $rows ) {
return array();
}
$output = array();
foreach ( $rows as $r ) {
$aid_raw = $r['attachment_id'] ?? '';
$scanned_raw = $r['external_scanned_at'] ?? '';
$domains_raw = $r['top_domains'] ?? '';
$mc_raw = $r['match_count'] ?? '';
$domains_decoded = is_string( $domains_raw ) && '' !== $domains_raw
? json_decode( $domains_raw, true )
: array();
$domain_parts = array();
if ( is_array( $domains_decoded ) ) {
foreach ( $domains_decoded as $d ) {
if ( is_string( $d ) ) {
$domain_parts[] = $d;
}
}
}
$domains_str = implode( '; ', $domain_parts );
$output[] = array(
is_numeric( $aid_raw ) ? (string) (int) $aid_raw : '',
is_string( $r['file_name'] ?? null ) ? (string) $r['file_name'] : '',
is_string( $r['file_url'] ?? null ) ? (string) $r['file_url'] : '',
is_string( $r['external_status'] ?? null ) ? (string) $r['external_status'] : '',
is_string( $scanned_raw ) ? $scanned_raw : '',
is_string( $r['provider'] ?? null ) ? (string) $r['provider'] : '',
is_numeric( $mc_raw ) ? (string) (int) $mc_raw : '',
$domains_str,
);
}
return $output;
}
/**
* Queues selected attachments for external scanning and redirects.
*