v1.7.0
This commit is contained in:
parent
98b1cf0f3b
commit
4402f3570d
20 changed files with 1683 additions and 382 deletions
769
includes/Admin/AlertsPage.php
Normal file
769
includes/Admin/AlertsPage.php
Normal file
|
|
@ -0,0 +1,769 @@
|
|||
<?php
|
||||
/**
|
||||
* Admin page controller for the Alerts view.
|
||||
*
|
||||
* @package MediaRightsAudit\Admin
|
||||
*/
|
||||
|
||||
namespace MediaRightsAudit\Admin;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
use MediaRightsAudit\External\HostnameFilter;
|
||||
use MediaRightsAudit\Admin\AttachmentDetailPage;
|
||||
|
||||
/**
|
||||
* Registers and renders the Alerts admin page.
|
||||
*
|
||||
* Responsibilities:
|
||||
* - Display active (non-dismissed) alert rows.
|
||||
* - Display dismissed alert rows.
|
||||
* - Handle dismiss / reactivate POST actions via PRG.
|
||||
* - Provide static helpers for dismissed-ID lookups (cached per request).
|
||||
*/
|
||||
class AlertsPage {
|
||||
|
||||
/**
|
||||
* Nonce action for all alert operations.
|
||||
*/
|
||||
const NONCE_ACTION = 'mra_alert_action';
|
||||
|
||||
/**
|
||||
* Nonce field name.
|
||||
*/
|
||||
const NONCE_FIELD = 'mra_alert_nonce';
|
||||
|
||||
/**
|
||||
* Maximum length of dismissal notes.
|
||||
*/
|
||||
const NOTES_MAX = 1000;
|
||||
|
||||
/**
|
||||
* Rows per page for paginated tabs.
|
||||
*/
|
||||
const PER_PAGE = 25;
|
||||
|
||||
/**
|
||||
* In-request cache of dismissed attachment IDs.
|
||||
*
|
||||
* @var array<int, true>|null
|
||||
*/
|
||||
private static ?array $dismissed_cache = null;
|
||||
|
||||
/**
|
||||
* Hook suffix assigned by add_submenu_page().
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private string $hook_suffix = '';
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Public static data methods
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Returns all dismissed attachment IDs as a map keyed by attachment_id.
|
||||
*
|
||||
* Cached for the lifetime of the current request.
|
||||
*
|
||||
* @return array<int, true>
|
||||
*/
|
||||
public static function get_dismissed_ids(): array {
|
||||
if ( null !== self::$dismissed_cache ) {
|
||||
return self::$dismissed_cache;
|
||||
}
|
||||
|
||||
global $wpdb;
|
||||
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
|
||||
$rows = $wpdb->get_col(
|
||||
"SELECT attachment_id FROM {$wpdb->prefix}mra_dismissed_alerts"
|
||||
);
|
||||
|
||||
self::$dismissed_cache = array();
|
||||
|
||||
if ( is_array( $rows ) ) {
|
||||
foreach ( $rows as $id ) {
|
||||
if ( is_numeric( $id ) ) {
|
||||
self::$dismissed_cache[ (int) $id ] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return self::$dismissed_cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidates the in-request dismissed-IDs cache.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function invalidate_cache(): void {
|
||||
self::$dismissed_cache = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts or replaces a dismissal record for an attachment.
|
||||
*
|
||||
* @param int $attachment_id WordPress attachment post ID.
|
||||
* @param int $user_id WordPress user ID performing the dismissal.
|
||||
* @param string $notes Optional notes (max NOTES_MAX chars).
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function dismiss( int $attachment_id, int $user_id, string $notes ): void {
|
||||
global $wpdb;
|
||||
|
||||
$notes = mb_substr( $notes, 0, self::NOTES_MAX );
|
||||
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery
|
||||
$wpdb->query(
|
||||
$wpdb->prepare(
|
||||
"INSERT INTO {$wpdb->prefix}mra_dismissed_alerts
|
||||
(attachment_id, dismissed_by, dismissed_at, notes)
|
||||
VALUES (%d, %d, %s, %s)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
dismissed_by = VALUES(dismissed_by),
|
||||
dismissed_at = VALUES(dismissed_at),
|
||||
notes = VALUES(notes)",
|
||||
$attachment_id,
|
||||
$user_id,
|
||||
current_time( 'mysql' ),
|
||||
$notes
|
||||
)
|
||||
);
|
||||
|
||||
self::invalidate_cache();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a dismissal record, making the alert active again.
|
||||
*
|
||||
* @param int $attachment_id WordPress attachment post ID.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function reactivate( int $attachment_id ): void {
|
||||
global $wpdb;
|
||||
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
|
||||
$wpdb->delete(
|
||||
$wpdb->prefix . 'mra_dismissed_alerts',
|
||||
array( 'attachment_id' => $attachment_id ),
|
||||
array( '%d' )
|
||||
);
|
||||
|
||||
self::invalidate_cache();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Instance methods
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Stores the hook suffix and registers the load-* action.
|
||||
*
|
||||
* @param string $suffix Hook suffix from add_submenu_page().
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function set_hook_suffix( string $suffix ): void {
|
||||
$this->hook_suffix = $suffix;
|
||||
add_action( 'load-' . $suffix, array( $this, 'handle_load' ) );
|
||||
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_assets' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueues CSS and JS assets for the Alerts page.
|
||||
*
|
||||
* @param string $hook Current admin page hook suffix.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function enqueue_assets( string $hook ): void {
|
||||
if ( '' === $this->hook_suffix || $hook !== $this->hook_suffix ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$base = ROBOTSTXT_MEDIAAUDIT_PLUGIN_URL . 'assets/';
|
||||
$ver = ROBOTSTXT_MEDIAAUDIT_VERSION;
|
||||
|
||||
wp_enqueue_style( 'mra-admin', $base . 'css/media-audit-admin.css', array(), $ver );
|
||||
wp_enqueue_script( 'mra-admin', $base . 'js/media-audit-admin.js', array( 'jquery' ), $ver, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Fires before the admin page HTML is output.
|
||||
*
|
||||
* Processes dismiss / reactivate POST actions and issues a PRG redirect.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle_load(): void {
|
||||
if ( ! isset( $_POST[ self::NONCE_FIELD ] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! current_user_can( 'edit_others_posts' ) ) {
|
||||
wp_die( esc_html__( 'You do not have permission to perform this action.', 'robotstxt-mediaaudit' ) );
|
||||
}
|
||||
|
||||
check_admin_referer( self::NONCE_ACTION, self::NONCE_FIELD );
|
||||
|
||||
$op_raw = isset( $_POST['mra_alert_op'] ) && is_string( $_POST['mra_alert_op'] )
|
||||
? sanitize_key( wp_unslash( $_POST['mra_alert_op'] ) )
|
||||
: '';
|
||||
|
||||
$aid_raw = isset( $_POST['mra_attachment_id'] ) && is_string( $_POST['mra_attachment_id'] )
|
||||
? (int) sanitize_text_field( wp_unslash( $_POST['mra_attachment_id'] ) )
|
||||
: 0;
|
||||
|
||||
$notice = 'error';
|
||||
|
||||
if ( $aid_raw > 0 ) {
|
||||
if ( 'dismiss' === $op_raw ) {
|
||||
$notes_raw = isset( $_POST['mra_notes'] ) && is_string( $_POST['mra_notes'] )
|
||||
? sanitize_textarea_field( wp_unslash( $_POST['mra_notes'] ) )
|
||||
: '';
|
||||
self::dismiss( $aid_raw, get_current_user_id(), $notes_raw );
|
||||
$notice = 'dismissed';
|
||||
} elseif ( 'reactivate' === $op_raw ) {
|
||||
self::reactivate( $aid_raw );
|
||||
$notice = 'reactivated';
|
||||
}
|
||||
}
|
||||
|
||||
// Read tab from POST body (the hidden input in the form), fall back to GET.
|
||||
$tab_raw = isset( $_POST['mra_tab'] ) && is_string( $_POST['mra_tab'] ) ? sanitize_key( wp_unslash( $_POST['mra_tab'] ) ) : '';
|
||||
$tab = in_array( $tab_raw, array( 'active', 'dismissed' ), true ) ? $tab_raw : $this->get_active_tab();
|
||||
|
||||
wp_safe_redirect(
|
||||
add_query_arg(
|
||||
array(
|
||||
'page' => 'robotstxt-mediaaudit-alerts',
|
||||
'tab' => $tab,
|
||||
'mra_notice' => $notice,
|
||||
'mra_op' => $op_raw,
|
||||
),
|
||||
admin_url( 'admin.php' )
|
||||
)
|
||||
);
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the Alerts admin page.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function render(): void {
|
||||
if ( ! current_user_can( 'edit_others_posts' ) ) {
|
||||
wp_die( esc_html__( 'You do not have permission to access this page.', 'robotstxt-mediaaudit' ) );
|
||||
}
|
||||
|
||||
$active_rows = self::get_active_alert_rows();
|
||||
$dismissed_rows = self::get_dismissed_rows();
|
||||
$tab = $this->get_active_tab();
|
||||
$notice = $this->get_notice_data();
|
||||
|
||||
$active_count = count( $active_rows );
|
||||
$dismissed_count = count( $dismissed_rows );
|
||||
|
||||
$paged = max( 1, isset( $_GET['paged'] ) && is_numeric( $_GET['paged'] ) ? (int) $_GET['paged'] : 1 ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
$offset = ( $paged - 1 ) * self::PER_PAGE;
|
||||
|
||||
$page_url = add_query_arg( 'page', 'robotstxt-mediaaudit-alerts', admin_url( 'admin.php' ) );
|
||||
?>
|
||||
<div class="wrap">
|
||||
<h1><?php esc_html_e( 'Alerts', 'robotstxt-mediaaudit' ); ?></h1>
|
||||
|
||||
<?php $this->render_notice( $notice ); ?>
|
||||
|
||||
<nav class="nav-tab-wrapper">
|
||||
<a
|
||||
class="nav-tab<?php echo ( 'active' === $tab ) ? ' nav-tab-active' : ''; ?>"
|
||||
href="<?php echo esc_url( add_query_arg( 'tab', 'active', $page_url ) ); ?>"
|
||||
>
|
||||
<?php
|
||||
printf(
|
||||
/* translators: %d: count of active alerts */
|
||||
esc_html__( 'Active Alerts (%d)', 'robotstxt-mediaaudit' ),
|
||||
(int) $active_count
|
||||
);
|
||||
?>
|
||||
</a>
|
||||
<a
|
||||
class="nav-tab<?php echo ( 'dismissed' === $tab ) ? ' nav-tab-active' : ''; ?>"
|
||||
href="<?php echo esc_url( add_query_arg( 'tab', 'dismissed', $page_url ) ); ?>"
|
||||
>
|
||||
<?php
|
||||
printf(
|
||||
/* translators: %d: count of dismissed alerts */
|
||||
esc_html__( 'Dismissed Alerts (%d)', 'robotstxt-mediaaudit' ),
|
||||
(int) $dismissed_count
|
||||
);
|
||||
?>
|
||||
</a>
|
||||
</nav>
|
||||
|
||||
<?php if ( 'active' === $tab ) : ?>
|
||||
<?php
|
||||
$page_rows = array_slice( $active_rows, $offset, self::PER_PAGE );
|
||||
$this->render_active_tab( $page_rows, $active_count, $paged, $page_url );
|
||||
?>
|
||||
<?php else : ?>
|
||||
<?php
|
||||
$page_rows = array_slice( $dismissed_rows, $offset, self::PER_PAGE );
|
||||
$this->render_dismissed_tab( $page_rows, $dismissed_count, $paged, $page_url );
|
||||
?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Private rendering helpers
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Returns the active tab slug ('active' or 'dismissed').
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function get_active_tab(): string {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
$tab = isset( $_GET['tab'] ) && is_string( $_GET['tab'] ) ? sanitize_key( wp_unslash( $_GET['tab'] ) ) : 'active';
|
||||
return in_array( $tab, array( 'active', 'dismissed' ), true ) ? $tab : 'active';
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads PRG query parameters and returns structured notice data.
|
||||
*
|
||||
* @return array{type: string, op: string}|null
|
||||
*/
|
||||
private function get_notice_data(): ?array {
|
||||
// phpcs:disable WordPress.Security.NonceVerification.Recommended
|
||||
if ( ! isset( $_GET['mra_notice'] ) ) {
|
||||
return null;
|
||||
}
|
||||
$type = sanitize_key( wp_unslash( is_string( $_GET['mra_notice'] ) ? $_GET['mra_notice'] : '' ) );
|
||||
$op = sanitize_key( wp_unslash( isset( $_GET['mra_op'] ) && is_string( $_GET['mra_op'] ) ? $_GET['mra_op'] : '' ) );
|
||||
// phpcs:enable WordPress.Security.NonceVerification.Recommended
|
||||
|
||||
return array(
|
||||
'type' => $type,
|
||||
'op' => $op,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a WP admin notice based on the PRG redirect result.
|
||||
*
|
||||
* @param array{type: string, op: string}|null $notice Notice data array, or null.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function render_notice( ?array $notice ): void {
|
||||
if ( null === $notice ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$type = $notice['type'];
|
||||
|
||||
$messages = array(
|
||||
'dismissed' => __( 'Alert dismissed successfully.', 'robotstxt-mediaaudit' ),
|
||||
'reactivated' => __( 'Alert reactivated successfully.', 'robotstxt-mediaaudit' ),
|
||||
'error' => __( 'An error occurred. Please try again.', 'robotstxt-mediaaudit' ),
|
||||
);
|
||||
|
||||
$class = ( 'error' === $type ) ? 'notice-error' : 'notice-success';
|
||||
$msg = $messages[ $type ] ?? __( 'Operation completed.', 'robotstxt-mediaaudit' );
|
||||
?>
|
||||
<div class="notice <?php echo esc_attr( $class ); ?> is-dismissible">
|
||||
<p><?php echo esc_html( $msg ); ?></p>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the Active Alerts tab.
|
||||
*
|
||||
* @param array<int, array<string, mixed>> $rows Page rows.
|
||||
* @param int $total Total row count.
|
||||
* @param int $paged Current page number.
|
||||
* @param string $page_url Base page URL.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function render_active_tab( array $rows, int $total, int $paged, string $page_url ): void {
|
||||
if ( empty( $rows ) ) {
|
||||
echo '<p>' . esc_html__( 'No active alerts found.', 'robotstxt-mediaaudit' ) . '</p>';
|
||||
return;
|
||||
}
|
||||
?>
|
||||
<table class="widefat striped mra-alerts-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:50px"><?php esc_html_e( 'Thumbnail', 'robotstxt-mediaaudit' ); ?></th>
|
||||
<th><?php esc_html_e( 'Filename', 'robotstxt-mediaaudit' ); ?></th>
|
||||
<th><?php esc_html_e( 'Alert Domains', 'robotstxt-mediaaudit' ); ?></th>
|
||||
<th style="width:80px"><?php esc_html_e( 'Usage', 'robotstxt-mediaaudit' ); ?></th>
|
||||
<th style="width:120px"><?php esc_html_e( 'Actions', 'robotstxt-mediaaudit' ); ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ( $rows as $index => $row ) : ?>
|
||||
<?php
|
||||
$aid_val = $row['attachment_id'] ?? 0;
|
||||
$aid = is_numeric( $aid_val ) ? (int) $aid_val : 0;
|
||||
$file_name_raw = $row['file_name'] ?? '';
|
||||
$file_name = is_string( $file_name_raw ) ? $file_name_raw : '';
|
||||
$file_url_raw = $row['file_url'] ?? '';
|
||||
$file_url = is_string( $file_url_raw ) ? $file_url_raw : '';
|
||||
$usage_cnt_raw = $row['usage_count'] ?? 0;
|
||||
$usage_count = is_numeric( $usage_cnt_raw ) ? (int) $usage_cnt_raw : 0;
|
||||
$domains_raw = $row['top_domains_data'] ?? array();
|
||||
$domains = is_array( $domains_raw ) ? $domains_raw : array();
|
||||
$dismiss_id = 'mra-dismiss-row-' . $aid;
|
||||
|
||||
$thumb = wp_get_attachment_image( $aid, array( 40, 40 ), false, array( 'class' => 'mra-thumb' ) );
|
||||
if ( ! $thumb ) {
|
||||
$thumb = '<span class="dashicons dashicons-format-image" style="font-size:40px;line-height:1;"></span>';
|
||||
}
|
||||
?>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="<?php echo esc_url( $file_url ); ?>" target="_blank"><?php echo $thumb; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></a>
|
||||
</td>
|
||||
<td>
|
||||
<a href="<?php echo esc_url( $file_url ); ?>" target="_blank"><?php echo esc_html( '' !== $file_name ? $file_name : '—' ); ?></a>
|
||||
<div class="row-actions">
|
||||
<?php
|
||||
$edit_link = get_edit_post_link( $aid );
|
||||
$row_links = array();
|
||||
if ( $edit_link ) {
|
||||
$row_links[] = '<a href="' . esc_url( $edit_link ) . '">' . esc_html__( 'Edit', 'robotstxt-mediaaudit' ) . '</a>';
|
||||
}
|
||||
$row_links[] = '<a href="' . esc_url( AttachmentDetailPage::url( $aid ) ) . '"><strong>' . esc_html__( 'Full Report', 'robotstxt-mediaaudit' ) . '</strong></a>';
|
||||
echo implode( ' | ', $row_links ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
?>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<?php if ( ! empty( $domains ) ) : ?>
|
||||
<ul class="mra-alert-domains">
|
||||
<?php foreach ( $domains as $domain => $count ) : ?>
|
||||
<li>
|
||||
<span class="mra-badge mra-badge-alert"><?php esc_html_e( 'Alert', 'robotstxt-mediaaudit' ); ?></span>
|
||||
<?php echo esc_html( $domain ); ?>
|
||||
<small>(<?php echo esc_html( number_format_i18n( is_numeric( $count ) ? (int) $count : 0 ) ); ?>)</small>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td><?php echo esc_html( number_format_i18n( $usage_count ) ); ?></td>
|
||||
<td>
|
||||
<button
|
||||
type="button"
|
||||
class="button mra-dismiss-toggle"
|
||||
data-target="<?php echo esc_attr( $dismiss_id ); ?>"
|
||||
><?php esc_html_e( 'Dismiss', 'robotstxt-mediaaudit' ); ?></button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr id="<?php echo esc_attr( $dismiss_id ); ?>" class="mra-dismiss-row" hidden>
|
||||
<td colspan="5">
|
||||
<div class="mra-dismiss-form-inner">
|
||||
<form method="post" action="<?php echo esc_url( admin_url( 'admin.php?page=robotstxt-mediaaudit-alerts' ) ); ?>">
|
||||
<?php wp_nonce_field( self::NONCE_ACTION, self::NONCE_FIELD ); ?>
|
||||
<input type="hidden" name="mra_alert_op" value="dismiss" />
|
||||
<input type="hidden" name="mra_attachment_id" value="<?php echo esc_attr( (string) $aid ); ?>" />
|
||||
<input type="hidden" name="mra_tab" value="active" />
|
||||
<label for="mra-notes-<?php echo esc_attr( (string) $aid ); ?>">
|
||||
<?php esc_html_e( 'Notes (optional):', 'robotstxt-mediaaudit' ); ?>
|
||||
</label>
|
||||
<textarea
|
||||
id="mra-notes-<?php echo esc_attr( (string) $aid ); ?>"
|
||||
name="mra_notes"
|
||||
rows="2"
|
||||
maxlength="<?php echo esc_attr( (string) self::NOTES_MAX ); ?>"
|
||||
class="large-text"
|
||||
></textarea>
|
||||
<div style="margin-top:8px">
|
||||
<button type="submit" class="button button-primary"><?php esc_html_e( 'Confirm Dismiss', 'robotstxt-mediaaudit' ); ?></button>
|
||||
<button type="button" class="button mra-dismiss-cancel"><?php esc_html_e( 'Cancel', 'robotstxt-mediaaudit' ); ?></button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<?php
|
||||
$pagination = paginate_links(
|
||||
array(
|
||||
'base' => add_query_arg( 'paged', '%#%', add_query_arg( 'tab', 'active', $page_url ) ),
|
||||
'format' => '',
|
||||
'current' => $paged,
|
||||
'total' => (int) ceil( $total / self::PER_PAGE ),
|
||||
'prev_text' => '«',
|
||||
'next_text' => '»',
|
||||
)
|
||||
);
|
||||
if ( $pagination ) {
|
||||
echo '<div class="tablenav"><div class="tablenav-pages">' . $pagination . '</div></div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the Dismissed Alerts tab.
|
||||
*
|
||||
* @param array<int, array<string, mixed>> $rows Page rows.
|
||||
* @param int $total Total row count.
|
||||
* @param int $paged Current page number.
|
||||
* @param string $page_url Base page URL.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function render_dismissed_tab( array $rows, int $total, int $paged, string $page_url ): void {
|
||||
if ( empty( $rows ) ) {
|
||||
echo '<p>' . esc_html__( 'No dismissed alerts.', 'robotstxt-mediaaudit' ) . '</p>';
|
||||
return;
|
||||
}
|
||||
?>
|
||||
<table class="widefat striped mra-alerts-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:50px"><?php esc_html_e( 'Thumbnail', 'robotstxt-mediaaudit' ); ?></th>
|
||||
<th><?php esc_html_e( 'Filename', 'robotstxt-mediaaudit' ); ?></th>
|
||||
<th><?php esc_html_e( 'Alert Domains', 'robotstxt-mediaaudit' ); ?></th>
|
||||
<th><?php esc_html_e( 'Notes', 'robotstxt-mediaaudit' ); ?></th>
|
||||
<th><?php esc_html_e( 'Dismissed By', 'robotstxt-mediaaudit' ); ?></th>
|
||||
<th><?php esc_html_e( 'Dismissed At', 'robotstxt-mediaaudit' ); ?></th>
|
||||
<th style="width:120px"><?php esc_html_e( 'Actions', 'robotstxt-mediaaudit' ); ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ( $rows as $row ) : ?>
|
||||
<?php
|
||||
$aid_val = $row['attachment_id'] ?? 0;
|
||||
$aid = is_numeric( $aid_val ) ? (int) $aid_val : 0;
|
||||
$file_name_raw = $row['file_name'] ?? '';
|
||||
$file_name = is_string( $file_name_raw ) ? $file_name_raw : '';
|
||||
$file_url_raw = $row['file_url'] ?? '';
|
||||
$file_url = is_string( $file_url_raw ) ? $file_url_raw : '';
|
||||
$notes_raw = $row['notes'] ?? '';
|
||||
$notes = is_string( $notes_raw ) ? $notes_raw : '';
|
||||
$dis_by_raw = $row['dismissed_by'] ?? 0;
|
||||
$dismissed_by = is_numeric( $dis_by_raw ) ? (int) $dis_by_raw : 0;
|
||||
$dis_at_raw = $row['dismissed_at'] ?? '';
|
||||
$dismissed_at = is_string( $dis_at_raw ) ? $dis_at_raw : '';
|
||||
$domains_raw = $row['top_domains_data'] ?? array();
|
||||
$domains = is_array( $domains_raw ) ? $domains_raw : array();
|
||||
|
||||
$user = $dismissed_by > 0 ? get_user_by( 'id', $dismissed_by ) : false;
|
||||
$display_name = ( $user && isset( $user->display_name ) ) ? $user->display_name : (string) $dismissed_by;
|
||||
|
||||
$thumb = wp_get_attachment_image( $aid, array( 40, 40 ), false, array( 'class' => 'mra-thumb' ) );
|
||||
if ( ! $thumb ) {
|
||||
$thumb = '<span class="dashicons dashicons-format-image" style="font-size:40px;line-height:1;"></span>';
|
||||
}
|
||||
?>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="<?php echo esc_url( $file_url ); ?>" target="_blank"><?php echo $thumb; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></a>
|
||||
</td>
|
||||
<td>
|
||||
<a href="<?php echo esc_url( $file_url ); ?>" target="_blank"><?php echo esc_html( '' !== $file_name ? $file_name : '—' ); ?></a>
|
||||
<div class="row-actions">
|
||||
<?php
|
||||
$edit_link = get_edit_post_link( $aid );
|
||||
$row_links2 = array();
|
||||
if ( $edit_link ) {
|
||||
$row_links2[] = '<a href="' . esc_url( $edit_link ) . '">' . esc_html__( 'Edit', 'robotstxt-mediaaudit' ) . '</a>';
|
||||
}
|
||||
$row_links2[] = '<a href="' . esc_url( AttachmentDetailPage::url( $aid ) ) . '"><strong>' . esc_html__( 'Full Report', 'robotstxt-mediaaudit' ) . '</strong></a>';
|
||||
echo implode( ' | ', $row_links2 ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
?>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<?php if ( ! empty( $domains ) ) : ?>
|
||||
<ul class="mra-alert-domains">
|
||||
<?php foreach ( $domains as $domain => $count ) : ?>
|
||||
<li>
|
||||
<span class="mra-badge mra-badge-dismissed"><?php esc_html_e( 'Dismissed', 'robotstxt-mediaaudit' ); ?></span>
|
||||
<?php echo esc_html( $domain ); ?>
|
||||
<small>(<?php echo esc_html( number_format_i18n( is_numeric( $count ) ? (int) $count : 0 ) ); ?>)</small>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td><?php echo esc_html( $notes ); ?></td>
|
||||
<td><?php echo esc_html( $display_name ); ?></td>
|
||||
<td><?php echo esc_html( $dismissed_at ); ?></td>
|
||||
<td>
|
||||
<form method="post" action="<?php echo esc_url( admin_url( 'admin.php?page=robotstxt-mediaaudit-alerts' ) ); ?>" style="display:inline">
|
||||
<?php wp_nonce_field( self::NONCE_ACTION, self::NONCE_FIELD ); ?>
|
||||
<input type="hidden" name="mra_alert_op" value="reactivate" />
|
||||
<input type="hidden" name="mra_attachment_id" value="<?php echo esc_attr( (string) $aid ); ?>" />
|
||||
<input type="hidden" name="mra_tab" value="dismissed" />
|
||||
<button type="submit" class="button"><?php esc_html_e( 'Reactivate', 'robotstxt-mediaaudit' ); ?></button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<?php
|
||||
$pagination = paginate_links(
|
||||
array(
|
||||
'base' => add_query_arg( 'paged', '%#%', add_query_arg( 'tab', 'dismissed', $page_url ) ),
|
||||
'format' => '',
|
||||
'current' => $paged,
|
||||
'total' => (int) ceil( $total / self::PER_PAGE ),
|
||||
'prev_text' => '«',
|
||||
'next_text' => '»',
|
||||
)
|
||||
);
|
||||
if ( $pagination ) {
|
||||
echo '<div class="tablenav"><div class="tablenav-pages">' . $pagination . '</div></div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Private static data methods
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Queries active (non-dismissed) alert rows.
|
||||
*
|
||||
* Fetches all mra_media_index candidates that have at least one row in
|
||||
* mra_external_results (via EXISTS), excludes dismissed attachments via LEFT JOIN,
|
||||
* bulk-fetches top_domains, and PHP-filters to only rows with at least one alert domain.
|
||||
*
|
||||
* @return array<int, array<string, mixed>>
|
||||
*/
|
||||
private static function get_active_alert_rows(): array {
|
||||
global $wpdb;
|
||||
|
||||
// phpcs:disable WordPress.DB.DirectDatabaseQuery,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$candidates = $wpdb->get_results(
|
||||
"SELECT i.attachment_id, i.file_name, i.file_url,
|
||||
(SELECT COUNT(*) FROM {$wpdb->prefix}mra_media_usage mu WHERE mu.attachment_id = i.attachment_id) AS usage_count
|
||||
FROM {$wpdb->prefix}mra_media_index i
|
||||
LEFT JOIN {$wpdb->prefix}mra_dismissed_alerts da ON da.attachment_id = i.attachment_id
|
||||
WHERE da.attachment_id IS NULL
|
||||
AND EXISTS (SELECT 1 FROM {$wpdb->prefix}mra_external_results er WHERE er.attachment_id = i.attachment_id)
|
||||
ORDER BY i.attachment_id ASC",
|
||||
ARRAY_A
|
||||
);
|
||||
// phpcs:enable WordPress.DB.DirectDatabaseQuery,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
|
||||
if ( ! is_array( $candidates ) || empty( $candidates ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$all_ids = array_map( 'intval', array_column( $candidates, 'attachment_id' ) );
|
||||
$top_domains_by_id = MediaListTable::fetch_top_domains( $all_ids );
|
||||
|
||||
$result = array();
|
||||
foreach ( $candidates as $row ) {
|
||||
if ( ! is_array( $row ) ) {
|
||||
continue;
|
||||
}
|
||||
$aid_val = $row['attachment_id'] ?? null;
|
||||
$aid = is_numeric( $aid_val ) ? (int) $aid_val : 0;
|
||||
$domains = $top_domains_by_id[ $aid ] ?? array();
|
||||
|
||||
// Keep only alert-classified domains.
|
||||
$alert_domains = array();
|
||||
foreach ( $domains as $domain => $count ) {
|
||||
if ( HostnameFilter::classify( $domain ) === 'alert' ) {
|
||||
$alert_domains[ $domain ] = $count;
|
||||
}
|
||||
}
|
||||
|
||||
if ( empty( $alert_domains ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$result[] = array(
|
||||
'attachment_id' => $aid,
|
||||
'file_name' => is_string( $row['file_name'] ?? null ) ? $row['file_name'] : '',
|
||||
'file_url' => is_string( $row['file_url'] ?? null ) ? $row['file_url'] : '',
|
||||
'usage_count' => is_numeric( $row['usage_count'] ?? null ) ? (int) $row['usage_count'] : 0,
|
||||
'top_domains_data' => $alert_domains,
|
||||
);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Queries dismissed alert rows.
|
||||
*
|
||||
* Fetches all mra_dismissed_alerts records LEFT JOINed with mra_media_index,
|
||||
* bulk-fetches top_domains, and PHP-filters to alert domains only.
|
||||
*
|
||||
* @return array<int, array<string, mixed>>
|
||||
*/
|
||||
private static function get_dismissed_rows(): array {
|
||||
global $wpdb;
|
||||
|
||||
// phpcs:disable WordPress.DB.DirectDatabaseQuery,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$candidates = $wpdb->get_results(
|
||||
"SELECT da.attachment_id, da.dismissed_by, da.dismissed_at, da.notes,
|
||||
COALESCE(i.file_name, '') AS file_name,
|
||||
COALESCE(i.file_url, '') AS file_url
|
||||
FROM {$wpdb->prefix}mra_dismissed_alerts da
|
||||
LEFT JOIN {$wpdb->prefix}mra_media_index i ON i.attachment_id = da.attachment_id
|
||||
ORDER BY da.dismissed_at DESC",
|
||||
ARRAY_A
|
||||
);
|
||||
// phpcs:enable WordPress.DB.DirectDatabaseQuery,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
|
||||
if ( ! is_array( $candidates ) || empty( $candidates ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$all_ids = array_map( 'intval', array_column( $candidates, 'attachment_id' ) );
|
||||
$top_domains_by_id = MediaListTable::fetch_top_domains( $all_ids );
|
||||
|
||||
$result = array();
|
||||
foreach ( $candidates as $row ) {
|
||||
if ( ! is_array( $row ) ) {
|
||||
continue;
|
||||
}
|
||||
$aid_val = $row['attachment_id'] ?? null;
|
||||
$aid = is_numeric( $aid_val ) ? (int) $aid_val : 0;
|
||||
$domains = $top_domains_by_id[ $aid ] ?? array();
|
||||
|
||||
// Keep only alert-classified domains.
|
||||
$alert_domains = array();
|
||||
foreach ( $domains as $domain => $count ) {
|
||||
if ( HostnameFilter::classify( $domain ) === 'alert' ) {
|
||||
$alert_domains[ $domain ] = $count;
|
||||
}
|
||||
}
|
||||
|
||||
$result[] = array(
|
||||
'attachment_id' => $aid,
|
||||
'file_name' => is_string( $row['file_name'] ?? null ) ? $row['file_name'] : '',
|
||||
'file_url' => is_string( $row['file_url'] ?? null ) ? $row['file_url'] : '',
|
||||
'dismissed_by' => is_numeric( $row['dismissed_by'] ?? null ) ? (int) $row['dismissed_by'] : 0,
|
||||
'dismissed_at' => is_string( $row['dismissed_at'] ?? null ) ? $row['dismissed_at'] : '',
|
||||
'notes' => is_string( $row['notes'] ?? null ) ? $row['notes'] : '',
|
||||
'top_domains_data' => $alert_domains,
|
||||
);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
namespace MediaRightsAudit\Admin;
|
||||
|
||||
use MediaRightsAudit\Admin\AlertsPage;
|
||||
use MediaRightsAudit\Admin\AttachmentDetailPage;
|
||||
use MediaRightsAudit\External\ExternalScanner;
|
||||
use MediaRightsAudit\External\HostnameFilter;
|
||||
|
|
@ -131,7 +132,7 @@ class AuditPage {
|
|||
* @return void
|
||||
*/
|
||||
public function render(): void {
|
||||
if ( ! current_user_can( 'edit_posts' ) ) {
|
||||
if ( ! current_user_can( 'edit_others_posts' ) ) {
|
||||
wp_die( esc_html__( 'You do not have permission to access this page.', 'robotstxt-mediaaudit' ) );
|
||||
}
|
||||
|
||||
|
|
@ -171,7 +172,7 @@ class AuditPage {
|
|||
public function ajax_usage_details(): void {
|
||||
check_ajax_referer( 'mra_usage_details', 'nonce' );
|
||||
|
||||
if ( ! current_user_can( 'edit_posts' ) ) {
|
||||
if ( ! current_user_can( 'edit_others_posts' ) ) {
|
||||
wp_send_json_error( array( 'message' => __( 'Insufficient permissions.', 'robotstxt-mediaaudit' ) ) );
|
||||
}
|
||||
|
||||
|
|
@ -525,7 +526,7 @@ class AuditPage {
|
|||
private function handle_export_csv( bool $alerts_only ): void {
|
||||
check_admin_referer( 'bulk-mra-attachments' );
|
||||
|
||||
if ( ! current_user_can( 'edit_posts' ) ) {
|
||||
if ( ! current_user_can( 'edit_others_posts' ) ) {
|
||||
wp_die( esc_html__( 'Insufficient permissions.', 'robotstxt-mediaaudit' ) );
|
||||
}
|
||||
|
||||
|
|
@ -565,6 +566,8 @@ class AuditPage {
|
|||
__( 'Alert Domains', 'robotstxt-mediaaudit' ),
|
||||
__( 'Other Domains', 'robotstxt-mediaaudit' ),
|
||||
__( 'Ignored Domains', 'robotstxt-mediaaudit' ),
|
||||
__( 'Dismissed', 'robotstxt-mediaaudit' ),
|
||||
__( 'Dismissed Notes', 'robotstxt-mediaaudit' ),
|
||||
)
|
||||
);
|
||||
|
||||
|
|
@ -696,6 +699,29 @@ class AuditPage {
|
|||
}
|
||||
}
|
||||
|
||||
// Fetch dismissed IDs and notes for CSV columns.
|
||||
$dismissed_ids = AlertsPage::get_dismissed_ids();
|
||||
|
||||
// phpcs:disable WordPress.DB.DirectDatabaseQuery
|
||||
$dismissed_notes_rows = $wpdb->get_results(
|
||||
"SELECT attachment_id, notes FROM {$wpdb->prefix}mra_dismissed_alerts",
|
||||
ARRAY_A
|
||||
);
|
||||
// phpcs:enable WordPress.DB.DirectDatabaseQuery
|
||||
|
||||
$dismissed_notes = array();
|
||||
if ( is_array( $dismissed_notes_rows ) ) {
|
||||
foreach ( $dismissed_notes_rows as $dn ) {
|
||||
if ( is_array( $dn ) ) {
|
||||
$dn_aid_raw = $dn['attachment_id'] ?? null;
|
||||
$dn_aid = is_numeric( $dn_aid_raw ) ? (int) $dn_aid_raw : 0;
|
||||
if ( $dn_aid > 0 ) {
|
||||
$dismissed_notes[ $dn_aid ] = is_string( $dn['notes'] ?? null ) ? (string) $dn['notes'] : '';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$known_providers = array( 'google_vision', 'tineye', 'picdefense' );
|
||||
$output = array();
|
||||
|
||||
|
|
@ -731,9 +757,11 @@ class AuditPage {
|
|||
}
|
||||
}
|
||||
|
||||
$has_alert = ! empty( $alert_list );
|
||||
$has_alert = ! empty( $alert_list );
|
||||
$is_dismissed = isset( $dismissed_ids[ $aid ] );
|
||||
|
||||
if ( $alerts_only && ! $has_alert ) {
|
||||
// Skip row if alerts_only and either no alert or already dismissed.
|
||||
if ( $alerts_only && ( ! $has_alert || $is_dismissed ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -757,6 +785,8 @@ class AuditPage {
|
|||
$row_data[] = implode( '; ', $alert_list );
|
||||
$row_data[] = implode( '; ', $other_list );
|
||||
$row_data[] = implode( '; ', $ignored_list );
|
||||
$row_data[] = $is_dismissed ? __( 'Yes', 'robotstxt-mediaaudit' ) : __( 'No', 'robotstxt-mediaaudit' );
|
||||
$row_data[] = $dismissed_notes[ $aid ] ?? '';
|
||||
|
||||
$output[] = $row_data;
|
||||
}
|
||||
|
|
@ -772,7 +802,7 @@ class AuditPage {
|
|||
private function handle_run_external_scan(): void {
|
||||
check_admin_referer( 'bulk-mra-attachments' );
|
||||
|
||||
if ( ! current_user_can( 'edit_posts' ) ) {
|
||||
if ( ! current_user_can( 'edit_others_posts' ) ) {
|
||||
wp_die( esc_html__( 'Insufficient permissions.', 'robotstxt-mediaaudit' ) );
|
||||
}
|
||||
|
||||
|
|
@ -804,7 +834,7 @@ class AuditPage {
|
|||
private function handle_purge_external_data(): void {
|
||||
check_admin_referer( 'bulk-mra-attachments' );
|
||||
|
||||
if ( ! current_user_can( 'edit_posts' ) ) {
|
||||
if ( ! current_user_can( 'edit_others_posts' ) ) {
|
||||
wp_die( esc_html__( 'Insufficient permissions.', 'robotstxt-mediaaudit' ) );
|
||||
}
|
||||
|
||||
|
|
@ -942,8 +972,12 @@ class AuditPage {
|
|||
}
|
||||
}
|
||||
|
||||
$dismissed = AlertsPage::get_dismissed_ids();
|
||||
$alert_count = 0;
|
||||
foreach ( $merged as $domains ) {
|
||||
foreach ( $merged as $aid => $domains ) {
|
||||
if ( isset( $dismissed[ $aid ] ) ) {
|
||||
continue;
|
||||
}
|
||||
if ( HostnameFilter::has_alert_domains( $domains ) ) {
|
||||
++$alert_count;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
namespace MediaRightsAudit\Admin;
|
||||
|
||||
use MediaRightsAudit\Admin\AlertsPage;
|
||||
use MediaRightsAudit\Admin\AttachmentDetailPage;
|
||||
use MediaRightsAudit\External\HostnameFilter;
|
||||
|
||||
|
|
@ -290,7 +291,23 @@ class MediaListTable extends \WP_List_Table {
|
|||
$top_domains_data[ $td_key ] = $td_val;
|
||||
}
|
||||
}
|
||||
$has_alert = ! empty( $top_domains_data ) && HostnameFilter::has_alert_domains( $top_domains_data );
|
||||
$has_alert = ! empty( $top_domains_data ) && HostnameFilter::has_alert_domains( $top_domains_data );
|
||||
$is_dismissed = ! empty( $item['is_dismissed'] );
|
||||
|
||||
if ( $has_alert && $is_dismissed ) {
|
||||
$aid_val = $item['attachment_id'];
|
||||
$aid = is_numeric( $aid_val ) ? (int) $aid_val : 0;
|
||||
$out = sprintf(
|
||||
'<span class="mra-badge mra-badge-dismissed">%s</span>',
|
||||
esc_html__( 'Dismissed', 'robotstxt-mediaaudit' )
|
||||
);
|
||||
$out .= sprintf(
|
||||
' <a href="#" class="mra-view-details" data-id="%d">%s</a>',
|
||||
$aid,
|
||||
esc_html__( 'View Results', 'robotstxt-mediaaudit' )
|
||||
);
|
||||
return $out;
|
||||
}
|
||||
|
||||
if ( $has_alert ) {
|
||||
$out = sprintf(
|
||||
|
|
@ -486,6 +503,8 @@ class MediaListTable extends \WP_List_Table {
|
|||
public function prepare_items() {
|
||||
global $wpdb;
|
||||
|
||||
$dismissed_ids = AlertsPage::get_dismissed_ids();
|
||||
|
||||
$per_page = 20;
|
||||
$paged = $this->get_pagenum();
|
||||
$offset = ( $paged - 1 ) * $per_page;
|
||||
|
|
@ -508,8 +527,10 @@ class MediaListTable extends \WP_List_Table {
|
|||
// Detect alert filter before normalising external_status.
|
||||
$filter_alert = ( 'alert' === $external_status );
|
||||
if ( $filter_alert ) {
|
||||
// Reuse existing matches WHERE logic to narrow the candidate set.
|
||||
$external_status = 'matches';
|
||||
// Do not restrict by external_status: attachments can have alert domains
|
||||
// even when external_status is not 'matches'. The PHP pass below handles
|
||||
// the real filter, consistent with get_alert_attachment_count().
|
||||
$external_status = '';
|
||||
}
|
||||
|
||||
if ( ! in_array( $external_status, self::EXTERNAL_STATUSES, true ) ) {
|
||||
|
|
@ -578,7 +599,7 @@ class MediaListTable extends \WP_List_Table {
|
|||
$all_ids = array_map( 'intval', array_column( $all_rows, 'attachment_id' ) );
|
||||
$top_domains_by_id = self::fetch_top_domains( $all_ids );
|
||||
|
||||
// PHP-filter: keep only rows that have at least one alert domain.
|
||||
// PHP-filter: keep only rows that have at least one alert domain and are not dismissed.
|
||||
$filtered = array();
|
||||
foreach ( $all_rows as $r ) {
|
||||
if ( ! is_array( $r ) ) {
|
||||
|
|
@ -586,7 +607,7 @@ class MediaListTable extends \WP_List_Table {
|
|||
}
|
||||
$aid_val = $r['attachment_id'] ?? null;
|
||||
$aid = is_numeric( $aid_val ) ? (int) $aid_val : 0;
|
||||
if ( HostnameFilter::has_alert_domains( $top_domains_by_id[ $aid ] ?? array() ) ) {
|
||||
if ( HostnameFilter::has_alert_domains( $top_domains_by_id[ $aid ] ?? array() ) && ! isset( $dismissed_ids[ $aid ] ) ) {
|
||||
$filtered[] = $r;
|
||||
}
|
||||
}
|
||||
|
|
@ -594,11 +615,12 @@ class MediaListTable extends \WP_List_Table {
|
|||
$total = count( $filtered );
|
||||
$rows = array_slice( $filtered, $offset, $per_page );
|
||||
|
||||
// Attach top_domains_data for the page slice.
|
||||
// Attach top_domains_data and is_dismissed for the page slice.
|
||||
foreach ( $rows as &$row ) {
|
||||
$rid_val = $row['attachment_id'] ?? null;
|
||||
$rid = is_numeric( $rid_val ) ? (int) $rid_val : 0;
|
||||
$row['top_domains_data'] = $top_domains_by_id[ $rid ] ?? array();
|
||||
$row['is_dismissed'] = isset( $dismissed_ids[ $rid ] );
|
||||
}
|
||||
unset( $row );
|
||||
} else {
|
||||
|
|
@ -649,6 +671,7 @@ class MediaListTable extends \WP_List_Table {
|
|||
$rid_val = $row['attachment_id'] ?? null;
|
||||
$rid = is_numeric( $rid_val ) ? (int) $rid_val : 0;
|
||||
$row['top_domains_data'] = $top_domains_by_id[ $rid ] ?? array();
|
||||
$row['is_dismissed'] = isset( $dismissed_ids[ $rid ] );
|
||||
}
|
||||
unset( $row );
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue