page_url() ) ); exit; } $site_ids = NetworkActivator::get_active_site_ids(); $count = 0; foreach ( $site_ids as $site_id ) { switch_to_blog( $site_id ); switch ( $op ) { case 'internal': AttachmentIndexer::schedule(); UsageScanner::schedule(); ++$count; break; case 'external': ExternalScanner::schedule(); ++$count; break; case 'requeue_errors': ExternalScanner::requeue_errors(); ++$count; break; } restore_current_blog(); } delete_transient( self::STATUS_TRANSIENT ); wp_safe_redirect( add_query_arg( array( 'mra_notice' => 'success', 'mra_op' => $op, 'mra_count' => $count, ), $this->page_url() ) ); exit; } /** * Returns the canonical URL for the Network Tools page. * * @return string */ private function page_url(): string { return network_admin_url( 'admin.php?page=robotstxt-mediaaudit-network-tools' ); } /** * Renders the full network Tools page (used in central mode). * * @return void */ public function render(): void { if ( ! current_user_can( 'manage_network_options' ) ) { wp_die( esc_html__( 'You do not have permission to access this page.', 'robotstxt-mediaaudit' ) ); } $status = $this->get_network_status(); $notice = $this->get_notice_data(); ?>

render_notice( $notice ); ?>

render_site_status_table( $status ); ?>

render_network_operations(); ?>
get_network_status(); ?>

render_site_status_table( $status ); ?>
> $status Per-site status data keyed by site_id. * * @return void */ private function render_site_status_table( array $status ): void { ?> $s ) : ?>

✓' : ''; ?>

'internal', 'label' => __( 'Schedule Internal Scan — All Sites', 'robotstxt-mediaaudit' ), 'desc' => __( 'Queues indexing and usage scanning jobs on every active site. Safe to run at any time — skips already-indexed attachments.', 'robotstxt-mediaaudit' ), 'class' => 'button button-primary', ), array( 'op' => 'external', 'label' => __( 'Schedule External Scan — All Sites', 'robotstxt-mediaaudit' ), 'desc' => __( 'Queues reverse-image-search jobs on every active site. Requires API keys configured in Network Settings.', 'robotstxt-mediaaudit' ), 'class' => 'button button-primary', ), array( 'op' => 'requeue_errors', 'label' => __( 'Requeue Scan Errors — All Sites', 'robotstxt-mediaaudit' ), 'desc' => __( 'Re-queues attachments that failed with an error on the last external scan run across all sites.', 'robotstxt-mediaaudit' ), 'class' => 'button', ), ); ?>

$type, 'op' => $op, 'count' => $count, ); } /** * Renders a WP admin notice for a completed network operation. * * @param array{type: string, op: string, count: int}|null $notice Notice data. * * @return void */ private function render_notice( ?array $notice ): void { if ( null === $notice || 'success' !== $notice['type'] ) { return; } $op = $notice['op']; $count = $notice['count']; ?>

> */ private function get_network_status(): array { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh // phpcs:ignore WordPress.Security.NonceVerification.Recommended $force_refresh = isset( $_GET['mra_refresh'] ) && '1' === $_GET['mra_refresh']; if ( ! $force_refresh ) { $cached = get_transient( self::STATUS_TRANSIENT ); if ( is_array( $cached ) ) { $typed = array(); foreach ( $cached as $k => $v ) { if ( ! is_int( $k ) || ! is_array( $v ) ) { continue; } $row = array(); foreach ( $v as $row_key => $row_value ) { if ( is_string( $row_key ) ) { $row[ $row_key ] = $row_value; } } $typed[ $k ] = $row; } return $typed; } } global $wpdb; $site_ids = NetworkActivator::get_active_site_ids(); $result = array(); $as_ok = Scheduler::is_available(); foreach ( $site_ids as $site_id ) { switch_to_blog( $site_id ); $site_obj = get_site( $site_id ); $site_name = $site_obj instanceof \WP_Site ? $site_obj->blogname : (string) $site_id; $site_url = get_site_url( $site_id ); $audit_url = get_admin_url( $site_id, 'admin.php?page=robotstxt-mediaaudit' ); $raw_ver = get_option( 'robotstxt_mediaaudit_db_version', '' ); $stored_ver = is_string( $raw_ver ) ? $raw_ver : ''; // phpcs:ignore WordPress.DB.DirectDatabaseQuery $total_images = (int) $wpdb->get_var( "SELECT COUNT(ID) FROM {$wpdb->posts} WHERE post_type = 'attachment' AND post_mime_type LIKE 'image/%' AND post_status != 'trash'" ); $indexed = AttachmentIndexer::get_indexed_count(); $ext = ExternalScanner::get_status_counts(); // phpcs:ignore WordPress.DB.DirectDatabaseQuery,WordPress.DB.PreparedSQL.InterpolatedNotPrepared $alert_count = (int) $wpdb->get_var( "SELECT COUNT(DISTINCT er.attachment_id) FROM {$wpdb->prefix}mra_external_results er WHERE NOT EXISTS ( SELECT 1 FROM {$wpdb->prefix}mra_dismissed_alerts da WHERE da.attachment_id = er.attachment_id )" ); $result[ $site_id ] = array( 'site_name' => $site_name, 'site_url' => $site_url, 'audit_url' => $audit_url, 'db_ok' => ROBOTSTXT_MEDIAAUDIT_DB_VERSION === $stored_ver, 'total_images' => $total_images, 'indexed' => $indexed, 'external' => $ext, 'total_alert' => $alert_count, 'jobs' => array( 'index' => $as_ok && Scheduler::has_pending( AttachmentIndexer::AS_HOOK ), 'usage' => $as_ok && Scheduler::has_pending( UsageScanner::AS_HOOK ), 'external' => $as_ok && Scheduler::has_pending( ExternalScanner::AS_HOOK ), ), ); restore_current_blog(); } set_transient( self::STATUS_TRANSIENT, $result, 5 * MINUTE_IN_SECONDS ); return $result; } }