diff --git a/assets/css/media-audit-admin.css b/assets/css/media-audit-admin.css index 8250db8..084682e 100644 --- a/assets/css/media-audit-admin.css +++ b/assets/css/media-audit-admin.css @@ -117,6 +117,16 @@ color: #155724; } +.mra-badge-alert { + background: #f8d7da; + color: #721c24; +} + +.mra-badge-dismissed { + background: #e8e8e8; + color: #50575e; +} + .mra-badge-match-count { background: #721c24; color: #fff; @@ -398,3 +408,55 @@ display: block; margin-bottom: 6px; } + +/* ----------------------------------------------------------------------- + Alerts page + ----------------------------------------------------------------------- */ + +/* Alert domain list in alerts table */ +.mra-alert-domains { + margin: 0; + padding: 0; + list-style: none; +} + +.mra-alert-domains li { + margin-bottom: 4px; + font-size: 13px; +} + +.mra-alert-domains small { + color: #787c82; +} + +/* Alerts table */ +.mra-alerts-table { + margin-top: 12px; +} + +.mra-alerts-table th { + font-weight: 600; +} + +/* Dismiss inline form row */ +.mra-dismiss-row td { + background: #f6f7f7; + padding: 12px 16px; +} + +.mra-dismiss-form-inner { + max-width: 600px; +} + +.mra-dismiss-form-inner label { + display: block; + font-weight: 600; + margin-bottom: 6px; + font-size: 13px; +} + +.mra-dismiss-form-inner textarea { + display: block; + width: 100%; + box-sizing: border-box; +} diff --git a/assets/js/media-audit-admin.js b/assets/js/media-audit-admin.js index 094d587..69efbb9 100644 --- a/assets/js/media-audit-admin.js +++ b/assets/js/media-audit-admin.js @@ -147,5 +147,21 @@ e.preventDefault(); openModal( $( this ).data( 'id' ) ); } ); + + // Dismiss toggle — Alerts page. + $( document ).on( 'click', '.mra-dismiss-toggle', function () { + var targetId = $( this ).data( 'target' ); + var $row = $( '#' + targetId ); + $row.prop( 'hidden', function ( i, v ) { return !v; } ); + $( this ).text( $row.prop( 'hidden' ) ? 'Dismiss' : 'Cancel' ); + } ); + + // Cancel button inside dismiss form row. + $( document ).on( 'click', '.mra-dismiss-cancel', function () { + var $row = $( this ).closest( 'tr.mra-dismiss-row' ); + var rowId = $row.attr( 'id' ); + $row.prop( 'hidden', true ); + $( '[data-target="' + rowId + '"]' ).text( 'Dismiss' ); + } ); } ); } ( jQuery ) ); diff --git a/changelog.txt b/changelog.txt index 2fd2370..f1135bd 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,41 @@ == Changelog == += 1.7.0 = + +_Release date: 2026-05-11_ + +**Added** + +* New **Alerts** admin page (Media Audit → Alerts) with two tabs: + * *Active Alerts* — lists all attachments with active (non-dismissed) alert-domain matches. Each row has a "Dismiss" button that expands an inline form to add optional notes. + * *Dismissed Alerts* — lists alerts that have been manually dismissed, with the dismissal notes, who dismissed them, and when. Each row has a "Reactivate" button to restore the alert. +* New DB table `mra_dismissed_alerts` (schema migration 1.2.0): stores attachment ID, dismissed-by user ID, timestamp, and free-text notes (up to 1,000 characters). +* Dashboard "Alert" card now counts only active (non-dismissed) alerts. +* Alert filter in the Media Audit list now excludes dismissed alerts. +* External Status column shows a grey "Dismissed" badge instead of the red "Alert" badge for dismissed attachments. +* "Export CSV (Alerts only)" bulk action now excludes dismissed alerts. Both CSV exports include new "Dismissed" and "Dismissed Notes" columns. +* `top_domains` stored per provider raised from 10 to 50 entries to prevent low-frequency alert domains from being silently discarded. + +**Fixed** + +* Alert filter in Media Audit list was incorrectly restricted to `external_status = 'matches'`, causing it to miss attachments whose alert domains were found but whose aggregate status had not yet been updated to `matches`. The filter now correctly matches the dashboard card count. +* Capability checks in `AuditPage` (render, AJAX handler, CSV export, bulk actions) used the weaker `edit_posts` capability instead of the `edit_others_posts` used in the menu registration. + +**Schema changes:** `ROBOTSTXT_MEDIAAUDIT_DB_VERSION` → `1.2.0` + +**Compatibility** + +* WordPress: 6.8 - 7.0 +* PHP: 8.2 - 8.5 +* WP-CLI: 2.x + +**Tests** + +* PHP Coding Standards: WPCS 3.x / PHPCS 3.x +* PHPStan: level 9 +* PHPCompatibility: PHP 8.2 - 8.5 +* PHPUnit: 48 tests, 78 assertions + = 1.6.0 = _Release date: 2026-05-05_ diff --git a/includes/Admin/AlertsPage.php b/includes/Admin/AlertsPage.php new file mode 100644 index 0000000..487c5db --- /dev/null +++ b/includes/Admin/AlertsPage.php @@ -0,0 +1,769 @@ +|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 + */ + 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' ) ); + ?> +
+

+ + render_notice( $notice ); ?> + + + + + render_active_tab( $page_rows, $active_count, $paged, $page_url ); + ?> + + render_dismissed_tab( $page_rows, $dismissed_count, $paged, $page_url ); + ?> + +
+ $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' ); + ?> +
+

+
+ > $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 '

' . esc_html__( 'No active alerts found.', 'robotstxt-mediaaudit' ) . '

'; + return; + } + ?> + + + + + + + + + + + + $row ) : ?> + 'mra-thumb' ) ); + if ( ! $thumb ) { + $thumb = ''; + } + ?> + + + + + + + + + + + + +
+ + + +
+ ' . esc_html__( 'Edit', 'robotstxt-mediaaudit' ) . ''; + } + $row_links[] = '' . esc_html__( 'Full Report', 'robotstxt-mediaaudit' ) . ''; + echo implode( ' | ', $row_links ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + ?> +
+
+ +
    + $count ) : ?> +
  • + + + () +
  • + +
+ +
+ +
+ + 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 '
' . $pagination . '
'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + } + } + + /** + * Renders the Dismissed Alerts tab. + * + * @param array> $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 '

' . esc_html__( 'No dismissed alerts.', 'robotstxt-mediaaudit' ) . '

'; + return; + } + ?> + + + + + + + + + + + + + + + 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 = ''; + } + ?> + + + + + + + + + + + +
+ + + +
+ ' . esc_html__( 'Edit', 'robotstxt-mediaaudit' ) . ''; + } + $row_links2[] = '' . esc_html__( 'Full Report', 'robotstxt-mediaaudit' ) . ''; + echo implode( ' | ', $row_links2 ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + ?> +
+
+ +
    + $count ) : ?> +
  • + + + () +
  • + +
+ +
+
+ + + + + +
+
+ + 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 '
' . $pagination . '
'; // 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> + */ + 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> + */ + 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; + } +} diff --git a/includes/Admin/AuditPage.php b/includes/Admin/AuditPage.php index c66d61b..ba21fee 100644 --- a/includes/Admin/AuditPage.php +++ b/includes/Admin/AuditPage.php @@ -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; } diff --git a/includes/Admin/MediaListTable.php b/includes/Admin/MediaListTable.php index cfcd18e..3f231df 100644 --- a/includes/Admin/MediaListTable.php +++ b/includes/Admin/MediaListTable.php @@ -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( + '%s', + esc_html__( 'Dismissed', 'robotstxt-mediaaudit' ) + ); + $out .= sprintf( + ' %s', + $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 ); } diff --git a/includes/Core/Database.php b/includes/Core/Database.php index 337450a..259a58a 100644 --- a/includes/Core/Database.php +++ b/includes/Core/Database.php @@ -24,6 +24,7 @@ class Database { self::migration_100(); self::migration_101(); self::migration_110(); + self::migration_120(); } /** @@ -48,6 +49,10 @@ class Database { if ( version_compare( $current, '1.1.0', '<' ) ) { self::migration_110(); } + + if ( version_compare( $current, '1.2.0', '<' ) ) { + self::migration_120(); + } } /** @@ -57,6 +62,7 @@ class Database { */ public static function drop_tables(): void { global $wpdb; + $wpdb->query( "DROP TABLE IF EXISTS `{$wpdb->prefix}mra_dismissed_alerts`" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery $wpdb->query( "DROP TABLE IF EXISTS `{$wpdb->prefix}mra_provider_status`" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery $wpdb->query( "DROP TABLE IF EXISTS `{$wpdb->prefix}mra_external_results`" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery $wpdb->query( "DROP TABLE IF EXISTS `{$wpdb->prefix}mra_media_usage`" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery @@ -74,6 +80,7 @@ class Database { global $wpdb; // phpcs:disable WordPress.DB.DirectDatabaseQuery + $wpdb->query( "TRUNCATE TABLE `{$wpdb->prefix}mra_dismissed_alerts`" ); $wpdb->query( "TRUNCATE TABLE `{$wpdb->prefix}mra_provider_status`" ); $wpdb->query( "TRUNCATE TABLE `{$wpdb->prefix}mra_external_results`" ); $wpdb->query( "TRUNCATE TABLE `{$wpdb->prefix}mra_media_usage`" ); @@ -215,4 +222,32 @@ class Database { ); // phpcs:enable WordPress.DB.DirectDatabaseQuery,WordPress.DB.PreparedSQL.InterpolatedNotPrepared } + + /** + * Migration 1.2.0 — creates the mra_dismissed_alerts table. + * + * Stores per-attachment alert dismissal records with dismisser, timestamp, + * and optional notes. Primary key on attachment_id (one dismissal per image). + * + * Safe to re-run: dbDelta skips existing tables. + * + * @return void + */ + private static function migration_120(): void { + global $wpdb; + + $charset_collate = $wpdb->get_charset_collate(); + + require_once ABSPATH . 'wp-admin/includes/upgrade.php'; + + dbDelta( + "CREATE TABLE {$wpdb->prefix}mra_dismissed_alerts ( + attachment_id bigint(20) unsigned NOT NULL, + dismissed_by bigint(20) unsigned NOT NULL DEFAULT 0, + dismissed_at datetime NOT NULL, + notes text NOT NULL DEFAULT '', + PRIMARY KEY (attachment_id) +) {$charset_collate};" + ); + } } diff --git a/includes/Core/Plugin.php b/includes/Core/Plugin.php index 33c51e7..bcd0a42 100644 --- a/includes/Core/Plugin.php +++ b/includes/Core/Plugin.php @@ -7,6 +7,7 @@ namespace MediaRightsAudit\Core; +use MediaRightsAudit\Admin\AlertsPage; use MediaRightsAudit\Admin\AttachmentDetailPage; use MediaRightsAudit\Admin\AuditPage; use MediaRightsAudit\Admin\Settings; @@ -27,6 +28,13 @@ use MediaRightsAudit\Privacy\DataExporter; */ class Plugin { + /** + * Alerts page controller. + * + * @var AlertsPage + */ + private AlertsPage $alerts_page; + /** * Settings page handler. * @@ -59,6 +67,7 @@ class Plugin { * Initialises dependencies. */ public function __construct() { + $this->alerts_page = new AlertsPage(); $this->settings = new Settings(); $this->audit_page = new AuditPage(); $this->tools_page = new ToolsPage(); @@ -127,6 +136,19 @@ class Plugin { array( $this->audit_page, 'render' ) ); + $alerts_suffix = add_submenu_page( + 'robotstxt-mediaaudit', + __( 'Alerts', 'robotstxt-mediaaudit' ), + __( 'Alerts', 'robotstxt-mediaaudit' ), + 'edit_others_posts', + 'robotstxt-mediaaudit-alerts', + array( $this->alerts_page, 'render' ) + ); + + if ( is_string( $alerts_suffix ) ) { + $this->alerts_page->set_hook_suffix( $alerts_suffix ); + } + $tools_suffix = add_submenu_page( 'robotstxt-mediaaudit', __( 'Tools', 'robotstxt-mediaaudit' ), diff --git a/includes/External/AbstractProvider.php b/includes/External/AbstractProvider.php index 2d52d75..938ab4e 100644 --- a/includes/External/AbstractProvider.php +++ b/includes/External/AbstractProvider.php @@ -99,7 +99,7 @@ abstract class AbstractProvider { } arsort( $counts ); - return array_slice( $counts, 0, 10, true ); + return array_slice( $counts, 0, 50, true ); } /** diff --git a/includes/External/ExternalScanner.php b/includes/External/ExternalScanner.php index 0bd233c..3afccf6 100644 --- a/includes/External/ExternalScanner.php +++ b/includes/External/ExternalScanner.php @@ -507,8 +507,7 @@ class ExternalScanner { $row = $wpdb->get_row( $wpdb->prepare( "SELECT - SUM(status = 'queued') AS queued_count, - SUM(status = 'error') AS error_count, + SUM(status = 'queued') AS queued_count, SUM(status = 'scanned') AS scanned_count FROM {$wpdb->prefix}mra_provider_status WHERE attachment_id = %d", @@ -523,7 +522,6 @@ class ExternalScanner { } $queued_count = isset( $row['queued_count'] ) && is_numeric( $row['queued_count'] ) ? (int) $row['queued_count'] : 0; - $error_count = isset( $row['error_count'] ) && is_numeric( $row['error_count'] ) ? (int) $row['error_count'] : 0; $scanned_count = isset( $row['scanned_count'] ) && is_numeric( $row['scanned_count'] ) ? (int) $row['scanned_count'] : 0; if ( $queued_count > 0 ) { @@ -544,9 +542,6 @@ class ExternalScanner { $new_status = ( is_numeric( $match_sum ) && (int) $match_sum > 0 ) ? 'matches' : 'scanned'; } - // Suppress unused variable inspection: $error_count is available for future use. - unset( $error_count ); - $wpdb->update( // phpcs:ignore WordPress.DB.DirectDatabaseQuery $wpdb->prefix . 'mra_media_index', array( diff --git a/languages/robotstxt-mediaaudit-ca.mo b/languages/robotstxt-mediaaudit-ca.mo index c6eaf4a..c0a06f2 100644 Binary files a/languages/robotstxt-mediaaudit-ca.mo and b/languages/robotstxt-mediaaudit-ca.mo differ diff --git a/languages/robotstxt-mediaaudit-ca.po b/languages/robotstxt-mediaaudit-ca.po index 19bf7c8..33504f8 100644 --- a/languages/robotstxt-mediaaudit-ca.po +++ b/languages/robotstxt-mediaaudit-ca.po @@ -2,10 +2,10 @@ # This file is distributed under the GPL-3.0-or-later. msgid "" msgstr "" -"Project-Id-Version: Media Audit (by ROBOTSTXT) 1.6.0\n" +"Project-Id-Version: Media Audit (by ROBOTSTXT) 1.7.0\n" "Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/robotstxt-" "mediaaudit\n" -"POT-Creation-Date: 2026-05-05T10:39:07+00:00\n" +"POT-Creation-Date: 2026-05-11T07:13:58+00:00\n" "PO-Revision-Date: 2026-05-01 07:41+0000\n" "Last-Translator: Javier Casares \n" "Language-Team: Catalan \n" @@ -45,13 +45,136 @@ msgstr "" msgid "https://www.robotstxt.es/" msgstr "" -#: includes/Admin/AttachmentDetailPage.php:40 includes/Admin/AuditPage.php:135 -#: includes/Admin/Settings.php:898 includes/Admin/ToolsPage.php:253 +#: includes/Admin/AlertsPage.php:210 +msgid "You do not have permission to perform this action." +msgstr "No teniu permís per accedir a aquesta pàgina." + +#: includes/Admin/AlertsPage.php:263 includes/Admin/AttachmentDetailPage.php:40 +#: includes/Admin/AuditPage.php:136 includes/Admin/Settings.php:898 +#: includes/Admin/ToolsPage.php:253 msgid "You do not have permission to access this page." msgstr "No teniu permís per accedir a aquesta pàgina." +#: includes/Admin/AlertsPage.php:280 includes/Core/Plugin.php:141 +#: includes/Core/Plugin.php:142 +msgid "Alerts" +msgstr "Alertes" + +#. translators: %d: count of active alerts +#: includes/Admin/AlertsPage.php:292 +#, php-format +msgid "Active Alerts (%d)" +msgstr "Alertes actives (%d)" + +#. translators: %d: count of dismissed alerts +#: includes/Admin/AlertsPage.php:304 +#, php-format +msgid "Dismissed Alerts (%d)" +msgstr "Alertes descartades (%d)" + +#: includes/Admin/AlertsPage.php:376 +msgid "Alert dismissed successfully." +msgstr "Alerta descartada correctament." + +#: includes/Admin/AlertsPage.php:377 +msgid "Alert reactivated successfully." +msgstr "Alerta reactivada correctament." + +#: includes/Admin/AlertsPage.php:378 +msgid "An error occurred. Please try again." +msgstr "S'ha produït un error. Si us plau, torna-ho a intentar." + +#: includes/Admin/AlertsPage.php:382 includes/Admin/ToolsPage.php:376 +msgid "Operation completed." +msgstr "Operació completada." + +#: includes/Admin/AlertsPage.php:402 +msgid "No active alerts found." +msgstr "No s'han trobat alertes actives." + +#: includes/Admin/AlertsPage.php:409 includes/Admin/AlertsPage.php:541 +#: includes/Admin/MediaListTable.php:55 +msgid "Thumbnail" +msgstr "Miniatura" + +#: includes/Admin/AlertsPage.php:410 includes/Admin/AlertsPage.php:542 +#: includes/Admin/AttachmentDetailPage.php:87 includes/Admin/AuditPage.php:554 +#: includes/Admin/MediaListTable.php:56 includes/Privacy/DataExporter.php:261 +msgid "Filename" +msgstr "Nom de fitxer" + +#: includes/Admin/AlertsPage.php:411 includes/Admin/AlertsPage.php:543 +#: includes/Admin/AttachmentDetailPage.php:372 includes/Admin/AuditPage.php:566 +msgid "Alert Domains" +msgstr "Dominis d'alerta" + +#: includes/Admin/AlertsPage.php:412 +msgid "Usage" +msgstr "Usos" + +#: includes/Admin/AlertsPage.php:413 includes/Admin/AlertsPage.php:547 +msgid "Actions" +msgstr "Accions" + +#: includes/Admin/AlertsPage.php:447 includes/Admin/AlertsPage.php:587 +#: includes/Admin/MediaListTable.php:172 +msgid "Edit" +msgstr "Edita" + +#: includes/Admin/AlertsPage.php:449 includes/Admin/AlertsPage.php:589 +#: includes/Admin/MediaListTable.php:182 +msgid "Full Report" +msgstr "Informe complet" + +#: includes/Admin/AlertsPage.php:459 includes/Admin/AuditPage.php:382 +#: includes/Admin/AuditPage.php:489 includes/Admin/MediaListTable.php:315 +#: includes/Admin/MediaListTable.php:452 +msgid "Alert" +msgstr "Alerta" + +#: includes/Admin/AlertsPage.php:473 +msgid "Dismiss" +msgstr "Descartar" + +#: includes/Admin/AlertsPage.php:485 +msgid "Notes (optional):" +msgstr "Notes (opcional):" + +#: includes/Admin/AlertsPage.php:495 +msgid "Confirm Dismiss" +msgstr "Confirmar descart" + +#: includes/Admin/AlertsPage.php:496 +msgid "Cancel" +msgstr "Cancel·lar" + +#: includes/Admin/AlertsPage.php:534 +msgid "No dismissed alerts." +msgstr "No hi ha alertes descartades." + +#: includes/Admin/AlertsPage.php:544 +msgid "Notes" +msgstr "Notes" + +#: includes/Admin/AlertsPage.php:545 +msgid "Dismissed By" +msgstr "Descartada per" + +#: includes/Admin/AlertsPage.php:546 +msgid "Dismissed At" +msgstr "Descartada el" + +#: includes/Admin/AlertsPage.php:599 includes/Admin/AuditPage.php:569 +#: includes/Admin/MediaListTable.php:302 +msgid "Dismissed" +msgstr "Descartada" + +#: includes/Admin/AlertsPage.php:616 +msgid "Reactivate" +msgstr "Reactivar" + #: includes/Admin/AttachmentDetailPage.php:51 -#: includes/Admin/AttachmentDetailPage.php:56 includes/Admin/AuditPage.php:180 +#: includes/Admin/AttachmentDetailPage.php:56 includes/Admin/AuditPage.php:181 msgid "Invalid attachment ID." msgstr "Identificador de fitxer adjunt no vàlid." @@ -64,21 +187,16 @@ msgid "File Information" msgstr "Informació del fitxer" #: includes/Admin/AttachmentDetailPage.php:81 -#: includes/Admin/MediaListTable.php:56 +#: includes/Admin/MediaListTable.php:57 msgid "ID" msgstr "ID" -#: includes/Admin/AttachmentDetailPage.php:87 includes/Admin/AuditPage.php:553 -#: includes/Admin/MediaListTable.php:55 includes/Privacy/DataExporter.php:261 -msgid "Filename" -msgstr "Nom de fitxer" - -#: includes/Admin/AttachmentDetailPage.php:94 includes/Admin/AuditPage.php:554 +#: includes/Admin/AttachmentDetailPage.php:94 includes/Admin/AuditPage.php:555 #: includes/Privacy/DataExporter.php:265 msgid "File URL" msgstr "URL del fitxer" -#: includes/Admin/AttachmentDetailPage.php:102 includes/Admin/AuditPage.php:555 +#: includes/Admin/AttachmentDetailPage.php:102 includes/Admin/AuditPage.php:556 msgid "MIME Type" msgstr "Tipus MIME" @@ -90,24 +208,24 @@ msgstr "Mida del fitxer" msgid "Internal scan date" msgstr "Data de l'exploració interna" -#: includes/Admin/AttachmentDetailPage.php:124 includes/Admin/AuditPage.php:558 -#: includes/Admin/MediaListTable.php:59 +#: includes/Admin/AttachmentDetailPage.php:124 includes/Admin/AuditPage.php:559 +#: includes/Admin/MediaListTable.php:60 msgid "External Status" msgstr "Estat extern" -#: includes/Admin/AttachmentDetailPage.php:139 includes/Admin/AuditPage.php:244 +#: includes/Admin/AttachmentDetailPage.php:139 includes/Admin/AuditPage.php:245 msgid "Internal Usage" msgstr "Ús intern" -#: includes/Admin/AttachmentDetailPage.php:144 includes/Admin/AuditPage.php:188 +#: includes/Admin/AttachmentDetailPage.php:144 includes/Admin/AuditPage.php:189 msgid "Featured Image" msgstr "Imatge destacada" -#: includes/Admin/AttachmentDetailPage.php:145 includes/Admin/AuditPage.php:189 +#: includes/Admin/AttachmentDetailPage.php:145 includes/Admin/AuditPage.php:190 msgid "Post Content" msgstr "Contingut de l'entrada" -#: includes/Admin/AttachmentDetailPage.php:146 includes/Admin/AuditPage.php:190 +#: includes/Admin/AttachmentDetailPage.php:146 includes/Admin/AuditPage.php:191 msgid "Custom Field" msgstr "Camp personalitzat" @@ -115,24 +233,24 @@ msgstr "Camp personalitzat" msgid "Not used in any post." msgstr "Aquest fitxer no s'utilitza en cap entrada." -#: includes/Admin/AttachmentDetailPage.php:154 includes/Admin/AuditPage.php:198 +#: includes/Admin/AttachmentDetailPage.php:154 includes/Admin/AuditPage.php:199 msgid "Post" msgstr "Entrada" -#: includes/Admin/AttachmentDetailPage.php:155 includes/Admin/AuditPage.php:199 +#: includes/Admin/AttachmentDetailPage.php:155 includes/Admin/AuditPage.php:200 msgid "Type" msgstr "Tipus" -#: includes/Admin/AttachmentDetailPage.php:156 includes/Admin/AuditPage.php:200 +#: includes/Admin/AttachmentDetailPage.php:156 includes/Admin/AuditPage.php:201 msgid "Context" msgstr "Context" -#: includes/Admin/AttachmentDetailPage.php:157 includes/Admin/AuditPage.php:201 -#: includes/Admin/AuditPage.php:373 +#: includes/Admin/AttachmentDetailPage.php:157 includes/Admin/AuditPage.php:202 +#: includes/Admin/AuditPage.php:374 msgid "Status" msgstr "Estat" -#: includes/Admin/AttachmentDetailPage.php:198 includes/Admin/AuditPage.php:293 +#: includes/Admin/AttachmentDetailPage.php:198 includes/Admin/AuditPage.php:294 msgid "External Scan Results" msgstr "Resultats de l'exploració externa" @@ -141,7 +259,7 @@ msgid "No external scan data available." msgstr "No hi ha dades d'exploració externa disponibles." #. translators: %d: number of external matches -#: includes/Admin/AttachmentDetailPage.php:313 includes/Admin/AuditPage.php:323 +#: includes/Admin/AttachmentDetailPage.php:313 includes/Admin/AuditPage.php:324 #, php-format msgid "%d match" msgid_plural "%d matches" @@ -154,10 +272,6 @@ msgstr[1] "%d coincidències" msgid "scanned %s" msgstr "explorat el %s" -#: includes/Admin/AttachmentDetailPage.php:372 includes/Admin/AuditPage.php:565 -msgid "Alert Domains" -msgstr "Dominis d'alerta" - #: includes/Admin/AttachmentDetailPage.php:373 msgid "" "These domains are associated with copyright-protected content and may " @@ -168,21 +282,21 @@ msgstr "" #: includes/Admin/AttachmentDetailPage.php:376 #: includes/Admin/AttachmentDetailPage.php:392 -#: includes/Admin/AttachmentDetailPage.php:408 includes/Admin/AuditPage.php:371 +#: includes/Admin/AttachmentDetailPage.php:408 includes/Admin/AuditPage.php:372 msgid "Domain" msgstr "Domini" #: includes/Admin/AttachmentDetailPage.php:377 #: includes/Admin/AttachmentDetailPage.php:393 -#: includes/Admin/AttachmentDetailPage.php:409 includes/Admin/AuditPage.php:372 +#: includes/Admin/AttachmentDetailPage.php:409 includes/Admin/AuditPage.php:373 msgid "Occurrences" msgstr "Aparicions" -#: includes/Admin/AttachmentDetailPage.php:389 includes/Admin/AuditPage.php:566 +#: includes/Admin/AttachmentDetailPage.php:389 includes/Admin/AuditPage.php:567 msgid "Other Domains" msgstr "Altres dominis" -#: includes/Admin/AttachmentDetailPage.php:405 includes/Admin/AuditPage.php:567 +#: includes/Admin/AttachmentDetailPage.php:405 includes/Admin/AuditPage.php:568 msgid "Ignored Domains" msgstr "Dominis ignorats" @@ -267,247 +381,235 @@ msgstr "Similitud" msgid "Labels" msgstr "Etiquetes" -#: includes/Admin/AuditPage.php:116 +#: includes/Admin/AuditPage.php:117 msgid "Loading…" msgstr "S'està carregant…" -#: includes/Admin/AuditPage.php:117 +#: includes/Admin/AuditPage.php:118 msgid "Error loading details." msgstr "S'ha produït un error en carregar els detalls." -#: includes/Admin/AuditPage.php:118 includes/Admin/AuditPage.php:194 +#: includes/Admin/AuditPage.php:119 includes/Admin/AuditPage.php:195 msgid "This image is not used in any post." msgstr "Aquesta imatge no s'utilitza en cap entrada." -#: includes/Admin/AuditPage.php:119 includes/Admin/AuditPage.php:156 +#: includes/Admin/AuditPage.php:120 includes/Admin/AuditPage.php:157 msgid "Usage Details" msgstr "Detalls d'ús" -#: includes/Admin/AuditPage.php:142 includes/Core/Plugin.php:109 -#: includes/Core/Plugin.php:110 includes/Core/Plugin.php:123 -#: includes/Core/Plugin.php:124 includes/Privacy/DataEraser.php:33 +#: includes/Admin/AuditPage.php:143 includes/Core/Plugin.php:118 +#: includes/Core/Plugin.php:119 includes/Core/Plugin.php:132 +#: includes/Core/Plugin.php:133 includes/Privacy/DataEraser.php:33 #: includes/Privacy/DataExporter.php:45 msgid "Media Audit" msgstr "Auditoria de mitjans" -#: includes/Admin/AuditPage.php:148 +#: includes/Admin/AuditPage.php:149 msgid "Search" msgstr "Cerca" -#: includes/Admin/AuditPage.php:157 +#: includes/Admin/AuditPage.php:158 msgid "Close" msgstr "Tanca" -#: includes/Admin/AuditPage.php:175 includes/Admin/AuditPage.php:529 -#: includes/Admin/AuditPage.php:776 includes/Admin/AuditPage.php:808 +#: includes/Admin/AuditPage.php:176 includes/Admin/AuditPage.php:530 +#: includes/Admin/AuditPage.php:806 includes/Admin/AuditPage.php:838 #: includes/Admin/ToolsPage.php:105 includes/Admin/ToolsPage.php:165 msgid "Insufficient permissions." msgstr "Permisos insuficients." -#: includes/Admin/AuditPage.php:328 +#: includes/Admin/AuditPage.php:329 msgid "No matches" msgstr "Sense coincidències" -#: includes/Admin/AuditPage.php:381 includes/Admin/AuditPage.php:488 -#: includes/Admin/MediaListTable.php:298 includes/Admin/MediaListTable.php:435 -msgid "Alert" -msgstr "Alerta" - -#: includes/Admin/AuditPage.php:403 +#: includes/Admin/AuditPage.php:404 msgid "Consensus Domains" msgstr "Dominis de consens" #. translators: %d: number of providers that agree -#: includes/Admin/AuditPage.php:409 +#: includes/Admin/AuditPage.php:410 #, php-format msgid "%d provider" msgid_plural "%d providers" msgstr[0] "%d proveïdor" msgstr[1] "%d proveïdors" -#: includes/Admin/AuditPage.php:419 +#: includes/Admin/AuditPage.php:420 msgid "View full report →" msgstr "Veure l'informe complet →" -#: includes/Admin/AuditPage.php:445 +#: includes/Admin/AuditPage.php:446 msgid "In Index" msgstr "A l'índex" #. translators: %d: count of images not yet indexed -#: includes/Admin/AuditPage.php:449 +#: includes/Admin/AuditPage.php:450 #, php-format msgid "%d not yet indexed" msgstr "%d encara no indexades" -#: includes/Admin/AuditPage.php:452 +#: includes/Admin/AuditPage.php:453 msgid "All indexed" msgstr "Totes indexades" -#: includes/Admin/AuditPage.php:457 includes/Admin/MediaListTable.php:329 +#: includes/Admin/AuditPage.php:458 includes/Admin/MediaListTable.php:346 msgid "Scanned" msgstr "Explorades" #. translators: %d: percentage -#: includes/Admin/AuditPage.php:460 +#: includes/Admin/AuditPage.php:461 #, php-format msgid "%d%% of index" msgstr "%d%% de l'índex" -#: includes/Admin/AuditPage.php:467 +#: includes/Admin/AuditPage.php:468 msgid "Used" msgstr "Usades" -#: includes/Admin/AuditPage.php:473 includes/Admin/MediaListTable.php:211 +#: includes/Admin/AuditPage.php:474 includes/Admin/MediaListTable.php:212 msgid "Unused" msgstr "No usades" -#: includes/Admin/AuditPage.php:480 +#: includes/Admin/AuditPage.php:481 msgid "External Matches" msgstr "Coincidències externes" -#: includes/Admin/AuditPage.php:543 +#: includes/Admin/AuditPage.php:544 msgid "Could not open output stream." msgstr "No s'ha pogut obrir el flux de sortida." -#: includes/Admin/AuditPage.php:552 includes/Privacy/DataExporter.php:257 +#: includes/Admin/AuditPage.php:553 includes/Privacy/DataExporter.php:257 #: includes/Privacy/DataExporter.php:299 msgid "Attachment ID" msgstr "ID del fitxer adjunt" -#: includes/Admin/AuditPage.php:556 +#: includes/Admin/AuditPage.php:557 msgid "File Size (bytes)" msgstr "Mida del fitxer (bytes)" -#: includes/Admin/AuditPage.php:557 +#: includes/Admin/AuditPage.php:558 msgid "Internal Scan Date" msgstr "Data de l'escaneig intern" -#: includes/Admin/AuditPage.php:559 +#: includes/Admin/AuditPage.php:560 msgid "Has Alert" msgstr "Té alerta" -#: includes/Admin/AuditPage.php:560 includes/Admin/MediaListTable.php:57 +#: includes/Admin/AuditPage.php:561 includes/Admin/MediaListTable.php:58 msgid "Usage Count" msgstr "Nombre d'usos" -#: includes/Admin/AuditPage.php:561 +#: includes/Admin/AuditPage.php:562 msgid "Used In" msgstr "Usat a" -#: includes/Admin/AuditPage.php:562 +#: includes/Admin/AuditPage.php:563 msgid "Google Vision — Matches" msgstr "Google Vision — Coincidències" -#: includes/Admin/AuditPage.php:563 +#: includes/Admin/AuditPage.php:564 msgid "TinEye — Matches" msgstr "TinEye — Coincidències" -#: includes/Admin/AuditPage.php:564 +#: includes/Admin/AuditPage.php:565 msgid "PicDefense — Matches" msgstr "PicDefense — Coincidències" -#: includes/Admin/AuditPage.php:748 +#: includes/Admin/AuditPage.php:570 +msgid "Dismissed Notes" +msgstr "Notes de descart" + +#: includes/Admin/AuditPage.php:776 includes/Admin/AuditPage.php:788 msgid "Yes" msgstr "Sí" -#: includes/Admin/AuditPage.php:748 +#: includes/Admin/AuditPage.php:776 includes/Admin/AuditPage.php:788 msgid "No" msgstr "No" -#: includes/Admin/MediaListTable.php:54 -msgid "Thumbnail" -msgstr "Miniatura" - -#: includes/Admin/MediaListTable.php:58 +#: includes/Admin/MediaListTable.php:59 msgid "Usages" msgstr "Usos" -#: includes/Admin/MediaListTable.php:60 +#: includes/Admin/MediaListTable.php:61 msgid "Last Scanned" msgstr "Darrera exploració" -#: includes/Admin/MediaListTable.php:84 +#: includes/Admin/MediaListTable.php:85 msgid "Run External Scan" msgstr "Executar exploració externa" -#: includes/Admin/MediaListTable.php:85 +#: includes/Admin/MediaListTable.php:86 msgid "Purge External Data" msgstr "Eliminar dades externes" -#: includes/Admin/MediaListTable.php:86 +#: includes/Admin/MediaListTable.php:87 msgid "Export CSV" msgstr "Exportar CSV" -#: includes/Admin/MediaListTable.php:87 +#: includes/Admin/MediaListTable.php:88 msgid "Export CSV (Alerts only)" msgstr "Exportar CSV (només alertes)" -#: includes/Admin/MediaListTable.php:171 -msgid "Edit" -msgstr "Edita" - -#: includes/Admin/MediaListTable.php:176 +#: includes/Admin/MediaListTable.php:177 msgid "View Details" msgstr "Visualitza els detalls" -#: includes/Admin/MediaListTable.php:181 -msgid "Full Report" -msgstr "Informe complet" - -#: includes/Admin/MediaListTable.php:247 +#: includes/Admin/MediaListTable.php:248 msgid "(no title)" msgstr "(sense títol)" #. translators: %d: number of additional usages -#: includes/Admin/MediaListTable.php:262 +#: includes/Admin/MediaListTable.php:263 #, php-format msgid "+ %d more…" msgstr "+ %d més…" -#: includes/Admin/MediaListTable.php:320 includes/Admin/MediaListTable.php:359 +#: includes/Admin/MediaListTable.php:307 includes/Admin/MediaListTable.php:337 +#: includes/Admin/MediaListTable.php:376 msgid "View Results" msgstr "Visualitza els resultats" -#: includes/Admin/MediaListTable.php:327 includes/Admin/ToolsPage.php:589 +#: includes/Admin/MediaListTable.php:344 includes/Admin/ToolsPage.php:589 #: includes/Admin/ToolsPage.php:590 includes/Admin/ToolsPage.php:591 msgid "Pending" msgstr "Pendent" -#: includes/Admin/MediaListTable.php:328 +#: includes/Admin/MediaListTable.php:345 msgid "Queued" msgstr "En cua" -#: includes/Admin/MediaListTable.php:330 +#: includes/Admin/MediaListTable.php:347 msgid "Matches Found" msgstr "Coincidències trobades" -#: includes/Admin/MediaListTable.php:331 +#: includes/Admin/MediaListTable.php:348 msgid "Error" msgstr "Error" #. translators: %s: human-readable time difference, e.g. "3 days" -#: includes/Admin/MediaListTable.php:401 +#: includes/Admin/MediaListTable.php:418 #, php-format msgid "%s ago" msgstr "fa %s" -#: includes/Admin/MediaListTable.php:431 +#: includes/Admin/MediaListTable.php:448 msgid "All statuses" msgstr "Tots els estats" -#: includes/Admin/MediaListTable.php:450 +#: includes/Admin/MediaListTable.php:467 msgid "All post types" msgstr "Tots els tipus d'entrada" -#: includes/Admin/MediaListTable.php:465 +#: includes/Admin/MediaListTable.php:482 msgid "Unused only" msgstr "Només no usades" -#: includes/Admin/MediaListTable.php:468 +#: includes/Admin/MediaListTable.php:485 msgid "Filter" msgstr "Filtra" -#: includes/Admin/MediaListTable.php:478 +#: includes/Admin/MediaListTable.php:495 msgid "" "No indexed attachments found. Run wp mra scan-internal to populate the index." msgstr "" @@ -797,10 +899,6 @@ msgstr "Dades d'exploració externa eliminades." msgid "All data reset. Fresh indexing scheduled." msgstr "Totes les dades restablertes. Nova indexació programada." -#: includes/Admin/ToolsPage.php:376 -msgid "Operation completed." -msgstr "Operació completada." - #: includes/Admin/ToolsPage.php:420 msgid "Database schema" msgstr "Esquema de base de dades" @@ -819,7 +917,7 @@ msgstr "Instal·lat: %1$s — Requerit: %2$s" #: includes/Admin/ToolsPage.php:441 msgid "Deactivate and reactivate the plugin to apply pending migrations." -msgstr "" +msgstr "Desactiva i reactiva el plugin per aplicar les migracions pendents." "Desactiveu i reactiveu el connector per aplicar les migracions pendents." #: includes/Admin/ToolsPage.php:448 @@ -1207,15 +1305,15 @@ msgstr "S'estan executant les exploracions externes…" msgid "External scan complete. %d attachments processed." msgstr "Exploració externa completada. %d fitxers adjunts processats." -#: includes/Core/Plugin.php:132 includes/Core/Plugin.php:133 +#: includes/Core/Plugin.php:154 includes/Core/Plugin.php:155 msgid "Tools" msgstr "Eines" -#: includes/Core/Plugin.php:145 includes/Core/Plugin.php:146 +#: includes/Core/Plugin.php:167 includes/Core/Plugin.php:168 msgid "Settings" msgstr "Configuració" -#: includes/Core/Plugin.php:154 +#: includes/Core/Plugin.php:176 msgid "Attachment Report" msgstr "Informe del fitxer adjunt" diff --git a/languages/robotstxt-mediaaudit-es_ES.mo b/languages/robotstxt-mediaaudit-es_ES.mo index f184218..f4d442f 100644 Binary files a/languages/robotstxt-mediaaudit-es_ES.mo and b/languages/robotstxt-mediaaudit-es_ES.mo differ diff --git a/languages/robotstxt-mediaaudit-es_ES.po b/languages/robotstxt-mediaaudit-es_ES.po index 0618d7d..aaac0d0 100644 --- a/languages/robotstxt-mediaaudit-es_ES.po +++ b/languages/robotstxt-mediaaudit-es_ES.po @@ -2,10 +2,10 @@ # This file is distributed under the GPL-3.0-or-later. msgid "" msgstr "" -"Project-Id-Version: Media Audit (by ROBOTSTXT) 1.6.0\n" +"Project-Id-Version: Media Audit (by ROBOTSTXT) 1.7.0\n" "Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/robotstxt-" "mediaaudit\n" -"POT-Creation-Date: 2026-05-05T10:39:07+00:00\n" +"POT-Creation-Date: 2026-05-11T07:13:58+00:00\n" "PO-Revision-Date: 2026-05-01 07:41+0000\n" "Last-Translator: Javier Casares \n" "Language-Team: Spanish (Spain) \n" @@ -45,13 +45,136 @@ msgstr "" msgid "https://www.robotstxt.es/" msgstr "" -#: includes/Admin/AttachmentDetailPage.php:40 includes/Admin/AuditPage.php:135 -#: includes/Admin/Settings.php:898 includes/Admin/ToolsPage.php:253 +#: includes/Admin/AlertsPage.php:210 +msgid "You do not have permission to perform this action." +msgstr "No tienes permiso para realizar esta acción." + +#: includes/Admin/AlertsPage.php:263 includes/Admin/AttachmentDetailPage.php:40 +#: includes/Admin/AuditPage.php:136 includes/Admin/Settings.php:898 +#: includes/Admin/ToolsPage.php:253 msgid "You do not have permission to access this page." msgstr "No tienes permiso para acceder a esta página." +#: includes/Admin/AlertsPage.php:280 includes/Core/Plugin.php:141 +#: includes/Core/Plugin.php:142 +msgid "Alerts" +msgstr "Alertas" + +#. translators: %d: count of active alerts +#: includes/Admin/AlertsPage.php:292 +#, php-format +msgid "Active Alerts (%d)" +msgstr "Alertas activas (%d)" + +#. translators: %d: count of dismissed alerts +#: includes/Admin/AlertsPage.php:304 +#, php-format +msgid "Dismissed Alerts (%d)" +msgstr "Alertas descartadas (%d)" + +#: includes/Admin/AlertsPage.php:376 +msgid "Alert dismissed successfully." +msgstr "Alerta descartada correctamente." + +#: includes/Admin/AlertsPage.php:377 +msgid "Alert reactivated successfully." +msgstr "Alerta reactivada correctamente." + +#: includes/Admin/AlertsPage.php:378 +msgid "An error occurred. Please try again." +msgstr "Se ha producido un error. Por favor, inténtalo de nuevo." + +#: includes/Admin/AlertsPage.php:382 includes/Admin/ToolsPage.php:376 +msgid "Operation completed." +msgstr "Operación completada." + +#: includes/Admin/AlertsPage.php:402 +msgid "No active alerts found." +msgstr "No se encontraron alertas activas." + +#: includes/Admin/AlertsPage.php:409 includes/Admin/AlertsPage.php:541 +#: includes/Admin/MediaListTable.php:55 +msgid "Thumbnail" +msgstr "Miniatura" + +#: includes/Admin/AlertsPage.php:410 includes/Admin/AlertsPage.php:542 +#: includes/Admin/AttachmentDetailPage.php:87 includes/Admin/AuditPage.php:554 +#: includes/Admin/MediaListTable.php:56 includes/Privacy/DataExporter.php:261 +msgid "Filename" +msgstr "Nombre de archivo" + +#: includes/Admin/AlertsPage.php:411 includes/Admin/AlertsPage.php:543 +#: includes/Admin/AttachmentDetailPage.php:372 includes/Admin/AuditPage.php:566 +msgid "Alert Domains" +msgstr "Dominios de alerta" + +#: includes/Admin/AlertsPage.php:412 +msgid "Usage" +msgstr "Usos" + +#: includes/Admin/AlertsPage.php:413 includes/Admin/AlertsPage.php:547 +msgid "Actions" +msgstr "Acciones" + +#: includes/Admin/AlertsPage.php:447 includes/Admin/AlertsPage.php:587 +#: includes/Admin/MediaListTable.php:172 +msgid "Edit" +msgstr "Editar" + +#: includes/Admin/AlertsPage.php:449 includes/Admin/AlertsPage.php:589 +#: includes/Admin/MediaListTable.php:182 +msgid "Full Report" +msgstr "Informe completo" + +#: includes/Admin/AlertsPage.php:459 includes/Admin/AuditPage.php:382 +#: includes/Admin/AuditPage.php:489 includes/Admin/MediaListTable.php:315 +#: includes/Admin/MediaListTable.php:452 +msgid "Alert" +msgstr "Alerta" + +#: includes/Admin/AlertsPage.php:473 +msgid "Dismiss" +msgstr "Descartar" + +#: includes/Admin/AlertsPage.php:485 +msgid "Notes (optional):" +msgstr "Notas (opcional):" + +#: includes/Admin/AlertsPage.php:495 +msgid "Confirm Dismiss" +msgstr "Confirmar descarte" + +#: includes/Admin/AlertsPage.php:496 +msgid "Cancel" +msgstr "Cancelar" + +#: includes/Admin/AlertsPage.php:534 +msgid "No dismissed alerts." +msgstr "No hay alertas descartadas." + +#: includes/Admin/AlertsPage.php:544 +msgid "Notes" +msgstr "Notas" + +#: includes/Admin/AlertsPage.php:545 +msgid "Dismissed By" +msgstr "Descartada por" + +#: includes/Admin/AlertsPage.php:546 +msgid "Dismissed At" +msgstr "Descartada el" + +#: includes/Admin/AlertsPage.php:599 includes/Admin/AuditPage.php:569 +#: includes/Admin/MediaListTable.php:302 +msgid "Dismissed" +msgstr "Descartada" + +#: includes/Admin/AlertsPage.php:616 +msgid "Reactivate" +msgstr "Reactivar" + #: includes/Admin/AttachmentDetailPage.php:51 -#: includes/Admin/AttachmentDetailPage.php:56 includes/Admin/AuditPage.php:180 +#: includes/Admin/AttachmentDetailPage.php:56 includes/Admin/AuditPage.php:181 msgid "Invalid attachment ID." msgstr "ID de adjunto no válido." @@ -64,21 +187,16 @@ msgid "File Information" msgstr "Información del archivo" #: includes/Admin/AttachmentDetailPage.php:81 -#: includes/Admin/MediaListTable.php:56 +#: includes/Admin/MediaListTable.php:57 msgid "ID" msgstr "ID" -#: includes/Admin/AttachmentDetailPage.php:87 includes/Admin/AuditPage.php:553 -#: includes/Admin/MediaListTable.php:55 includes/Privacy/DataExporter.php:261 -msgid "Filename" -msgstr "Nombre de archivo" - -#: includes/Admin/AttachmentDetailPage.php:94 includes/Admin/AuditPage.php:554 +#: includes/Admin/AttachmentDetailPage.php:94 includes/Admin/AuditPage.php:555 #: includes/Privacy/DataExporter.php:265 msgid "File URL" msgstr "URL del archivo" -#: includes/Admin/AttachmentDetailPage.php:102 includes/Admin/AuditPage.php:555 +#: includes/Admin/AttachmentDetailPage.php:102 includes/Admin/AuditPage.php:556 msgid "MIME Type" msgstr "Tipo MIME" @@ -90,24 +208,24 @@ msgstr "Tamaño del archivo" msgid "Internal scan date" msgstr "Fecha del escaneo interno" -#: includes/Admin/AttachmentDetailPage.php:124 includes/Admin/AuditPage.php:558 -#: includes/Admin/MediaListTable.php:59 +#: includes/Admin/AttachmentDetailPage.php:124 includes/Admin/AuditPage.php:559 +#: includes/Admin/MediaListTable.php:60 msgid "External Status" msgstr "Estado externo" -#: includes/Admin/AttachmentDetailPage.php:139 includes/Admin/AuditPage.php:244 +#: includes/Admin/AttachmentDetailPage.php:139 includes/Admin/AuditPage.php:245 msgid "Internal Usage" msgstr "Uso interno" -#: includes/Admin/AttachmentDetailPage.php:144 includes/Admin/AuditPage.php:188 +#: includes/Admin/AttachmentDetailPage.php:144 includes/Admin/AuditPage.php:189 msgid "Featured Image" msgstr "Imagen destacada" -#: includes/Admin/AttachmentDetailPage.php:145 includes/Admin/AuditPage.php:189 +#: includes/Admin/AttachmentDetailPage.php:145 includes/Admin/AuditPage.php:190 msgid "Post Content" msgstr "Contenido de la entrada" -#: includes/Admin/AttachmentDetailPage.php:146 includes/Admin/AuditPage.php:190 +#: includes/Admin/AttachmentDetailPage.php:146 includes/Admin/AuditPage.php:191 msgid "Custom Field" msgstr "Campo personalizado" @@ -115,24 +233,24 @@ msgstr "Campo personalizado" msgid "Not used in any post." msgstr "Este archivo no se usa en ninguna entrada." -#: includes/Admin/AttachmentDetailPage.php:154 includes/Admin/AuditPage.php:198 +#: includes/Admin/AttachmentDetailPage.php:154 includes/Admin/AuditPage.php:199 msgid "Post" msgstr "Entrada" -#: includes/Admin/AttachmentDetailPage.php:155 includes/Admin/AuditPage.php:199 +#: includes/Admin/AttachmentDetailPage.php:155 includes/Admin/AuditPage.php:200 msgid "Type" msgstr "Tipo" -#: includes/Admin/AttachmentDetailPage.php:156 includes/Admin/AuditPage.php:200 +#: includes/Admin/AttachmentDetailPage.php:156 includes/Admin/AuditPage.php:201 msgid "Context" msgstr "Contexto" -#: includes/Admin/AttachmentDetailPage.php:157 includes/Admin/AuditPage.php:201 -#: includes/Admin/AuditPage.php:373 +#: includes/Admin/AttachmentDetailPage.php:157 includes/Admin/AuditPage.php:202 +#: includes/Admin/AuditPage.php:374 msgid "Status" msgstr "Estado" -#: includes/Admin/AttachmentDetailPage.php:198 includes/Admin/AuditPage.php:293 +#: includes/Admin/AttachmentDetailPage.php:198 includes/Admin/AuditPage.php:294 msgid "External Scan Results" msgstr "Resultados del escaneo externo" @@ -141,7 +259,7 @@ msgid "No external scan data available." msgstr "No hay datos de escaneo externo disponibles." #. translators: %d: number of external matches -#: includes/Admin/AttachmentDetailPage.php:313 includes/Admin/AuditPage.php:323 +#: includes/Admin/AttachmentDetailPage.php:313 includes/Admin/AuditPage.php:324 #, php-format msgid "%d match" msgid_plural "%d matches" @@ -154,10 +272,6 @@ msgstr[1] "%d coincidencias" msgid "scanned %s" msgstr "escaneado el %s" -#: includes/Admin/AttachmentDetailPage.php:372 includes/Admin/AuditPage.php:565 -msgid "Alert Domains" -msgstr "Dominios de alerta" - #: includes/Admin/AttachmentDetailPage.php:373 msgid "" "These domains are associated with copyright-protected content and may " @@ -168,21 +282,21 @@ msgstr "" #: includes/Admin/AttachmentDetailPage.php:376 #: includes/Admin/AttachmentDetailPage.php:392 -#: includes/Admin/AttachmentDetailPage.php:408 includes/Admin/AuditPage.php:371 +#: includes/Admin/AttachmentDetailPage.php:408 includes/Admin/AuditPage.php:372 msgid "Domain" msgstr "Dominio" #: includes/Admin/AttachmentDetailPage.php:377 #: includes/Admin/AttachmentDetailPage.php:393 -#: includes/Admin/AttachmentDetailPage.php:409 includes/Admin/AuditPage.php:372 +#: includes/Admin/AttachmentDetailPage.php:409 includes/Admin/AuditPage.php:373 msgid "Occurrences" msgstr "Apariciones" -#: includes/Admin/AttachmentDetailPage.php:389 includes/Admin/AuditPage.php:566 +#: includes/Admin/AttachmentDetailPage.php:389 includes/Admin/AuditPage.php:567 msgid "Other Domains" msgstr "Otros dominios" -#: includes/Admin/AttachmentDetailPage.php:405 includes/Admin/AuditPage.php:567 +#: includes/Admin/AttachmentDetailPage.php:405 includes/Admin/AuditPage.php:568 msgid "Ignored Domains" msgstr "Dominios ignorados" @@ -267,247 +381,235 @@ msgstr "Similitud" msgid "Labels" msgstr "Etiquetas" -#: includes/Admin/AuditPage.php:116 +#: includes/Admin/AuditPage.php:117 msgid "Loading…" msgstr "Cargando…" -#: includes/Admin/AuditPage.php:117 +#: includes/Admin/AuditPage.php:118 msgid "Error loading details." msgstr "Error al cargar los detalles." -#: includes/Admin/AuditPage.php:118 includes/Admin/AuditPage.php:194 +#: includes/Admin/AuditPage.php:119 includes/Admin/AuditPage.php:195 msgid "This image is not used in any post." msgstr "Esta imagen no se usa en ninguna entrada." -#: includes/Admin/AuditPage.php:119 includes/Admin/AuditPage.php:156 +#: includes/Admin/AuditPage.php:120 includes/Admin/AuditPage.php:157 msgid "Usage Details" msgstr "Detalles de uso" -#: includes/Admin/AuditPage.php:142 includes/Core/Plugin.php:109 -#: includes/Core/Plugin.php:110 includes/Core/Plugin.php:123 -#: includes/Core/Plugin.php:124 includes/Privacy/DataEraser.php:33 +#: includes/Admin/AuditPage.php:143 includes/Core/Plugin.php:118 +#: includes/Core/Plugin.php:119 includes/Core/Plugin.php:132 +#: includes/Core/Plugin.php:133 includes/Privacy/DataEraser.php:33 #: includes/Privacy/DataExporter.php:45 msgid "Media Audit" msgstr "Auditoría de medios" -#: includes/Admin/AuditPage.php:148 +#: includes/Admin/AuditPage.php:149 msgid "Search" msgstr "Buscar" -#: includes/Admin/AuditPage.php:157 +#: includes/Admin/AuditPage.php:158 msgid "Close" msgstr "Cerrar" -#: includes/Admin/AuditPage.php:175 includes/Admin/AuditPage.php:529 -#: includes/Admin/AuditPage.php:776 includes/Admin/AuditPage.php:808 +#: includes/Admin/AuditPage.php:176 includes/Admin/AuditPage.php:530 +#: includes/Admin/AuditPage.php:806 includes/Admin/AuditPage.php:838 #: includes/Admin/ToolsPage.php:105 includes/Admin/ToolsPage.php:165 msgid "Insufficient permissions." msgstr "Permisos insuficientes." -#: includes/Admin/AuditPage.php:328 +#: includes/Admin/AuditPage.php:329 msgid "No matches" msgstr "Sin coincidencias" -#: includes/Admin/AuditPage.php:381 includes/Admin/AuditPage.php:488 -#: includes/Admin/MediaListTable.php:298 includes/Admin/MediaListTable.php:435 -msgid "Alert" -msgstr "Alerta" - -#: includes/Admin/AuditPage.php:403 +#: includes/Admin/AuditPage.php:404 msgid "Consensus Domains" msgstr "Dominios de consenso" #. translators: %d: number of providers that agree -#: includes/Admin/AuditPage.php:409 +#: includes/Admin/AuditPage.php:410 #, php-format msgid "%d provider" msgid_plural "%d providers" msgstr[0] "%d proveedor" msgstr[1] "%d proveedores" -#: includes/Admin/AuditPage.php:419 +#: includes/Admin/AuditPage.php:420 msgid "View full report →" msgstr "Ver informe completo →" -#: includes/Admin/AuditPage.php:445 +#: includes/Admin/AuditPage.php:446 msgid "In Index" msgstr "En el índice" #. translators: %d: count of images not yet indexed -#: includes/Admin/AuditPage.php:449 +#: includes/Admin/AuditPage.php:450 #, php-format msgid "%d not yet indexed" msgstr "%d aún no indexadas" -#: includes/Admin/AuditPage.php:452 +#: includes/Admin/AuditPage.php:453 msgid "All indexed" msgstr "Todas indexadas" -#: includes/Admin/AuditPage.php:457 includes/Admin/MediaListTable.php:329 +#: includes/Admin/AuditPage.php:458 includes/Admin/MediaListTable.php:346 msgid "Scanned" msgstr "Escaneadas" #. translators: %d: percentage -#: includes/Admin/AuditPage.php:460 +#: includes/Admin/AuditPage.php:461 #, php-format msgid "%d%% of index" msgstr "%d%% del índice" -#: includes/Admin/AuditPage.php:467 +#: includes/Admin/AuditPage.php:468 msgid "Used" msgstr "Usadas" -#: includes/Admin/AuditPage.php:473 includes/Admin/MediaListTable.php:211 +#: includes/Admin/AuditPage.php:474 includes/Admin/MediaListTable.php:212 msgid "Unused" msgstr "Sin usar" -#: includes/Admin/AuditPage.php:480 +#: includes/Admin/AuditPage.php:481 msgid "External Matches" msgstr "Coincidencias externas" -#: includes/Admin/AuditPage.php:543 +#: includes/Admin/AuditPage.php:544 msgid "Could not open output stream." msgstr "No se pudo abrir el flujo de salida." -#: includes/Admin/AuditPage.php:552 includes/Privacy/DataExporter.php:257 +#: includes/Admin/AuditPage.php:553 includes/Privacy/DataExporter.php:257 #: includes/Privacy/DataExporter.php:299 msgid "Attachment ID" msgstr "ID del adjunto" -#: includes/Admin/AuditPage.php:556 +#: includes/Admin/AuditPage.php:557 msgid "File Size (bytes)" msgstr "Tamaño del archivo (bytes)" -#: includes/Admin/AuditPage.php:557 +#: includes/Admin/AuditPage.php:558 msgid "Internal Scan Date" msgstr "Fecha del escaneo interno" -#: includes/Admin/AuditPage.php:559 +#: includes/Admin/AuditPage.php:560 msgid "Has Alert" msgstr "Tiene alerta" -#: includes/Admin/AuditPage.php:560 includes/Admin/MediaListTable.php:57 +#: includes/Admin/AuditPage.php:561 includes/Admin/MediaListTable.php:58 msgid "Usage Count" msgstr "Número de usos" -#: includes/Admin/AuditPage.php:561 +#: includes/Admin/AuditPage.php:562 msgid "Used In" msgstr "Usado en" -#: includes/Admin/AuditPage.php:562 +#: includes/Admin/AuditPage.php:563 msgid "Google Vision — Matches" msgstr "Google Vision — Coincidencias" -#: includes/Admin/AuditPage.php:563 +#: includes/Admin/AuditPage.php:564 msgid "TinEye — Matches" msgstr "TinEye — Coincidencias" -#: includes/Admin/AuditPage.php:564 +#: includes/Admin/AuditPage.php:565 msgid "PicDefense — Matches" msgstr "PicDefense — Coincidencias" -#: includes/Admin/AuditPage.php:748 +#: includes/Admin/AuditPage.php:570 +msgid "Dismissed Notes" +msgstr "Notas de descarte" + +#: includes/Admin/AuditPage.php:776 includes/Admin/AuditPage.php:788 msgid "Yes" msgstr "Sí" -#: includes/Admin/AuditPage.php:748 +#: includes/Admin/AuditPage.php:776 includes/Admin/AuditPage.php:788 msgid "No" msgstr "No" -#: includes/Admin/MediaListTable.php:54 -msgid "Thumbnail" -msgstr "Miniatura" - -#: includes/Admin/MediaListTable.php:58 +#: includes/Admin/MediaListTable.php:59 msgid "Usages" msgstr "Usos" -#: includes/Admin/MediaListTable.php:60 +#: includes/Admin/MediaListTable.php:61 msgid "Last Scanned" msgstr "Último escaneo" -#: includes/Admin/MediaListTable.php:84 +#: includes/Admin/MediaListTable.php:85 msgid "Run External Scan" msgstr "Ejecutar escaneo externo" -#: includes/Admin/MediaListTable.php:85 +#: includes/Admin/MediaListTable.php:86 msgid "Purge External Data" msgstr "Purgar datos externos" -#: includes/Admin/MediaListTable.php:86 +#: includes/Admin/MediaListTable.php:87 msgid "Export CSV" msgstr "Exportar CSV" -#: includes/Admin/MediaListTable.php:87 +#: includes/Admin/MediaListTable.php:88 msgid "Export CSV (Alerts only)" msgstr "Exportar CSV (solo alertas)" -#: includes/Admin/MediaListTable.php:171 -msgid "Edit" -msgstr "Editar" - -#: includes/Admin/MediaListTable.php:176 +#: includes/Admin/MediaListTable.php:177 msgid "View Details" msgstr "Ver detalles" -#: includes/Admin/MediaListTable.php:181 -msgid "Full Report" -msgstr "Informe completo" - -#: includes/Admin/MediaListTable.php:247 +#: includes/Admin/MediaListTable.php:248 msgid "(no title)" msgstr "(sin título)" #. translators: %d: number of additional usages -#: includes/Admin/MediaListTable.php:262 +#: includes/Admin/MediaListTable.php:263 #, php-format msgid "+ %d more…" msgstr "+ %d más…" -#: includes/Admin/MediaListTable.php:320 includes/Admin/MediaListTable.php:359 +#: includes/Admin/MediaListTable.php:307 includes/Admin/MediaListTable.php:337 +#: includes/Admin/MediaListTable.php:376 msgid "View Results" msgstr "Ver resultados" -#: includes/Admin/MediaListTable.php:327 includes/Admin/ToolsPage.php:589 +#: includes/Admin/MediaListTable.php:344 includes/Admin/ToolsPage.php:589 #: includes/Admin/ToolsPage.php:590 includes/Admin/ToolsPage.php:591 msgid "Pending" msgstr "Pendiente" -#: includes/Admin/MediaListTable.php:328 +#: includes/Admin/MediaListTable.php:345 msgid "Queued" msgstr "En cola" -#: includes/Admin/MediaListTable.php:330 +#: includes/Admin/MediaListTable.php:347 msgid "Matches Found" msgstr "Coincidencias encontradas" -#: includes/Admin/MediaListTable.php:331 +#: includes/Admin/MediaListTable.php:348 msgid "Error" msgstr "Error" #. translators: %s: human-readable time difference, e.g. "3 days" -#: includes/Admin/MediaListTable.php:401 +#: includes/Admin/MediaListTable.php:418 #, php-format msgid "%s ago" msgstr "hace %s" -#: includes/Admin/MediaListTable.php:431 +#: includes/Admin/MediaListTable.php:448 msgid "All statuses" msgstr "Todos los estados" -#: includes/Admin/MediaListTable.php:450 +#: includes/Admin/MediaListTable.php:467 msgid "All post types" msgstr "Todos los tipos de entrada" -#: includes/Admin/MediaListTable.php:465 +#: includes/Admin/MediaListTable.php:482 msgid "Unused only" msgstr "Solo sin usar" -#: includes/Admin/MediaListTable.php:468 +#: includes/Admin/MediaListTable.php:485 msgid "Filter" msgstr "Filtrar" -#: includes/Admin/MediaListTable.php:478 +#: includes/Admin/MediaListTable.php:495 msgid "" "No indexed attachments found. Run wp mra scan-internal to populate the index." msgstr "" @@ -799,10 +901,6 @@ msgstr "Datos de escaneo externo eliminados." msgid "All data reset. Fresh indexing scheduled." msgstr "Todos los datos restablecidos. Re-indexación programada." -#: includes/Admin/ToolsPage.php:376 -msgid "Operation completed." -msgstr "Operación completada." - #: includes/Admin/ToolsPage.php:420 msgid "Database schema" msgstr "Esquema de base de datos" @@ -821,7 +919,7 @@ msgstr "Instalado: %1$s — Requerido: %2$s" #: includes/Admin/ToolsPage.php:441 msgid "Deactivate and reactivate the plugin to apply pending migrations." -msgstr "" +msgstr "Desactiva y reactiva el plugin para aplicar las migraciones pendientes." "Desactiva y reactiva el plugin para aplicar las migraciones pendientes." #: includes/Admin/ToolsPage.php:448 @@ -1208,15 +1306,15 @@ msgstr "Ejecutando escaneos externos…" msgid "External scan complete. %d attachments processed." msgstr "Escaneo externo completo. %d adjuntos procesados." -#: includes/Core/Plugin.php:132 includes/Core/Plugin.php:133 +#: includes/Core/Plugin.php:154 includes/Core/Plugin.php:155 msgid "Tools" msgstr "Herramientas" -#: includes/Core/Plugin.php:145 includes/Core/Plugin.php:146 +#: includes/Core/Plugin.php:167 includes/Core/Plugin.php:168 msgid "Settings" msgstr "Ajustes" -#: includes/Core/Plugin.php:154 +#: includes/Core/Plugin.php:176 msgid "Attachment Report" msgstr "Informe del archivo adjunto" diff --git a/languages/robotstxt-mediaaudit.pot b/languages/robotstxt-mediaaudit.pot index 5dc9c8b..8e69208 100644 --- a/languages/robotstxt-mediaaudit.pot +++ b/languages/robotstxt-mediaaudit.pot @@ -2,14 +2,14 @@ # This file is distributed under the GPL-3.0-or-later. msgid "" msgstr "" -"Project-Id-Version: Media Audit (by ROBOTSTXT) 1.6.0\n" +"Project-Id-Version: Media Audit (by ROBOTSTXT) 1.7.0\n" "Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/robotstxt-mediaaudit\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"POT-Creation-Date: 2026-05-05T10:39:07+00:00\n" +"POT-Creation-Date: 2026-05-11T07:13:58+00:00\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "X-Generator: WP-CLI 2.12.0\n" "X-Domain: robotstxt-mediaaudit\n" @@ -39,16 +39,153 @@ msgstr "" msgid "https://www.robotstxt.es/" msgstr "" +#: includes/Admin/AlertsPage.php:210 +msgid "You do not have permission to perform this action." +msgstr "" + +#: includes/Admin/AlertsPage.php:263 #: includes/Admin/AttachmentDetailPage.php:40 -#: includes/Admin/AuditPage.php:135 +#: includes/Admin/AuditPage.php:136 #: includes/Admin/Settings.php:898 #: includes/Admin/ToolsPage.php:253 msgid "You do not have permission to access this page." msgstr "" +#: includes/Admin/AlertsPage.php:280 +#: includes/Core/Plugin.php:141 +#: includes/Core/Plugin.php:142 +msgid "Alerts" +msgstr "" + +#. translators: %d: count of active alerts +#: includes/Admin/AlertsPage.php:292 +#, php-format +msgid "Active Alerts (%d)" +msgstr "" + +#. translators: %d: count of dismissed alerts +#: includes/Admin/AlertsPage.php:304 +#, php-format +msgid "Dismissed Alerts (%d)" +msgstr "" + +#: includes/Admin/AlertsPage.php:376 +msgid "Alert dismissed successfully." +msgstr "" + +#: includes/Admin/AlertsPage.php:377 +msgid "Alert reactivated successfully." +msgstr "" + +#: includes/Admin/AlertsPage.php:378 +msgid "An error occurred. Please try again." +msgstr "" + +#: includes/Admin/AlertsPage.php:382 +#: includes/Admin/ToolsPage.php:376 +msgid "Operation completed." +msgstr "" + +#: includes/Admin/AlertsPage.php:402 +msgid "No active alerts found." +msgstr "" + +#: includes/Admin/AlertsPage.php:409 +#: includes/Admin/AlertsPage.php:541 +#: includes/Admin/MediaListTable.php:55 +msgid "Thumbnail" +msgstr "" + +#: includes/Admin/AlertsPage.php:410 +#: includes/Admin/AlertsPage.php:542 +#: includes/Admin/AttachmentDetailPage.php:87 +#: includes/Admin/AuditPage.php:554 +#: includes/Admin/MediaListTable.php:56 +#: includes/Privacy/DataExporter.php:261 +msgid "Filename" +msgstr "" + +#: includes/Admin/AlertsPage.php:411 +#: includes/Admin/AlertsPage.php:543 +#: includes/Admin/AttachmentDetailPage.php:372 +#: includes/Admin/AuditPage.php:566 +msgid "Alert Domains" +msgstr "" + +#: includes/Admin/AlertsPage.php:412 +msgid "Usage" +msgstr "" + +#: includes/Admin/AlertsPage.php:413 +#: includes/Admin/AlertsPage.php:547 +msgid "Actions" +msgstr "" + +#: includes/Admin/AlertsPage.php:447 +#: includes/Admin/AlertsPage.php:587 +#: includes/Admin/MediaListTable.php:172 +msgid "Edit" +msgstr "" + +#: includes/Admin/AlertsPage.php:449 +#: includes/Admin/AlertsPage.php:589 +#: includes/Admin/MediaListTable.php:182 +msgid "Full Report" +msgstr "" + +#: includes/Admin/AlertsPage.php:459 +#: includes/Admin/AuditPage.php:382 +#: includes/Admin/AuditPage.php:489 +#: includes/Admin/MediaListTable.php:315 +#: includes/Admin/MediaListTable.php:452 +msgid "Alert" +msgstr "" + +#: includes/Admin/AlertsPage.php:473 +msgid "Dismiss" +msgstr "" + +#: includes/Admin/AlertsPage.php:485 +msgid "Notes (optional):" +msgstr "" + +#: includes/Admin/AlertsPage.php:495 +msgid "Confirm Dismiss" +msgstr "" + +#: includes/Admin/AlertsPage.php:496 +msgid "Cancel" +msgstr "" + +#: includes/Admin/AlertsPage.php:534 +msgid "No dismissed alerts." +msgstr "" + +#: includes/Admin/AlertsPage.php:544 +msgid "Notes" +msgstr "" + +#: includes/Admin/AlertsPage.php:545 +msgid "Dismissed By" +msgstr "" + +#: includes/Admin/AlertsPage.php:546 +msgid "Dismissed At" +msgstr "" + +#: includes/Admin/AlertsPage.php:599 +#: includes/Admin/AuditPage.php:569 +#: includes/Admin/MediaListTable.php:302 +msgid "Dismissed" +msgstr "" + +#: includes/Admin/AlertsPage.php:616 +msgid "Reactivate" +msgstr "" + #: includes/Admin/AttachmentDetailPage.php:51 #: includes/Admin/AttachmentDetailPage.php:56 -#: includes/Admin/AuditPage.php:180 +#: includes/Admin/AuditPage.php:181 msgid "Invalid attachment ID." msgstr "" @@ -61,25 +198,18 @@ msgid "File Information" msgstr "" #: includes/Admin/AttachmentDetailPage.php:81 -#: includes/Admin/MediaListTable.php:56 +#: includes/Admin/MediaListTable.php:57 msgid "ID" msgstr "" -#: includes/Admin/AttachmentDetailPage.php:87 -#: includes/Admin/AuditPage.php:553 -#: includes/Admin/MediaListTable.php:55 -#: includes/Privacy/DataExporter.php:261 -msgid "Filename" -msgstr "" - #: includes/Admin/AttachmentDetailPage.php:94 -#: includes/Admin/AuditPage.php:554 +#: includes/Admin/AuditPage.php:555 #: includes/Privacy/DataExporter.php:265 msgid "File URL" msgstr "" #: includes/Admin/AttachmentDetailPage.php:102 -#: includes/Admin/AuditPage.php:555 +#: includes/Admin/AuditPage.php:556 msgid "MIME Type" msgstr "" @@ -92,28 +222,28 @@ msgid "Internal scan date" msgstr "" #: includes/Admin/AttachmentDetailPage.php:124 -#: includes/Admin/AuditPage.php:558 -#: includes/Admin/MediaListTable.php:59 +#: includes/Admin/AuditPage.php:559 +#: includes/Admin/MediaListTable.php:60 msgid "External Status" msgstr "" #: includes/Admin/AttachmentDetailPage.php:139 -#: includes/Admin/AuditPage.php:244 +#: includes/Admin/AuditPage.php:245 msgid "Internal Usage" msgstr "" #: includes/Admin/AttachmentDetailPage.php:144 -#: includes/Admin/AuditPage.php:188 +#: includes/Admin/AuditPage.php:189 msgid "Featured Image" msgstr "" #: includes/Admin/AttachmentDetailPage.php:145 -#: includes/Admin/AuditPage.php:189 +#: includes/Admin/AuditPage.php:190 msgid "Post Content" msgstr "" #: includes/Admin/AttachmentDetailPage.php:146 -#: includes/Admin/AuditPage.php:190 +#: includes/Admin/AuditPage.php:191 msgid "Custom Field" msgstr "" @@ -122,28 +252,28 @@ msgid "Not used in any post." msgstr "" #: includes/Admin/AttachmentDetailPage.php:154 -#: includes/Admin/AuditPage.php:198 +#: includes/Admin/AuditPage.php:199 msgid "Post" msgstr "" #: includes/Admin/AttachmentDetailPage.php:155 -#: includes/Admin/AuditPage.php:199 +#: includes/Admin/AuditPage.php:200 msgid "Type" msgstr "" #: includes/Admin/AttachmentDetailPage.php:156 -#: includes/Admin/AuditPage.php:200 +#: includes/Admin/AuditPage.php:201 msgid "Context" msgstr "" #: includes/Admin/AttachmentDetailPage.php:157 -#: includes/Admin/AuditPage.php:201 -#: includes/Admin/AuditPage.php:373 +#: includes/Admin/AuditPage.php:202 +#: includes/Admin/AuditPage.php:374 msgid "Status" msgstr "" #: includes/Admin/AttachmentDetailPage.php:198 -#: includes/Admin/AuditPage.php:293 +#: includes/Admin/AuditPage.php:294 msgid "External Scan Results" msgstr "" @@ -153,7 +283,7 @@ msgstr "" #. translators: %d: number of external matches #: includes/Admin/AttachmentDetailPage.php:313 -#: includes/Admin/AuditPage.php:323 +#: includes/Admin/AuditPage.php:324 #, php-format msgid "%d match" msgid_plural "%d matches" @@ -166,11 +296,6 @@ msgstr[1] "" msgid "scanned %s" msgstr "" -#: includes/Admin/AttachmentDetailPage.php:372 -#: includes/Admin/AuditPage.php:565 -msgid "Alert Domains" -msgstr "" - #: includes/Admin/AttachmentDetailPage.php:373 msgid "These domains are associated with copyright-protected content and may indicate a copyright risk." msgstr "" @@ -178,24 +303,24 @@ msgstr "" #: includes/Admin/AttachmentDetailPage.php:376 #: includes/Admin/AttachmentDetailPage.php:392 #: includes/Admin/AttachmentDetailPage.php:408 -#: includes/Admin/AuditPage.php:371 +#: includes/Admin/AuditPage.php:372 msgid "Domain" msgstr "" #: includes/Admin/AttachmentDetailPage.php:377 #: includes/Admin/AttachmentDetailPage.php:393 #: includes/Admin/AttachmentDetailPage.php:409 -#: includes/Admin/AuditPage.php:372 +#: includes/Admin/AuditPage.php:373 msgid "Occurrences" msgstr "" #: includes/Admin/AttachmentDetailPage.php:389 -#: includes/Admin/AuditPage.php:566 +#: includes/Admin/AuditPage.php:567 msgid "Other Domains" msgstr "" #: includes/Admin/AttachmentDetailPage.php:405 -#: includes/Admin/AuditPage.php:567 +#: includes/Admin/AuditPage.php:568 msgid "Ignored Domains" msgstr "" @@ -278,264 +403,252 @@ msgstr "" msgid "Labels" msgstr "" -#: includes/Admin/AuditPage.php:116 +#: includes/Admin/AuditPage.php:117 msgid "Loading…" msgstr "" -#: includes/Admin/AuditPage.php:117 +#: includes/Admin/AuditPage.php:118 msgid "Error loading details." msgstr "" -#: includes/Admin/AuditPage.php:118 -#: includes/Admin/AuditPage.php:194 +#: includes/Admin/AuditPage.php:119 +#: includes/Admin/AuditPage.php:195 msgid "This image is not used in any post." msgstr "" -#: includes/Admin/AuditPage.php:119 -#: includes/Admin/AuditPage.php:156 +#: includes/Admin/AuditPage.php:120 +#: includes/Admin/AuditPage.php:157 msgid "Usage Details" msgstr "" -#: includes/Admin/AuditPage.php:142 -#: includes/Core/Plugin.php:109 -#: includes/Core/Plugin.php:110 -#: includes/Core/Plugin.php:123 -#: includes/Core/Plugin.php:124 +#: includes/Admin/AuditPage.php:143 +#: includes/Core/Plugin.php:118 +#: includes/Core/Plugin.php:119 +#: includes/Core/Plugin.php:132 +#: includes/Core/Plugin.php:133 #: includes/Privacy/DataEraser.php:33 #: includes/Privacy/DataExporter.php:45 msgid "Media Audit" msgstr "" -#: includes/Admin/AuditPage.php:148 +#: includes/Admin/AuditPage.php:149 msgid "Search" msgstr "" -#: includes/Admin/AuditPage.php:157 +#: includes/Admin/AuditPage.php:158 msgid "Close" msgstr "" -#: includes/Admin/AuditPage.php:175 -#: includes/Admin/AuditPage.php:529 -#: includes/Admin/AuditPage.php:776 -#: includes/Admin/AuditPage.php:808 +#: includes/Admin/AuditPage.php:176 +#: includes/Admin/AuditPage.php:530 +#: includes/Admin/AuditPage.php:806 +#: includes/Admin/AuditPage.php:838 #: includes/Admin/ToolsPage.php:105 #: includes/Admin/ToolsPage.php:165 msgid "Insufficient permissions." msgstr "" -#: includes/Admin/AuditPage.php:328 +#: includes/Admin/AuditPage.php:329 msgid "No matches" msgstr "" -#: includes/Admin/AuditPage.php:381 -#: includes/Admin/AuditPage.php:488 -#: includes/Admin/MediaListTable.php:298 -#: includes/Admin/MediaListTable.php:435 -msgid "Alert" -msgstr "" - -#: includes/Admin/AuditPage.php:403 +#: includes/Admin/AuditPage.php:404 msgid "Consensus Domains" msgstr "" #. translators: %d: number of providers that agree -#: includes/Admin/AuditPage.php:409 +#: includes/Admin/AuditPage.php:410 #, php-format msgid "%d provider" msgid_plural "%d providers" msgstr[0] "" msgstr[1] "" -#: includes/Admin/AuditPage.php:419 +#: includes/Admin/AuditPage.php:420 msgid "View full report →" msgstr "" -#: includes/Admin/AuditPage.php:445 +#: includes/Admin/AuditPage.php:446 msgid "In Index" msgstr "" #. translators: %d: count of images not yet indexed -#: includes/Admin/AuditPage.php:449 +#: includes/Admin/AuditPage.php:450 #, php-format msgid "%d not yet indexed" msgstr "" -#: includes/Admin/AuditPage.php:452 +#: includes/Admin/AuditPage.php:453 msgid "All indexed" msgstr "" -#: includes/Admin/AuditPage.php:457 -#: includes/Admin/MediaListTable.php:329 +#: includes/Admin/AuditPage.php:458 +#: includes/Admin/MediaListTable.php:346 msgid "Scanned" msgstr "" #. translators: %d: percentage -#: includes/Admin/AuditPage.php:460 +#: includes/Admin/AuditPage.php:461 #, php-format msgid "%d%% of index" msgstr "" -#: includes/Admin/AuditPage.php:467 +#: includes/Admin/AuditPage.php:468 msgid "Used" msgstr "" -#: includes/Admin/AuditPage.php:473 -#: includes/Admin/MediaListTable.php:211 +#: includes/Admin/AuditPage.php:474 +#: includes/Admin/MediaListTable.php:212 msgid "Unused" msgstr "" -#: includes/Admin/AuditPage.php:480 +#: includes/Admin/AuditPage.php:481 msgid "External Matches" msgstr "" -#: includes/Admin/AuditPage.php:543 +#: includes/Admin/AuditPage.php:544 msgid "Could not open output stream." msgstr "" -#: includes/Admin/AuditPage.php:552 +#: includes/Admin/AuditPage.php:553 #: includes/Privacy/DataExporter.php:257 #: includes/Privacy/DataExporter.php:299 msgid "Attachment ID" msgstr "" -#: includes/Admin/AuditPage.php:556 +#: includes/Admin/AuditPage.php:557 msgid "File Size (bytes)" msgstr "" -#: includes/Admin/AuditPage.php:557 +#: includes/Admin/AuditPage.php:558 msgid "Internal Scan Date" msgstr "" -#: includes/Admin/AuditPage.php:559 +#: includes/Admin/AuditPage.php:560 msgid "Has Alert" msgstr "" -#: includes/Admin/AuditPage.php:560 -#: includes/Admin/MediaListTable.php:57 +#: includes/Admin/AuditPage.php:561 +#: includes/Admin/MediaListTable.php:58 msgid "Usage Count" msgstr "" -#: includes/Admin/AuditPage.php:561 +#: includes/Admin/AuditPage.php:562 msgid "Used In" msgstr "" -#: includes/Admin/AuditPage.php:562 +#: includes/Admin/AuditPage.php:563 msgid "Google Vision — Matches" msgstr "" -#: includes/Admin/AuditPage.php:563 +#: includes/Admin/AuditPage.php:564 msgid "TinEye — Matches" msgstr "" -#: includes/Admin/AuditPage.php:564 +#: includes/Admin/AuditPage.php:565 msgid "PicDefense — Matches" msgstr "" -#: includes/Admin/AuditPage.php:748 +#: includes/Admin/AuditPage.php:570 +msgid "Dismissed Notes" +msgstr "" + +#: includes/Admin/AuditPage.php:776 +#: includes/Admin/AuditPage.php:788 msgid "Yes" msgstr "" -#: includes/Admin/AuditPage.php:748 +#: includes/Admin/AuditPage.php:776 +#: includes/Admin/AuditPage.php:788 msgid "No" msgstr "" -#: includes/Admin/MediaListTable.php:54 -msgid "Thumbnail" -msgstr "" - -#: includes/Admin/MediaListTable.php:58 +#: includes/Admin/MediaListTable.php:59 msgid "Usages" msgstr "" -#: includes/Admin/MediaListTable.php:60 +#: includes/Admin/MediaListTable.php:61 msgid "Last Scanned" msgstr "" -#: includes/Admin/MediaListTable.php:84 +#: includes/Admin/MediaListTable.php:85 msgid "Run External Scan" msgstr "" -#: includes/Admin/MediaListTable.php:85 +#: includes/Admin/MediaListTable.php:86 msgid "Purge External Data" msgstr "" -#: includes/Admin/MediaListTable.php:86 +#: includes/Admin/MediaListTable.php:87 msgid "Export CSV" msgstr "" -#: includes/Admin/MediaListTable.php:87 +#: includes/Admin/MediaListTable.php:88 msgid "Export CSV (Alerts only)" msgstr "" -#: includes/Admin/MediaListTable.php:171 -msgid "Edit" -msgstr "" - -#: includes/Admin/MediaListTable.php:176 +#: includes/Admin/MediaListTable.php:177 msgid "View Details" msgstr "" -#: includes/Admin/MediaListTable.php:181 -msgid "Full Report" -msgstr "" - -#: includes/Admin/MediaListTable.php:247 +#: includes/Admin/MediaListTable.php:248 msgid "(no title)" msgstr "" #. translators: %d: number of additional usages -#: includes/Admin/MediaListTable.php:262 +#: includes/Admin/MediaListTable.php:263 #, php-format msgid "+ %d more…" msgstr "" -#: includes/Admin/MediaListTable.php:320 -#: includes/Admin/MediaListTable.php:359 +#: includes/Admin/MediaListTable.php:307 +#: includes/Admin/MediaListTable.php:337 +#: includes/Admin/MediaListTable.php:376 msgid "View Results" msgstr "" -#: includes/Admin/MediaListTable.php:327 +#: includes/Admin/MediaListTable.php:344 #: includes/Admin/ToolsPage.php:589 #: includes/Admin/ToolsPage.php:590 #: includes/Admin/ToolsPage.php:591 msgid "Pending" msgstr "" -#: includes/Admin/MediaListTable.php:328 +#: includes/Admin/MediaListTable.php:345 msgid "Queued" msgstr "" -#: includes/Admin/MediaListTable.php:330 +#: includes/Admin/MediaListTable.php:347 msgid "Matches Found" msgstr "" -#: includes/Admin/MediaListTable.php:331 +#: includes/Admin/MediaListTable.php:348 msgid "Error" msgstr "" #. translators: %s: human-readable time difference, e.g. "3 days" -#: includes/Admin/MediaListTable.php:401 +#: includes/Admin/MediaListTable.php:418 #, php-format msgid "%s ago" msgstr "" -#: includes/Admin/MediaListTable.php:431 +#: includes/Admin/MediaListTable.php:448 msgid "All statuses" msgstr "" -#: includes/Admin/MediaListTable.php:450 +#: includes/Admin/MediaListTable.php:467 msgid "All post types" msgstr "" -#: includes/Admin/MediaListTable.php:465 +#: includes/Admin/MediaListTable.php:482 msgid "Unused only" msgstr "" -#: includes/Admin/MediaListTable.php:468 +#: includes/Admin/MediaListTable.php:485 msgid "Filter" msgstr "" -#: includes/Admin/MediaListTable.php:478 +#: includes/Admin/MediaListTable.php:495 msgid "No indexed attachments found. Run wp mra scan-internal to populate the index." msgstr "" @@ -774,10 +887,6 @@ msgstr "" msgid "All data reset. Fresh indexing scheduled." msgstr "" -#: includes/Admin/ToolsPage.php:376 -msgid "Operation completed." -msgstr "" - #: includes/Admin/ToolsPage.php:420 msgid "Database schema" msgstr "" @@ -1098,17 +1207,17 @@ msgstr "" msgid "External scan complete. %d attachments processed." msgstr "" -#: includes/Core/Plugin.php:132 -#: includes/Core/Plugin.php:133 +#: includes/Core/Plugin.php:154 +#: includes/Core/Plugin.php:155 msgid "Tools" msgstr "" -#: includes/Core/Plugin.php:145 -#: includes/Core/Plugin.php:146 +#: includes/Core/Plugin.php:167 +#: includes/Core/Plugin.php:168 msgid "Settings" msgstr "" -#: includes/Core/Plugin.php:154 +#: includes/Core/Plugin.php:176 msgid "Attachment Report" msgstr "" diff --git a/readme.txt b/readme.txt index 1127263..802c8ef 100644 --- a/readme.txt +++ b/readme.txt @@ -5,7 +5,7 @@ Requires at least: 6.8 Tested up to: 7.0 Requires PHP: 8.2 Requires Plugins: action-scheduler -Stable tag: 1.6.0 +Stable tag: 1.7.0 License: GPL-3.0-or-later License URI: https://www.gnu.org/licenses/gpl-3.0.txt @@ -21,6 +21,7 @@ Media Audit helps site owners and editors understand where their media files are * **External reverse image search** — submits images to Google Cloud Vision (Web Detection) and TinEye Commercial API to find matching pages across the web. * **Consensus detection** — surfaces domains independently confirmed by multiple providers, providing stronger copyright-risk signals. * **Audit list** — filterable admin page showing each attachment's external scan status, alert classification, and top matching domains. +* **Alert management** — dedicated Alerts page to review active copyright-risk alerts and dismiss false positives with optional notes. Dismissed alerts are tracked separately and can be reactivated at any time. * **WP-CLI support** — run or schedule scans from the command line. * **GDPR compliance** — full WordPress privacy API integration for personal data export and erasure on request. * **Async processing** — all scanning runs via Action Scheduler to avoid blocking web requests. diff --git a/robotstxt-mediaaudit.php b/robotstxt-mediaaudit.php index fb04ccf..6652d90 100644 --- a/robotstxt-mediaaudit.php +++ b/robotstxt-mediaaudit.php @@ -3,7 +3,7 @@ * Plugin Name: Media Audit (by ROBOTSTXT) * Plugin URI: https://git.robotstxt.es/ROBOTSTXT/robotstxt-mediaaudit * Description: Internal media library usage auditing and external reverse image search to detect potential copyright issues. - * Version: 1.6.0 + * Version: 1.7.0 * Requires at least: 6.8 * Tested up to: 7.0 * Requires PHP: 8.2 @@ -23,8 +23,8 @@ if ( ! defined( 'ABSPATH' ) ) { exit; } -define( 'ROBOTSTXT_MEDIAAUDIT_VERSION', '1.6.0' ); -define( 'ROBOTSTXT_MEDIAAUDIT_DB_VERSION', '1.1.0' ); +define( 'ROBOTSTXT_MEDIAAUDIT_VERSION', '1.7.0' ); +define( 'ROBOTSTXT_MEDIAAUDIT_DB_VERSION', '1.2.0' ); define( 'ROBOTSTXT_MEDIAAUDIT_PLUGIN_FILE', __FILE__ ); define( 'ROBOTSTXT_MEDIAAUDIT_PLUGIN_DIR', plugin_dir_path( __FILE__ ) ); define( 'ROBOTSTXT_MEDIAAUDIT_PLUGIN_URL', plugin_dir_url( __FILE__ ) ); diff --git a/uninstall.php b/uninstall.php index 19ca27f..3c33384 100644 --- a/uninstall.php +++ b/uninstall.php @@ -23,6 +23,7 @@ if ( ! $delete ) { global $wpdb; // Drop tables in reverse dependency order. +$wpdb->query( "DROP TABLE IF EXISTS `{$wpdb->prefix}mra_dismissed_alerts`" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery $wpdb->query( "DROP TABLE IF EXISTS `{$wpdb->prefix}mra_provider_status`" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery $wpdb->query( "DROP TABLE IF EXISTS `{$wpdb->prefix}mra_external_results`" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery $wpdb->query( "DROP TABLE IF EXISTS `{$wpdb->prefix}mra_media_usage`" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery diff --git a/vendor/composer/autoload_classmap.php b/vendor/composer/autoload_classmap.php index 3a43b2c..a1c1dfa 100644 --- a/vendor/composer/autoload_classmap.php +++ b/vendor/composer/autoload_classmap.php @@ -7,6 +7,7 @@ $baseDir = dirname($vendorDir); return array( 'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php', + 'MediaRightsAudit\\Admin\\AlertsPage' => $baseDir . '/includes/Admin/AlertsPage.php', 'MediaRightsAudit\\Admin\\AttachmentDetailPage' => $baseDir . '/includes/Admin/AttachmentDetailPage.php', 'MediaRightsAudit\\Admin\\AuditPage' => $baseDir . '/includes/Admin/AuditPage.php', 'MediaRightsAudit\\Admin\\MediaListTable' => $baseDir . '/includes/Admin/MediaListTable.php', diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php index 99abdd8..b7dcac9 100644 --- a/vendor/composer/autoload_static.php +++ b/vendor/composer/autoload_static.php @@ -22,6 +22,7 @@ class ComposerStaticInit957728ab3efa005f456e5f9df13a19d2 public static $classMap = array ( 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', + 'MediaRightsAudit\\Admin\\AlertsPage' => __DIR__ . '/../..' . '/includes/Admin/AlertsPage.php', 'MediaRightsAudit\\Admin\\AttachmentDetailPage' => __DIR__ . '/../..' . '/includes/Admin/AttachmentDetailPage.php', 'MediaRightsAudit\\Admin\\AuditPage' => __DIR__ . '/../..' . '/includes/Admin/AuditPage.php', 'MediaRightsAudit\\Admin\\MediaListTable' => __DIR__ . '/../..' . '/includes/Admin/MediaListTable.php',