diff --git a/assets/css/media-audit-admin.css b/assets/css/media-audit-admin.css index 23de874..0c4fa33 100644 --- a/assets/css/media-audit-admin.css +++ b/assets/css/media-audit-admin.css @@ -218,6 +218,99 @@ font-weight: 600; } +/* ----------------------------------------------------------------------- + Tools & Status page + ----------------------------------------------------------------------- */ + +/* Status table */ +.mra-status-table td.mra-status-dot-col { + width: 24px; + padding-right: 4px; +} + +.mra-status-table th { + white-space: nowrap; + width: 180px; + font-weight: 600; +} + +.mra-status-table td.mra-status-hint { + color: #50575e; + font-size: 12px; +} + +/* Status indicator dots */ +.mra-dot { + display: inline-block; + width: 10px; + height: 10px; + border-radius: 50%; + vertical-align: middle; +} + +.mra-dot-ok { background: #00a32a; } +.mra-dot-warn { background: #dba617; } +.mra-dot-error { background: #d63638; } + +/* Job status labels in the scheduled-jobs row */ +.mra-job-label { + display: inline-block; + margin-right: 18px; +} + +/* WP-CLI hint below status table */ +.mra-cli-hint { + margin-top: 8px; +} + +.mra-cli-hint code { + margin-right: 8px; +} + +/* Operations section */ +.mra-ops-heading { + margin-top: 2em; +} + +.mra-destructive-heading { + margin-top: 1.5em; + color: #b32d2e; + font-size: 14px; + font-weight: 600; +} + +.mra-ops-grid { + display: flex; + flex-wrap: wrap; + gap: 16px; + margin: 12px 0 20px; +} + +.mra-op-card { + background: #fff; + border: 1px solid #dcdcde; + border-radius: 4px; + padding: 16px 20px; + width: 280px; + max-width: 100%; + display: flex; + flex-direction: column; +} + +.mra-op-card-title { + margin: 0 0 8px; + font-size: 13px; + font-weight: 600; + color: #1d2327; +} + +.mra-op-card-desc { + flex: 1; + margin-bottom: 12px; + font-size: 12px; + line-height: 1.5; +} + /* Pricing reference table (settings page) */ .mra-pricing-table { margin-top: 8px; @@ -238,6 +331,44 @@ border-bottom: 1px solid #dcdcde; } +/* ----------------------------------------------------------------------- + Direct scan runner + ----------------------------------------------------------------------- */ + +.mra-runner-progress { + margin: 8px 0 10px; +} + +.mra-runner-bar { + height: 8px; + background: #dcdcde; + border-radius: 4px; + overflow: hidden; + margin-bottom: 4px; +} + +.mra-runner-bar-fill { + height: 100%; + width: 0; + background: #2271b1; + border-radius: 4px; + transition: width 0.25s ease; +} + +.mra-runner-label { + display: block; + font-size: 12px; + color: #50575e; + min-height: 1.4em; +} + +.mra-runner-actions { + display: flex; + gap: 8px; + align-items: center; + margin-top: 4px; +} + /* Consensus block */ .mra-consensus { background: #f0f8e8; diff --git a/assets/js/media-audit-admin.js b/assets/js/media-audit-admin.js index 3669edc..094d587 100644 --- a/assets/js/media-audit-admin.js +++ b/assets/js/media-audit-admin.js @@ -1,7 +1,86 @@ -/* global mraAdmin */ +/* global mraAdmin, mraTools */ ( function ( $ ) { 'use strict'; + // ------------------------------------------------------------------------- + // Batch runner — Tools page + // ------------------------------------------------------------------------- + + function initRunner( type ) { + var running = false; + var stopReq = false; + + var $card = $( '#mra-runner-' + type ); + var $start = $card.find( '.mra-runner-start' ); + var $stop = $card.find( '.mra-runner-stop' ); + var $prog = $card.find( '.mra-runner-progress' ); + var $fill = $card.find( '.mra-runner-bar-fill' ); + var $label = $card.find( '.mra-runner-label' ); + + $start.on( 'click', function () { + if ( running ) { return; } + running = true; + stopReq = false; + $start.prop( 'disabled', true ); + $stop.prop( 'hidden', false ); + $prog.prop( 'hidden', false ); + $fill.css( 'width', '0%' ); + $label.text( '' ); + runBatch(); + } ); + + $stop.on( 'click', function () { + stopReq = true; + $( this ).prop( 'disabled', true ); + } ); + + function runBatch() { + if ( stopReq ) { + finish( mraTools.i18n.stopped ); + return; + } + $.ajax( { + url: mraTools.ajaxUrl, + type: 'POST', + data: { + action: 'mra_run_batch', + type: type, + nonce: mraTools.nonce + }, + success: function ( res ) { + if ( ! res.success ) { + finish( mraTools.i18n.error ); + return; + } + var d = res.data; + var pct = d.total > 0 ? Math.round( ( d.done / d.total ) * 100 ) : 100; + $fill.css( 'width', pct + '%' ); + $label.text( d.done + ' / ' + d.total ); + if ( d.remaining > 0 ) { + setTimeout( runBatch, 300 ); + } else { + $fill.css( 'width', '100%' ); + finish( mraTools.i18n.done ); + } + }, + error: function () { + finish( mraTools.i18n.error ); + } + } ); + } + + function finish( msg ) { + running = false; + $start.prop( 'disabled', false ); + $stop.prop( 'hidden', true ).prop( 'disabled', false ); + $label.text( msg ); + } + } + + // ------------------------------------------------------------------------- + // Modal — Audit page + // ------------------------------------------------------------------------- + var $overlay, $modalTitle, $modalBody; function openModal( attachmentId ) { @@ -37,6 +116,11 @@ } $( function () { + // Initialize batch runners if mraTools is available (Tools page only). + if ( typeof mraTools !== 'undefined' ) { + [ 'index', 'usage', 'external' ].forEach( initRunner ); + } + $overlay = $( '#mra-modal-overlay' ); $modalTitle = $overlay.find( '.mra-modal-title' ); $modalBody = $overlay.find( '.mra-modal-body' ); diff --git a/changelog.txt b/changelog.txt index 91fceb2..bb2f71c 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,50 @@ == Changelog == += 1.2.0 = + +_Release date: 2026-05-02_ + +**Added** + +* Browser-based AJAX scan runner in the Tools page: run index, usage, and external scan batches directly from the browser with a live progress bar. Useful when WP-Cron is disabled or Action Scheduler is not running. +* "Requeue scan errors" operation: re-queues only attachments that failed the last external scan run (status `error`), without touching already-scanned items. +* "Last Scanned" column in the audit list, showing a human-readable relative time with a precise date tooltip. Sortable with NULLs always listed last. +* Bulk action "Export External Results CSV": downloads a CSV with attachment metadata and all external scan results, including top matching domains per provider. + +**Compatibility** + +* WordPress: 6.8 - 7.0 +* PHP: 8.2 - 8.4 +* WP-CLI: 2.x + +**Tests** + +* PHP Coding Standards: WPCS 3.x / PHPCS 3.x +* PHPStan: level 9 +* PHPCompatibility: PHP 8.2 - 8.4 + += 1.1.0 = + +_Release date: 2026-05-02_ + +**Changed** + +* Plugin URI updated to `https://git.robotstxt.es/ROBOTSTXT/robotstxt-mediaaudit`. +* Author changed to `ROBOTSTXT` with URI `https://www.robotstxt.es/`. +* Contributors updated to `javiercasares, robotstxt`. + +**Compatibility** + +* WordPress: 6.8 - 7.0 +* PHP: 8.2 - 8.4 +* WP-CLI: 2.x + +**Tests** + +* PHP Coding Standards: WPCS 3.x / PHPCS 3.x +* PHPStan: level 9 +* PHPCompatibility: PHP 8.2 - 8.4 + = 1.0.0 = _Release date: 2026-05-01_ diff --git a/includes/Admin/AuditPage.php b/includes/Admin/AuditPage.php index c4f82a9..b4cb720 100644 --- a/includes/Admin/AuditPage.php +++ b/includes/Admin/AuditPage.php @@ -65,6 +65,8 @@ class AuditPage { if ( 'export_csv' === $action ) { $this->handle_export_csv(); + } elseif ( 'export_external_csv' === $action ) { + $this->handle_export_external_csv(); } elseif ( 'run_external_scan' === $action ) { $this->handle_run_external_scan(); } elseif ( 'purge_external_data' === $action ) { @@ -498,6 +500,142 @@ class AuditPage { exit; } + /** + * Outputs an external-results CSV for selected attachment IDs and terminates. + * + * One row per attachment × provider pair. Attachments with no results still + * appear with empty provider columns. + * + * @return void + */ + private function handle_export_external_csv(): void { + check_admin_referer( 'bulk-mra-attachments' ); + + if ( ! current_user_can( 'edit_posts' ) ) { + wp_die( esc_html__( 'Insufficient permissions.', 'robotstxt-mediaaudit' ) ); + } + + $ids = $this->collect_attachment_ids(); + $rows = $this->build_external_csv_rows( $ids ); + + $filename = 'media-audit-external-' . gmdate( 'Y-m-d' ) . '.csv'; + + header( 'Content-Type: text/csv; charset=utf-8' ); + header( 'Content-Disposition: attachment; filename="' . $filename . '"' ); + header( 'Pragma: no-cache' ); + + $out = fopen( 'php://output', 'w' ); + if ( false === $out ) { + wp_die( esc_html__( 'Could not open output stream.', 'robotstxt-mediaaudit' ) ); + } + + fwrite( $out, "\xEF\xBB\xBF" ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fwrite + + fputcsv( + $out, + array( + __( 'Attachment ID', 'robotstxt-mediaaudit' ), + __( 'Filename', 'robotstxt-mediaaudit' ), + __( 'File URL', 'robotstxt-mediaaudit' ), + __( 'External Status', 'robotstxt-mediaaudit' ), + __( 'Last Scanned', 'robotstxt-mediaaudit' ), + __( 'Provider', 'robotstxt-mediaaudit' ), + __( 'Match Count', 'robotstxt-mediaaudit' ), + __( 'Top Domains', 'robotstxt-mediaaudit' ), + ) + ); + + foreach ( $rows as $row ) { + fputcsv( $out, $row ); + } + + fclose( $out ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose + exit; + } + + /** + * Builds external-results CSV rows for the given attachment IDs. + * + * Each row represents one attachment × provider combination. Attachments with + * no provider results are included with empty provider columns. If $ids is + * empty, all indexed attachments are exported. + * + * @param array $ids Attachment IDs to export (empty = all). + * + * @return array> + */ + private function build_external_csv_rows( array $ids ): array { + global $wpdb; + + // phpcs:disable WordPress.DB.DirectDatabaseQuery,WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare + + if ( empty( $ids ) ) { + $rows = $wpdb->get_results( + "SELECT i.attachment_id, i.file_name, i.file_url, i.external_status, + i.external_scanned_at, er.provider, er.match_count, er.top_domains + FROM {$wpdb->prefix}mra_media_index i + LEFT JOIN {$wpdb->prefix}mra_external_results er ON er.attachment_id = i.attachment_id + ORDER BY i.attachment_id ASC, er.provider ASC", + ARRAY_A + ); + } else { + $placeholders = implode( ',', array_fill( 0, count( $ids ), '%d' ) ); + $rows = $wpdb->get_results( + $wpdb->prepare( + "SELECT i.attachment_id, i.file_name, i.file_url, i.external_status, + i.external_scanned_at, er.provider, er.match_count, er.top_domains + FROM {$wpdb->prefix}mra_media_index i + LEFT JOIN {$wpdb->prefix}mra_external_results er ON er.attachment_id = i.attachment_id + WHERE i.attachment_id IN ({$placeholders}) + ORDER BY i.attachment_id ASC, er.provider ASC", + ...$ids + ), + ARRAY_A + ); + } + + // phpcs:enable WordPress.DB.DirectDatabaseQuery,WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare + + if ( ! $rows ) { + return array(); + } + + $output = array(); + + foreach ( $rows as $r ) { + $aid_raw = $r['attachment_id'] ?? ''; + $scanned_raw = $r['external_scanned_at'] ?? ''; + $domains_raw = $r['top_domains'] ?? ''; + $mc_raw = $r['match_count'] ?? ''; + + $domains_decoded = is_string( $domains_raw ) && '' !== $domains_raw + ? json_decode( $domains_raw, true ) + : array(); + $domain_parts = array(); + if ( is_array( $domains_decoded ) ) { + foreach ( $domains_decoded as $d ) { + if ( is_string( $d ) ) { + $domain_parts[] = $d; + } + } + } + $domains_str = implode( '; ', $domain_parts ); + + $output[] = array( + is_numeric( $aid_raw ) ? (string) (int) $aid_raw : '', + is_string( $r['file_name'] ?? null ) ? (string) $r['file_name'] : '', + is_string( $r['file_url'] ?? null ) ? (string) $r['file_url'] : '', + is_string( $r['external_status'] ?? null ) ? (string) $r['external_status'] : '', + is_string( $scanned_raw ) ? $scanned_raw : '', + is_string( $r['provider'] ?? null ) ? (string) $r['provider'] : '', + is_numeric( $mc_raw ) ? (string) (int) $mc_raw : '', + $domains_str, + ); + } + + return $output; + } + /** * Queues selected attachments for external scanning and redirects. * diff --git a/includes/Admin/MediaListTable.php b/includes/Admin/MediaListTable.php index 2e32fc7..58b3a40 100644 --- a/includes/Admin/MediaListTable.php +++ b/includes/Admin/MediaListTable.php @@ -47,13 +47,14 @@ class MediaListTable extends \WP_List_Table { */ public function get_columns() { return array( - 'cb' => '', - 'thumbnail' => __( 'Thumbnail', 'robotstxt-mediaaudit' ), - 'filename' => __( 'Filename', 'robotstxt-mediaaudit' ), - 'attachment_id' => __( 'ID', 'robotstxt-mediaaudit' ), - 'usage_count' => __( 'Usage Count', 'robotstxt-mediaaudit' ), - 'usages' => __( 'Usages', 'robotstxt-mediaaudit' ), - 'external_status' => __( 'External Status', 'robotstxt-mediaaudit' ), + 'cb' => '', + 'thumbnail' => __( 'Thumbnail', 'robotstxt-mediaaudit' ), + 'filename' => __( 'Filename', 'robotstxt-mediaaudit' ), + 'attachment_id' => __( 'ID', 'robotstxt-mediaaudit' ), + 'usage_count' => __( 'Usage Count', 'robotstxt-mediaaudit' ), + 'usages' => __( 'Usages', 'robotstxt-mediaaudit' ), + 'external_status' => __( 'External Status', 'robotstxt-mediaaudit' ), + 'external_scanned_at' => __( 'Last Scanned', 'robotstxt-mediaaudit' ), ); } @@ -64,8 +65,9 @@ class MediaListTable extends \WP_List_Table { */ protected function get_sortable_columns() { return array( - 'attachment_id' => array( 'attachment_id', true ), - 'usage_count' => array( 'usage_count', false ), + 'attachment_id' => array( 'attachment_id', true ), + 'usage_count' => array( 'usage_count', false ), + 'external_scanned_at' => array( 'external_scanned_at', false ), ); } @@ -79,6 +81,7 @@ class MediaListTable extends \WP_List_Table { 'run_external_scan' => __( 'Run External Scan', 'robotstxt-mediaaudit' ), 'purge_external_data' => __( 'Purge External Data', 'robotstxt-mediaaudit' ), 'export_csv' => __( 'Export CSV', 'robotstxt-mediaaudit' ), + 'export_external_csv' => __( 'Export External Results CSV', 'robotstxt-mediaaudit' ), ); } @@ -309,6 +312,47 @@ class MediaListTable extends \WP_List_Table { return $out; } + /** + * Renders the external scan date column. + * + * Shows a human-readable time difference (e.g. "3 days ago") with the full + * date/time as a tooltip. Displays "—" when the attachment has never been scanned. + * + * @param array $item Row data. + * + * @return string + */ + protected function column_external_scanned_at( $item ) { + $raw = $item['external_scanned_at']; + + if ( ! is_string( $raw ) || '' === $raw ) { + return '—'; + } + + $ts = strtotime( $raw ); + if ( false === $ts ) { + return '—'; + } + + $date_fmt = get_option( 'date_format' ); + $time_fmt = get_option( 'time_format' ); + $format = ( is_string( $date_fmt ) ? $date_fmt : 'Y-m-d' ) + . ' ' + . ( is_string( $time_fmt ) ? $time_fmt : 'H:i' ); + $full_val = wp_date( $format, $ts ); + $full = is_string( $full_val ) ? $full_val : $raw; + + return sprintf( + '%s', + esc_attr( $full ), + sprintf( + /* translators: %s: human-readable time difference, e.g. "3 days" */ + esc_html__( '%s ago', 'robotstxt-mediaaudit' ), + esc_html( human_time_diff( $ts ) ) + ) + ); + } + /** * Renders filter controls above the table. * @@ -391,7 +435,7 @@ class MediaListTable extends \WP_List_Table { $offset = ( $paged - 1 ) * $per_page; // Sorting. - $allowed_orderby = array( 'attachment_id', 'usage_count' ); + $allowed_orderby = array( 'attachment_id', 'usage_count', 'external_scanned_at' ); // phpcs:disable WordPress.Security.NonceVerification.Recommended $orderby_raw = isset( $_REQUEST['orderby'] ) && is_string( $_REQUEST['orderby'] ) ? sanitize_key( wp_unslash( $_REQUEST['orderby'] ) ) : ''; $orderby = in_array( $orderby_raw, $allowed_orderby, true ) ? $orderby_raw : 'attachment_id'; @@ -438,6 +482,9 @@ class MediaListTable extends \WP_List_Table { if ( 'usage_count' === $orderby ) { $order_sql = "(SELECT COUNT(*) FROM {$wpdb->prefix}mra_media_usage mu WHERE mu.attachment_id = i.attachment_id) {$order}"; + } elseif ( 'external_scanned_at' === $orderby ) { + // NULLs always last, regardless of sort direction. + $order_sql = "i.external_scanned_at IS NULL ASC, i.external_scanned_at {$order}"; } else { $order_sql = "i.attachment_id {$order}"; } diff --git a/includes/Admin/ToolsPage.php b/includes/Admin/ToolsPage.php new file mode 100644 index 0000000..a3e00d8 --- /dev/null +++ b/includes/Admin/ToolsPage.php @@ -0,0 +1,776 @@ +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 Tools page. + * + * @param string $hook Current admin page hook suffix. + * + * @return void + */ + public function enqueue_assets( string $hook ): void { + if ( $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 ); + + wp_localize_script( + 'mra-admin', + 'mraTools', + array( + 'ajaxUrl' => admin_url( 'admin-ajax.php' ), + 'nonce' => wp_create_nonce( self::NONCE_BATCH ), + 'i18n' => array( + 'done' => __( 'Done.', 'robotstxt-mediaaudit' ), + 'stopped' => __( 'Stopped.', 'robotstxt-mediaaudit' ), + 'error' => __( 'An error occurred.', 'robotstxt-mediaaudit' ), + ), + ) + ); + } + + /** + * AJAX handler for the batch runner. Processes one batch and returns progress data. + * + * Expected POST fields: type (index|usage|external), nonce. + * Returns JSON: {done: int, total: int, remaining: int}. + * + * @return void + */ + public function ajax_run_batch(): void { + check_ajax_referer( self::NONCE_BATCH, 'nonce' ); + + if ( ! current_user_can( 'manage_options' ) ) { + wp_send_json_error( array( 'message' => __( 'Insufficient permissions.', 'robotstxt-mediaaudit' ) ), 403 ); + } + + $type = sanitize_key( wp_unslash( isset( $_POST['type'] ) && is_string( $_POST['type'] ) ? $_POST['type'] : '' ) ); + + $remaining = 0; + $done = 0; + $total = 0; + + switch ( $type ) { + case 'index': + AttachmentIndexer::index_batch(); + $remaining = AttachmentIndexer::get_pending_count(); + $done = AttachmentIndexer::get_indexed_count(); + $total = $done + $remaining; + break; + + case 'usage': + UsageScanner::scan_batch(); + $remaining = UsageScanner::get_pending_count(); + $total = AttachmentIndexer::get_indexed_count(); + $done = max( 0, $total - $remaining ); + break; + + case 'external': + ExternalScanner::queue_all_pending(); + ExternalScanner::scan_batch(); + $counts = ExternalScanner::get_status_counts(); + $remaining = $counts['queued'] ?? 0; + $done = ( $counts['scanned'] ?? 0 ) + ( $counts['matches'] ?? 0 ) + ( $counts['error'] ?? 0 ); + $total = $done + $remaining; + break; + + default: + wp_send_json_error( array( 'message' => __( 'Invalid batch type.', 'robotstxt-mediaaudit' ) ), 400 ); + } + + wp_send_json_success( + array( + 'done' => $done, + 'total' => $total, + 'remaining' => $remaining, + ) + ); + } + + /** + * Fires before page HTML is output; processes POST operations and redirects. + * + * @return void + */ + public function handle_load(): void { + if ( ! isset( $_POST['mra_tools_op'] ) ) { + return; + } + if ( ! current_user_can( 'manage_options' ) ) { + wp_die( esc_html__( 'Insufficient permissions.', 'robotstxt-mediaaudit' ) ); + } + check_admin_referer( self::NONCE_ACTION, self::NONCE_FIELD ); + + $op = sanitize_key( wp_unslash( is_string( $_POST['mra_tools_op'] ) ? $_POST['mra_tools_op'] : '' ) ); + $result = $this->dispatch_op( $op ); + + wp_safe_redirect( + add_query_arg( + array( + 'page' => 'robotstxt-mediaaudit-tools', + 'mra_notice' => $result['notice'], + 'mra_count' => $result['count'], + 'mra_op' => $op, + ), + admin_url( 'admin.php' ) + ) + ); + exit; + } + + /** + * Routes an operation key to the appropriate handler and returns a result array. + * + * @param string $op Operation key from the POST body. + * + * @return array{notice: string, count: int} + */ + private function dispatch_op( string $op ): array { + $count = 0; + + switch ( $op ) { + case 'schedule_internal': + AttachmentIndexer::schedule(); + UsageScanner::schedule(); + break; + + case 'schedule_external': + ExternalScanner::schedule(); + break; + + case 'requeue_errors': + $count = ExternalScanner::requeue_errors(); + break; + + case 'rescan_usage': + $count = UsageScanner::reset_all(); + UsageScanner::schedule(); + break; + + case 'reindex_all': + $count = AttachmentIndexer::reset_all(); + AttachmentIndexer::schedule(); + break; + + case 'purge_external': + $count = ExternalScanner::purge_all(); + break; + + case 'full_reset': + Database::truncate_all(); + AttachmentIndexer::schedule(); + break; + + default: + return array( + 'notice' => 'error', + 'count' => 0, + ); + } + + return array( + 'notice' => 'success', + 'count' => $count, + ); + } + + /** + * Renders the Tools & Status page. + * + * @return void + */ + public function render(): void { + if ( ! current_user_can( 'manage_options' ) ) { + wp_die( esc_html__( 'You do not have permission to access this page.', 'robotstxt-mediaaudit' ) ); + } + + $status = $this->get_system_status(); + $notice = $this->get_notice_data(); + ?> +
+

+ + render_notice( $notice ); ?> + +

+ render_status( $status ); ?> + +

+ render_operations(); ?> + +

+

+ +

+ render_runner(); ?> +
+ , jobs: array{index: bool, usage: bool, external: bool}} + */ + private function get_system_status(): array { + global $wpdb; + + $raw_ver = get_option( 'robotstxt_mediaaudit_db_version', '' ); + $stored_ver = is_string( $raw_ver ) ? $raw_ver : ''; + $as_ok = Scheduler::is_available(); + + // phpcs:ignore WordPress.DB.DirectDatabaseQuery + $total_images = (int) $wpdb->get_var( + "SELECT COUNT(ID) FROM {$wpdb->posts} + WHERE post_type = 'attachment' + AND post_mime_type LIKE 'image/%' + AND post_status != 'trash'" + ); + + $indexed = AttachmentIndexer::get_indexed_count(); + $pending_idx = AttachmentIndexer::get_pending_count(); + $pending_use = UsageScanner::get_pending_count(); + $ext_counts = ExternalScanner::get_status_counts(); + + $fallback_ver = '' !== $stored_ver ? $stored_ver : __( 'not installed', 'robotstxt-mediaaudit' ); + + return array( + 'db_version_ok' => ROBOTSTXT_MEDIAAUDIT_DB_VERSION === $stored_ver, + 'stored_version' => $fallback_ver, + 'as_available' => $as_ok, + 'cron_disabled' => defined( 'DISABLE_WP_CRON' ) && DISABLE_WP_CRON, + 'total_images' => $total_images, + 'indexed' => $indexed, + 'pending_index' => $pending_idx, + 'pending_usage' => $pending_use, + 'external' => $ext_counts, + 'jobs' => array( + 'index' => $as_ok && Scheduler::has_pending( AttachmentIndexer::AS_HOOK ), + 'usage' => $as_ok && Scheduler::has_pending( UsageScanner::AS_HOOK ), + 'external' => $as_ok && Scheduler::has_pending( ExternalScanner::AS_HOOK ), + ), + ); + } + + /** + * Reads PRG redirect query parameters and returns structured notice data. + * + * @return array{type: string, op: string, count: int}|null + */ + private function get_notice_data(): ?array { + // phpcs:disable WordPress.Security.NonceVerification.Recommended + if ( ! isset( $_GET['mra_notice'] ) ) { + return null; + } + $type = sanitize_key( wp_unslash( is_string( $_GET['mra_notice'] ) ? $_GET['mra_notice'] : '' ) ); + $op2 = sanitize_key( wp_unslash( isset( $_GET['mra_op'] ) && is_string( $_GET['mra_op'] ) ? $_GET['mra_op'] : '' ) ); + $count = isset( $_GET['mra_count'] ) && is_numeric( $_GET['mra_count'] ) ? (int) $_GET['mra_count'] : 0; + return array( + 'type' => $type, + 'op' => $op2, + 'count' => abs( $count ), + ); + // phpcs:enable WordPress.Security.NonceVerification.Recommended + } + + // ------------------------------------------------------------------------- + // Rendering helpers + // ------------------------------------------------------------------------- + + /** + * Renders a WP admin notice for a completed operation. + * + * @param array{type: string, op: string, count: int}|null $notice Notice data. + * + * @return void + */ + private function render_notice( ?array $notice ): void { + if ( null === $notice || 'success' !== $notice['type'] ) { + return; + } + + $messages = array( + 'schedule_internal' => __( 'Internal scan scheduled.', 'robotstxt-mediaaudit' ), + 'schedule_external' => __( 'External scan scheduled.', 'robotstxt-mediaaudit' ), + 'requeue_errors' => __( 'Failed scans re-queued. Background scan scheduled.', 'robotstxt-mediaaudit' ), + 'rescan_usage' => __( 'Usage data cleared. Background scan scheduled.', 'robotstxt-mediaaudit' ), + 'reindex_all' => __( 'Index cleared. Fresh indexing scheduled.', 'robotstxt-mediaaudit' ), + 'purge_external' => __( 'External scan data purged.', 'robotstxt-mediaaudit' ), + 'full_reset' => __( 'All data reset. Fresh indexing scheduled.', 'robotstxt-mediaaudit' ), + ); + + $msg = $messages[ $notice['op'] ] ?? __( 'Operation completed.', 'robotstxt-mediaaudit' ); + ?> +
+

+
+ , jobs: array{index: bool, usage: bool, external: bool}} $status Status data. + * + * @return void + */ + private function render_status( array $status ): void { + $db_ok = $status['db_version_ok']; + $as_ok = $status['as_available']; + $cron = $status['cron_disabled']; + $tot = $status['total_images']; + $idx = $status['indexed']; + $pi = $status['pending_index']; + $pu = $status['pending_usage']; + + $ext = $status['external']; + $ep = $ext['pending'] ?? 0; + $eq = $ext['queued'] ?? 0; + $es = $ext['scanned'] ?? 0; + $em = $ext['matches'] ?? 0; + $ee = $ext['error'] ?? 0; + + $jobs = $status['jobs']; + $ji = $jobs['index']; + $ju = $jobs['usage']; + $je = $jobs['external']; + + $needs_work = $pi > 0 || $pu > 0 || $ep > 0 || $eq > 0; + $any_job = $ji || $ju || $je; + ?> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
dot( $db_ok ? 'ok' : 'error' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> + + + + + +
dot( $as_ok ? 'ok' : 'error' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> + + + + + + + + + +
dot( $cron ? 'warn' : 'ok' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> + + + + + + + + + wp cron event run --due-now + +
dot( $pi > 0 ? 'warn' : 'ok' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> + + + 0 ) : ?> + + +
dot( $pu > 0 ? 'warn' : 'ok' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> + + + 0 ) : ?> + + +
+ 0 ) { + $ext_dot = 'error'; + } elseif ( $ep > 0 || $eq > 0 ) { + $ext_dot = 'warn'; + } else { + $ext_dot = 'ok'; + } + echo $this->dot( $ext_dot ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + ?> + + + + 0 ) : ?> + + 0 ) : ?> + + +
+ dot( $jobs_dot ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + ?> + + + + + + + + + + + + + + +
+ + +

+ + wp mra scan-internal + wp mra scan-external +

+ + 'schedule_internal', + 'label' => __( 'Schedule internal scan', 'robotstxt-mediaaudit' ), + 'desc' => __( 'Queue a background job to index all unindexed media files and scan their usage across posts, pages, and custom fields. Safe to run at any time — will not re-process completed work.', 'robotstxt-mediaaudit' ), + 'class' => 'button button-primary', + 'confirm' => '', + ), + array( + 'op' => 'schedule_external', + 'label' => __( 'Schedule external scan', 'robotstxt-mediaaudit' ), + 'desc' => __( 'Mark all pending attachments as queued and launch a background reverse-image-search via the configured providers. Requires API keys in Settings.', 'robotstxt-mediaaudit' ), + 'class' => 'button button-primary', + 'confirm' => '', + ), + array( + 'op' => 'requeue_errors', + 'label' => __( 'Requeue scan errors', 'robotstxt-mediaaudit' ), + 'desc' => __( 'Re-queues only the attachments that failed with an error on the last external scan run. Does not affect already-scanned items. Useful to retry after fixing API key issues.', 'robotstxt-mediaaudit' ), + 'class' => 'button', + 'confirm' => '', + ), + ); + + $destructive = array( + array( + 'op' => 'rescan_usage', + 'label' => __( 'Reset usage data', 'robotstxt-mediaaudit' ), + 'desc' => __( 'Clears all usage rows and resets the usage-scan flag for every indexed attachment. File metadata in the index is preserved. A background usage scan is scheduled immediately after.', 'robotstxt-mediaaudit' ), + 'class' => 'button', + 'confirm' => __( 'This will delete all usage data. Continue?', 'robotstxt-mediaaudit' ), + ), + array( + 'op' => 'reindex_all', + 'label' => __( 'Re-index media library', 'robotstxt-mediaaudit' ), + 'desc' => __( 'Deletes all index and usage entries, then schedules a full fresh re-index. External scan results are not affected. Useful if many media files have been added or removed.', 'robotstxt-mediaaudit' ), + 'class' => 'button', + 'confirm' => __( 'This will delete all index and usage data. Continue?', 'robotstxt-mediaaudit' ), + ), + array( + 'op' => 'purge_external', + 'label' => __( 'Purge external data', 'robotstxt-mediaaudit' ), + 'desc' => __( 'Deletes all external scan results and resets every attachment\'s external status to pending. Use this before switching API providers or to force a complete re-scan.', 'robotstxt-mediaaudit' ), + 'class' => 'button', + 'confirm' => __( 'This will delete all external scan results. Continue?', 'robotstxt-mediaaudit' ), + ), + array( + 'op' => 'full_reset', + 'label' => __( 'Reset all data', 'robotstxt-mediaaudit' ), + 'desc' => __( 'Truncates all three plugin tables (index, usage, external results) and schedules a fresh indexing run. Use this to start completely from scratch.', 'robotstxt-mediaaudit' ), + 'class' => 'button button-link-delete', + 'confirm' => __( 'This will permanently delete ALL plugin data and cannot be undone. Are you absolutely sure?', 'robotstxt-mediaaudit' ), + ), + ); + ?> +
+ + render_op_card( $op ); ?> + +
+ +

+ +

+

+ +

+
+ + render_op_card( $op ); ?> + +
+ +
+

+

+
+ + + +
+
+ 'index', + 'label' => __( 'Index media files', 'robotstxt-mediaaudit' ), + 'desc' => __( 'Discovers unindexed images and registers them in the plugin index. Run this first.', 'robotstxt-mediaaudit' ), + ), + array( + 'type' => 'usage', + 'label' => __( 'Scan usage', 'robotstxt-mediaaudit' ), + 'desc' => __( 'Checks every indexed image for usage across posts, pages, and custom fields.', 'robotstxt-mediaaudit' ), + ), + array( + 'type' => 'external', + 'label' => __( 'External scan', 'robotstxt-mediaaudit' ), + 'desc' => __( 'Sends images to external providers for reverse-image search. Requires API keys in Settings.', 'robotstxt-mediaaudit' ), + ), + ); + ?> +
+ +
+

+

+ +
+ + +
+
+ +
+ '; + } +} diff --git a/includes/Core/Database.php b/includes/Core/Database.php index 8fa5a18..7f74413 100644 --- a/includes/Core/Database.php +++ b/includes/Core/Database.php @@ -57,6 +57,23 @@ class Database { $wpdb->query( "DROP TABLE IF EXISTS `{$wpdb->prefix}mra_media_index`" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery } + /** + * Truncates all plugin tables, removing all data while preserving the table structure. + * + * Used by the Tools page "Reset all data" operation. + * + * @return void + */ + public static function truncate_all(): void { + global $wpdb; + + // phpcs:disable WordPress.DB.DirectDatabaseQuery + $wpdb->query( "TRUNCATE TABLE `{$wpdb->prefix}mra_external_results`" ); + $wpdb->query( "TRUNCATE TABLE `{$wpdb->prefix}mra_media_usage`" ); + $wpdb->query( "TRUNCATE TABLE `{$wpdb->prefix}mra_media_index`" ); + // phpcs:enable WordPress.DB.DirectDatabaseQuery + } + // ------------------------------------------------------------------------- // Migrations // ------------------------------------------------------------------------- diff --git a/includes/Core/Plugin.php b/includes/Core/Plugin.php index 7266f8e..8a5da87 100644 --- a/includes/Core/Plugin.php +++ b/includes/Core/Plugin.php @@ -9,6 +9,7 @@ namespace MediaRightsAudit\Core; use MediaRightsAudit\Admin\AuditPage; use MediaRightsAudit\Admin\Settings; +use MediaRightsAudit\Admin\ToolsPage; use MediaRightsAudit\CLI\Command; use MediaRightsAudit\External\AbstractProvider; use MediaRightsAudit\External\ExternalScanner; @@ -38,12 +39,20 @@ class Plugin { */ private AuditPage $audit_page; + /** + * Tools & Status page controller. + * + * @var ToolsPage + */ + private ToolsPage $tools_page; + /** * Initialises dependencies. */ public function __construct() { $this->settings = new Settings(); $this->audit_page = new AuditPage(); + $this->tools_page = new ToolsPage(); } /** @@ -59,6 +68,7 @@ class Plugin { add_action( AttachmentIndexer::AS_HOOK, array( AttachmentIndexer::class, 'process_scheduled_batch' ) ); add_action( UsageScanner::AS_HOOK, array( UsageScanner::class, 'process_scheduled_batch' ) ); add_action( ExternalScanner::AS_HOOK, array( ExternalScanner::class, 'process_scheduled_batch' ) ); + add_action( 'wp_ajax_mra_run_batch', array( $this->tools_page, 'ajax_run_batch' ) ); add_filter( 'mra/external/providers', array( $this, 'register_providers' ) ); add_filter( 'wp_privacy_personal_data_exporters', array( DataExporter::class, 'register' ) ); add_filter( 'wp_privacy_personal_data_erasers', array( DataEraser::class, 'register' ) ); @@ -107,6 +117,19 @@ class Plugin { array( $this->audit_page, 'render' ) ); + $tools_suffix = add_submenu_page( + 'robotstxt-mediaaudit', + __( 'Tools', 'robotstxt-mediaaudit' ), + __( 'Tools', 'robotstxt-mediaaudit' ), + 'manage_options', + 'robotstxt-mediaaudit-tools', + array( $this->tools_page, 'render' ) + ); + + if ( is_string( $tools_suffix ) ) { + $this->tools_page->set_hook_suffix( $tools_suffix ); + } + add_submenu_page( 'robotstxt-mediaaudit', __( 'Settings', 'robotstxt-mediaaudit' ), diff --git a/includes/External/ExternalScanner.php b/includes/External/ExternalScanner.php index 806c16d..3d7600c 100644 --- a/includes/External/ExternalScanner.php +++ b/includes/External/ExternalScanner.php @@ -184,6 +184,39 @@ class ExternalScanner { } } + /** + * Re-queues all attachments that previously failed with external_status = 'error'. + * + * Does not touch pending, queued, scanned, or matched attachments. + * Schedules a background scan batch if any items were re-queued and + * no batch is already pending. + * + * @return int Number of attachments re-queued. + */ + public static function requeue_errors(): int { + global $wpdb; + + // phpcs:disable WordPress.DB.DirectDatabaseQuery + $count = (int) $wpdb->get_var( + "SELECT COUNT(*) FROM {$wpdb->prefix}mra_media_index WHERE external_status = 'error'" + ); + + if ( $count > 0 ) { + $wpdb->query( + "UPDATE {$wpdb->prefix}mra_media_index + SET external_status = 'queued', external_scanned_at = NULL + WHERE external_status = 'error'" + ); + + if ( ! Scheduler::has_pending( self::AS_HOOK ) ) { + Scheduler::schedule_single( self::AS_HOOK ); + } + } + // phpcs:enable WordPress.DB.DirectDatabaseQuery + + return $count; + } + /** * Deletes external scan results and resets status to pending for given IDs. * @@ -221,6 +254,62 @@ class ExternalScanner { // phpcs:enable WordPress.DB.DirectDatabaseQuery,WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare } + /** + * Deletes all external scan results and resets every attachment's external_status to pending. + * + * @return int Number of result rows deleted. + */ + public static function purge_all(): int { + global $wpdb; + + // phpcs:disable WordPress.DB.DirectDatabaseQuery + $count = (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->prefix}mra_external_results" ); + $wpdb->query( "DELETE FROM {$wpdb->prefix}mra_external_results" ); + $wpdb->query( + "UPDATE {$wpdb->prefix}mra_media_index + SET external_status = 'pending', external_scanned_at = NULL" + ); + // phpcs:enable WordPress.DB.DirectDatabaseQuery + + return $count; + } + + /** + * Returns a count of attachments grouped by external_status. + * + * @return array + */ + public static function get_status_counts(): array { + global $wpdb; + + $rows = $wpdb->get_results( // phpcs:ignore WordPress.DB.DirectDatabaseQuery + "SELECT external_status, COUNT(*) AS cnt + FROM {$wpdb->prefix}mra_media_index + GROUP BY external_status", + ARRAY_A + ); + + $counts = array( + 'pending' => 0, + 'queued' => 0, + 'scanned' => 0, + 'matches' => 0, + 'error' => 0, + ); + + if ( is_array( $rows ) ) { + foreach ( $rows as $row ) { + $s = is_array( $row ) && is_string( $row['external_status'] ) ? $row['external_status'] : ''; + if ( isset( $counts[ $s ] ) ) { + $cnt = isset( $row['cnt'] ) && is_numeric( $row['cnt'] ) ? (int) $row['cnt'] : 0; + $counts[ $s ] = $cnt; + } + } + } + + return $counts; + } + // ------------------------------------------------------------------------- // Private helpers // ------------------------------------------------------------------------- diff --git a/includes/Internal/AttachmentIndexer.php b/includes/Internal/AttachmentIndexer.php index 0ba069f..4f213b0 100644 --- a/includes/Internal/AttachmentIndexer.php +++ b/includes/Internal/AttachmentIndexer.php @@ -172,4 +172,21 @@ class AttachmentIndexer { Scheduler::schedule_single( self::AS_HOOK ); } } + + /** + * Deletes all rows from mra_media_index and mra_media_usage, forcing a full re-index on the next run. + * + * @return int Number of index rows removed. + */ + public static function reset_all(): int { + global $wpdb; + + // phpcs:disable WordPress.DB.DirectDatabaseQuery + $count = (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->prefix}mra_media_index" ); + $wpdb->query( "DELETE FROM {$wpdb->prefix}mra_media_usage" ); + $wpdb->query( "DELETE FROM {$wpdb->prefix}mra_media_index" ); + // phpcs:enable WordPress.DB.DirectDatabaseQuery + + return $count; + } } diff --git a/includes/Internal/UsageScanner.php b/includes/Internal/UsageScanner.php index 17558cc..470d859 100644 --- a/includes/Internal/UsageScanner.php +++ b/includes/Internal/UsageScanner.php @@ -461,4 +461,26 @@ class UsageScanner { Scheduler::schedule_single( self::AS_HOOK ); } } + + /** + * Resets usage scan state for all indexed attachments. + * + * Clears internal_scanned_at so every attachment is re-queued for scanning, and + * deletes all mra_media_usage rows. File metadata in mra_media_index is preserved. + * + * @return int Number of attachments reset. + */ + public static function reset_all(): int { + global $wpdb; + + // phpcs:disable WordPress.DB.DirectDatabaseQuery + $count = (int) $wpdb->get_var( + "SELECT COUNT(*) FROM {$wpdb->prefix}mra_media_index WHERE internal_scanned_at IS NOT NULL" + ); + $wpdb->query( "UPDATE {$wpdb->prefix}mra_media_index SET internal_scanned_at = NULL" ); + $wpdb->query( "DELETE FROM {$wpdb->prefix}mra_media_usage" ); + // phpcs:enable WordPress.DB.DirectDatabaseQuery + + return $count; + } } diff --git a/languages/robotstxt-mediaaudit-ca.mo b/languages/robotstxt-mediaaudit-ca.mo index f18b88b..90b83b0 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 4adb207..07b72d0 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.0.0\n" +"Project-Id-Version: Media Audit (by ROBOTSTXT) 1.2.0\n" "Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/robotstxt-" "mediaaudit\n" -"POT-Creation-Date: 2026-05-02T06:06:40+00:00\n" +"POT-Creation-Date: 2026-05-02T07:36:54+00:00\n" "PO-Revision-Date: 2026-05-01 07:41+0000\n" "Last-Translator: Javier Casares \n" "Language-Team: Catalan \n" @@ -23,8 +23,8 @@ msgstr "Auditoria de mitjans (by ROBOTSTXT)" #. Plugin URI of the plugin #: robotstxt-mediaaudit.php -msgid "https://robotstxt.es/plugins/media-audit/" -msgstr "https://robotstxt.es/plugins/media-audit/" +msgid "https://git.robotstxt.es/ROBOTSTXT/robotstxt-mediaaudit" +msgstr "" #. Description of the plugin #: robotstxt-mediaaudit.php @@ -37,279 +37,311 @@ msgstr "" #. Author of the plugin #: robotstxt-mediaaudit.php -msgid "Javier Casares" -msgstr "Javier Casares" +msgid "ROBOTSTXT" +msgstr "" #. Author URI of the plugin #: robotstxt-mediaaudit.php -msgid "https://javiercasares.com" -msgstr "https://javiercasares.com" +msgid "https://www.robotstxt.es/" +msgstr "" -#: includes/Admin/AuditPage.php:112 +#: includes/Admin/AuditPage.php:114 msgid "Loading…" msgstr "S'està carregant…" -#: includes/Admin/AuditPage.php:113 +#: includes/Admin/AuditPage.php:115 msgid "Error loading details." msgstr "S'ha produït un error en carregar els detalls." -#: includes/Admin/AuditPage.php:114 includes/Admin/AuditPage.php:190 +#: includes/Admin/AuditPage.php:116 includes/Admin/AuditPage.php:192 msgid "This image is not used in any post." msgstr "Aquesta imatge no s'utilitza en cap entrada." -#: includes/Admin/AuditPage.php:115 includes/Admin/AuditPage.php:152 +#: includes/Admin/AuditPage.php:117 includes/Admin/AuditPage.php:154 msgid "Usage Details" msgstr "Detalls d'ús" -#: includes/Admin/AuditPage.php:131 includes/Admin/Settings.php:325 +#: includes/Admin/AuditPage.php:133 includes/Admin/Settings.php:325 +#: includes/Admin/ToolsPage.php:245 msgid "You do not have permission to access this page." msgstr "No teniu permís per accedir a aquesta pàgina." -#: includes/Admin/AuditPage.php:138 includes/Core/Plugin.php:89 -#: includes/Core/Plugin.php:90 includes/Core/Plugin.php:103 -#: includes/Core/Plugin.php:104 includes/Privacy/DataEraser.php:33 +#: includes/Admin/AuditPage.php:140 includes/Core/Plugin.php:99 +#: includes/Core/Plugin.php:100 includes/Core/Plugin.php:113 +#: includes/Core/Plugin.php:114 includes/Privacy/DataEraser.php:33 #: includes/Privacy/DataExporter.php:45 msgid "Media Audit" msgstr "Auditoria de mitjans" -#: includes/Admin/AuditPage.php:144 +#: includes/Admin/AuditPage.php:146 msgid "Search" msgstr "Cerca" -#: includes/Admin/AuditPage.php:153 +#: includes/Admin/AuditPage.php:155 msgid "Close" msgstr "Tanca" -#: includes/Admin/AuditPage.php:171 includes/Admin/AuditPage.php:459 -#: includes/Admin/AuditPage.php:510 includes/Admin/AuditPage.php:542 +#: includes/Admin/AuditPage.php:173 includes/Admin/AuditPage.php:461 +#: includes/Admin/AuditPage.php:515 includes/Admin/AuditPage.php:648 +#: includes/Admin/AuditPage.php:680 includes/Admin/ToolsPage.php:105 +#: includes/Admin/ToolsPage.php:161 msgid "Insufficient permissions." msgstr "Permisos insuficients." -#: includes/Admin/AuditPage.php:176 +#: includes/Admin/AuditPage.php:178 msgid "Invalid attachment ID." msgstr "Identificador de fitxer adjunt no vàlid." -#: includes/Admin/AuditPage.php:184 +#: includes/Admin/AuditPage.php:186 msgid "Featured Image" msgstr "Imatge destacada" -#: includes/Admin/AuditPage.php:185 +#: includes/Admin/AuditPage.php:187 msgid "Post Content" msgstr "Contingut de l'entrada" -#: includes/Admin/AuditPage.php:186 +#: includes/Admin/AuditPage.php:188 msgid "Custom Field" msgstr "Camp personalitzat" -#: includes/Admin/AuditPage.php:194 +#: includes/Admin/AuditPage.php:196 msgid "Post" msgstr "Entrada" -#: includes/Admin/AuditPage.php:195 +#: includes/Admin/AuditPage.php:197 msgid "Type" msgstr "Tipus" -#: includes/Admin/AuditPage.php:196 +#: includes/Admin/AuditPage.php:198 msgid "Context" msgstr "Context" -#: includes/Admin/AuditPage.php:197 +#: includes/Admin/AuditPage.php:199 msgid "Status" msgstr "Estat" -#: includes/Admin/AuditPage.php:240 +#: includes/Admin/AuditPage.php:242 msgid "Internal Usage" msgstr "Ús intern" -#: includes/Admin/AuditPage.php:288 +#: includes/Admin/AuditPage.php:290 msgid "External Scan Results" msgstr "Resultats de l'exploració externa" #. translators: %d: number of external matches -#: includes/Admin/AuditPage.php:312 +#: includes/Admin/AuditPage.php:314 #, php-format msgid "%d match" msgid_plural "%d matches" msgstr[0] "%d coincidència" msgstr[1] "%d coincidències" -#: includes/Admin/AuditPage.php:317 +#: includes/Admin/AuditPage.php:319 msgid "No matches" msgstr "Sense coincidències" -#: includes/Admin/AuditPage.php:328 +#: includes/Admin/AuditPage.php:330 msgid "Domain" msgstr "Domini" -#: includes/Admin/AuditPage.php:329 +#: includes/Admin/AuditPage.php:331 msgid "Occurrences" msgstr "Aparicions" -#: includes/Admin/AuditPage.php:350 +#: includes/Admin/AuditPage.php:352 msgid "Consensus Domains" msgstr "Dominis de consens" #. translators: %d: number of providers that agree -#: includes/Admin/AuditPage.php:356 +#: includes/Admin/AuditPage.php:358 #, php-format msgid "%d provider" msgid_plural "%d providers" msgstr[0] "%d proveïdor" msgstr[1] "%d proveïdors" -#: includes/Admin/AuditPage.php:388 +#: includes/Admin/AuditPage.php:390 msgid "In Index" msgstr "A l'índex" #. translators: %d: count of images not yet indexed -#: includes/Admin/AuditPage.php:392 +#: includes/Admin/AuditPage.php:394 #, php-format msgid "%d not yet indexed" msgstr "%d encara no indexades" -#: includes/Admin/AuditPage.php:395 +#: includes/Admin/AuditPage.php:397 msgid "All indexed" msgstr "Totes indexades" -#: includes/Admin/AuditPage.php:400 includes/Admin/MediaListTable.php:275 +#: includes/Admin/AuditPage.php:402 includes/Admin/MediaListTable.php:278 msgid "Scanned" msgstr "Explorades" #. translators: %d: percentage -#: includes/Admin/AuditPage.php:403 +#: includes/Admin/AuditPage.php:405 #, php-format msgid "%d%% of index" msgstr "%d%% de l'índex" -#: includes/Admin/AuditPage.php:410 +#: includes/Admin/AuditPage.php:412 msgid "Used" msgstr "Usades" -#: includes/Admin/AuditPage.php:416 includes/Admin/MediaListTable.php:200 +#: includes/Admin/AuditPage.php:418 includes/Admin/MediaListTable.php:203 msgid "Unused" msgstr "No usades" -#: includes/Admin/AuditPage.php:423 +#: includes/Admin/AuditPage.php:425 msgid "External Matches" msgstr "Coincidències externes" -#: includes/Admin/AuditPage.php:473 +#: includes/Admin/AuditPage.php:475 includes/Admin/AuditPage.php:529 msgid "Could not open output stream." msgstr "No s'ha pogut obrir el flux de sortida." -#: includes/Admin/AuditPage.php:482 includes/Privacy/DataExporter.php:257 -#: includes/Privacy/DataExporter.php:299 +#: includes/Admin/AuditPage.php:484 includes/Admin/AuditPage.php:537 +#: includes/Privacy/DataExporter.php:257 includes/Privacy/DataExporter.php:299 msgid "Attachment ID" msgstr "ID del fitxer adjunt" -#: includes/Admin/AuditPage.php:483 includes/Admin/MediaListTable.php:52 -#: includes/Privacy/DataExporter.php:261 +#: includes/Admin/AuditPage.php:485 includes/Admin/AuditPage.php:538 +#: includes/Admin/MediaListTable.php:52 includes/Privacy/DataExporter.php:261 msgid "Filename" msgstr "Nom de fitxer" -#: includes/Admin/AuditPage.php:484 includes/Privacy/DataExporter.php:265 +#: includes/Admin/AuditPage.php:486 includes/Admin/AuditPage.php:539 +#: includes/Privacy/DataExporter.php:265 msgid "File URL" msgstr "URL del fitxer" -#: includes/Admin/AuditPage.php:485 +#: includes/Admin/AuditPage.php:487 msgid "MIME Type" msgstr "Tipus MIME" -#: includes/Admin/AuditPage.php:486 +#: includes/Admin/AuditPage.php:488 msgid "File Size (bytes)" msgstr "Mida del fitxer (bytes)" -#: includes/Admin/AuditPage.php:487 includes/Admin/MediaListTable.php:56 +#: includes/Admin/AuditPage.php:489 includes/Admin/AuditPage.php:540 +#: includes/Admin/MediaListTable.php:56 msgid "External Status" msgstr "Estat extern" -#: includes/Admin/AuditPage.php:488 includes/Admin/MediaListTable.php:54 +#: includes/Admin/AuditPage.php:490 includes/Admin/MediaListTable.php:54 msgid "Usage Count" msgstr "Nombre d'usos" -#: includes/Admin/AuditPage.php:489 +#: includes/Admin/AuditPage.php:491 msgid "Used In (post titles)" msgstr "Usat a (títols d'entrades)" +#: includes/Admin/AuditPage.php:541 includes/Admin/MediaListTable.php:57 +msgid "Last Scanned" +msgstr "Darrera exploració" + +#: includes/Admin/AuditPage.php:542 includes/Privacy/DataExporter.php:303 +msgid "Provider" +msgstr "Proveïdor" + +#: includes/Admin/AuditPage.php:543 includes/Privacy/DataExporter.php:307 +msgid "Match Count" +msgstr "Nombre de coincidències" + +#: includes/Admin/AuditPage.php:544 +msgid "Top Domains" +msgstr "Dominis principals" + #: includes/Admin/MediaListTable.php:51 msgid "Thumbnail" msgstr "Miniatura" #: includes/Admin/MediaListTable.php:53 msgid "ID" -msgstr "" +msgstr "ID" #: includes/Admin/MediaListTable.php:55 msgid "Usages" msgstr "Usos" -#: includes/Admin/MediaListTable.php:79 +#: includes/Admin/MediaListTable.php:81 msgid "Run External Scan" msgstr "Executar exploració externa" -#: includes/Admin/MediaListTable.php:80 +#: includes/Admin/MediaListTable.php:82 msgid "Purge External Data" msgstr "Eliminar dades externes" -#: includes/Admin/MediaListTable.php:81 +#: includes/Admin/MediaListTable.php:83 msgid "Export CSV" msgstr "Exportar CSV" -#: includes/Admin/MediaListTable.php:165 +#: includes/Admin/MediaListTable.php:84 +msgid "Export External Results CSV" +msgstr "Exportar CSV de resultats externs" + +#: includes/Admin/MediaListTable.php:168 msgid "Edit" msgstr "Edita" -#: includes/Admin/MediaListTable.php:170 +#: includes/Admin/MediaListTable.php:173 msgid "View Details" msgstr "Visualitza els detalls" -#: includes/Admin/MediaListTable.php:236 +#: includes/Admin/MediaListTable.php:239 msgid "(no title)" msgstr "(sense títol)" #. translators: %d: number of additional usages -#: includes/Admin/MediaListTable.php:251 +#: includes/Admin/MediaListTable.php:254 #, php-format msgid "+ %d more…" msgstr "+ %d més…" -#: includes/Admin/MediaListTable.php:273 +#: includes/Admin/MediaListTable.php:276 includes/Admin/ToolsPage.php:580 +#: includes/Admin/ToolsPage.php:581 includes/Admin/ToolsPage.php:582 msgid "Pending" msgstr "Pendent" -#: includes/Admin/MediaListTable.php:274 +#: includes/Admin/MediaListTable.php:277 msgid "Queued" msgstr "En cua" -#: includes/Admin/MediaListTable.php:276 +#: includes/Admin/MediaListTable.php:279 msgid "Matches Found" msgstr "Coincidències trobades" -#: includes/Admin/MediaListTable.php:277 +#: includes/Admin/MediaListTable.php:280 msgid "Error" msgstr "Error" -#: includes/Admin/MediaListTable.php:305 +#: includes/Admin/MediaListTable.php:308 msgid "View Results" msgstr "Visualitza els resultats" -#: includes/Admin/MediaListTable.php:336 +#. translators: %s: human-readable time difference, e.g. "3 days" +#: includes/Admin/MediaListTable.php:350 +#, php-format +msgid "%s ago" +msgstr "fa %s" + +#: includes/Admin/MediaListTable.php:380 msgid "All statuses" msgstr "Tots els estats" -#: includes/Admin/MediaListTable.php:350 +#: includes/Admin/MediaListTable.php:394 msgid "All post types" msgstr "Tots els tipus d'entrada" -#: includes/Admin/MediaListTable.php:365 +#: includes/Admin/MediaListTable.php:409 msgid "Unused only" msgstr "Només no usades" -#: includes/Admin/MediaListTable.php:368 +#: includes/Admin/MediaListTable.php:412 msgid "Filter" msgstr "Filtra" -#: includes/Admin/MediaListTable.php:378 +#: includes/Admin/MediaListTable.php:422 msgid "" "No indexed attachments found. Run wp mra scan-internal to populate the index." msgstr "" @@ -434,6 +466,417 @@ msgstr "" "Nombre màxim de sol·licituds d'API per minut per proveïdor (1–60). Per " "defecte: 10." +#: includes/Admin/ToolsPage.php:85 +msgid "Done." +msgstr "Fet." + +#: includes/Admin/ToolsPage.php:86 +msgid "Stopped." +msgstr "Aturat." + +#: includes/Admin/ToolsPage.php:87 +msgid "An error occurred." +msgstr "S'ha produït un error." + +#: includes/Admin/ToolsPage.php:139 +msgid "Invalid batch type." +msgstr "Tipus de lot no vàlid." + +#: includes/Admin/ToolsPage.php:256 +msgid "System Status" +msgstr "Estat del sistema" + +#: includes/Admin/ToolsPage.php:259 +msgid "Operations" +msgstr "Operacions" + +#: includes/Admin/ToolsPage.php:262 +msgid "Direct scan runner" +msgstr "Executor d'exploració directa" + +#: includes/Admin/ToolsPage.php:264 +msgid "" +"Run scan batches directly from your browser without relying on background " +"jobs. Useful when WP-Cron is disabled or Action Scheduler is not running. " +"Keep this tab open while processing." +msgstr "" +"Executeu lots d'exploració directament des del navegador sense dependre de " +"treballs en segon pla. Útil quan WP-Cron està desactivat o Action Scheduler " +"no s'està executant. Manteniu aquesta pestanya oberta mentre es processa." + +#: includes/Admin/ToolsPage.php:300 +msgid "not installed" +msgstr "no instal·lat" + +#: includes/Admin/ToolsPage.php:358 +msgid "Internal scan scheduled." +msgstr "Exploració interna programada." + +#: includes/Admin/ToolsPage.php:359 +msgid "External scan scheduled." +msgstr "Exploració externa programada." + +#: includes/Admin/ToolsPage.php:360 +msgid "Failed scans re-queued. Background scan scheduled." +msgstr "" +"Exploracions fallides reencolades. Exploració en segon pla " +"programada." + +#: includes/Admin/ToolsPage.php:361 +msgid "Usage data cleared. Background scan scheduled." +msgstr "Dades d'ús eliminades. Exploració en segon pla programada." + +#: includes/Admin/ToolsPage.php:362 +msgid "Index cleared. Fresh indexing scheduled." +msgstr "Índex eliminat. Nova indexació programada." + +#: includes/Admin/ToolsPage.php:363 +msgid "External scan data purged." +msgstr "Dades d'exploració externa eliminades." + +#: includes/Admin/ToolsPage.php:364 +msgid "All data reset. Fresh indexing scheduled." +msgstr "Totes les dades restablertes. Nova indexació programada." + +#: includes/Admin/ToolsPage.php:367 +msgid "Operation completed." +msgstr "Operació completada." + +#: includes/Admin/ToolsPage.php:411 +msgid "Database schema" +msgstr "Esquema de base de dades" + +#. translators: %s: version number +#: includes/Admin/ToolsPage.php:417 +#, php-format +msgid "v%s – up to date" +msgstr "v%s – actualitzat" + +#. translators: 1: stored version, 2: expected version +#: includes/Admin/ToolsPage.php:423 +#, php-format +msgid "Installed: %1$s — Required: %2$s" +msgstr "Instal·lat: %1$s — Requerit: %2$s" + +#: includes/Admin/ToolsPage.php:432 +msgid "Deactivate and reactivate the plugin to apply pending migrations." +msgstr "" +"Desactiveu i reactiveu el connector per aplicar les migracions " +"pendents." + +#: includes/Admin/ToolsPage.php:439 +msgid "Action Scheduler" +msgstr "Action Scheduler" + +#: includes/Admin/ToolsPage.php:442 +msgid "Available" +msgstr "Disponible" + +#: includes/Admin/ToolsPage.php:444 +msgid "Not available" +msgstr "No disponible" + +#: includes/Admin/ToolsPage.php:449 +msgid "" +"Install and activate the Action Scheduler plugin. Background scanning will " +"not work without it." +msgstr "" +"Instal·leu i activeu el connector Action Scheduler. L'exploració en segon " +"pla no funcionarà sense ell." + +#: includes/Admin/ToolsPage.php:456 +msgid "WP-Cron" +msgstr "WP-Cron" + +#: includes/Admin/ToolsPage.php:459 +msgid "Disabled (DISABLE_WP_CRON)" +msgstr "Desactivat (DISABLE_WP_CRON)" + +#: includes/Admin/ToolsPage.php:461 +msgid "Enabled" +msgstr "Activat" + +#: includes/Admin/ToolsPage.php:466 +msgid "" +"Background jobs will not fire automatically. Trigger the cron externally or " +"use WP-CLI:" +msgstr "" +"Els treballs en segon pla no s'executaran automàticament. Activeu el cron " +"externament o useu WP-CLI:" + +#: includes/Admin/ToolsPage.php:474 +msgid "Internal index" +msgstr "Índex intern" + +#. translators: 1: indexed count, 2: total images +#: includes/Admin/ToolsPage.php:479 +#, php-format +msgid "%1$s / %2$s images indexed" +msgstr "%1$s / %2$s imatges indexades" + +#. translators: %s: formatted count +#: includes/Admin/ToolsPage.php:490 +#, php-format +msgid "%s image not yet indexed." +msgid_plural "%s images not yet indexed." +msgstr[0] "%s imatge encara no indexada." +msgstr[1] "%s imatges encara no indexades." + +#: includes/Admin/ToolsPage.php:494 +msgid "Schedule an internal scan below." +msgstr "Programeu una exploració interna a continuació." + +#: includes/Admin/ToolsPage.php:502 +msgid "Usage scan" +msgstr "Exploració d'ús" + +#. translators: 1: scanned count, 2: total indexed +#: includes/Admin/ToolsPage.php:507 +#, php-format +msgid "%1$s / %2$s scanned for usage" +msgstr "%1$s / %2$s explorades per a ús" + +#. translators: %s: formatted count +#: includes/Admin/ToolsPage.php:518 +#, php-format +msgid "%s pending." +msgid_plural "%s pending." +msgstr[0] "%s pendent." +msgstr[1] "%s pendents." + +#: includes/Admin/ToolsPage.php:539 includes/Admin/ToolsPage.php:736 +msgid "External scan" +msgstr "Exploració externa" + +#. translators: 1: scanned 2: matches 3: queued 4: pending 5: errors +#: includes/Admin/ToolsPage.php:544 +#, php-format +msgid "%1$s scanned · %2$s matches · %3$s queued · %4$s pending · %5$s errors" +msgstr "" +"%1$s explorades · %2$s coincidències · %3$s en cua · %4$s pendents · " +"%5$s errors" + +#: includes/Admin/ToolsPage.php:555 +msgid "" +"Some scans failed. Check that API keys are configured correctly in Settings." +msgstr "" +"Algunes exploracions han fallat. Comproveu que les claus d'API estan " +"configurades correctament a la Configuració." + +#: includes/Admin/ToolsPage.php:557 +msgid "Schedule an external scan below to process pending items." +msgstr "" +"Programeu una exploració externa a continuació per processar els elements " +"pendents." + +#: includes/Admin/ToolsPage.php:575 +msgid "Scheduled jobs" +msgstr "Treballs programats" + +#: includes/Admin/ToolsPage.php:578 +msgid "Action Scheduler not available." +msgstr "Action Scheduler no disponible." + +#: includes/Admin/ToolsPage.php:580 +msgid "Index:" +msgstr "Índex:" + +#: includes/Admin/ToolsPage.php:581 +msgid "Usage:" +msgstr "Ús:" + +#: includes/Admin/ToolsPage.php:582 +msgid "External:" +msgstr "Extern:" + +#: includes/Admin/ToolsPage.php:587 +msgid "" +"There is work to do but no background job is scheduled. Use the operations " +"below." +msgstr "" +"Hi ha feina pendent però no hi ha cap treball en segon pla programat. " +"Useu les operacions de sota." + +#: includes/Admin/ToolsPage.php:589 +msgid "WP-Cron is disabled. Jobs will not fire automatically." +msgstr "" +"WP-Cron està desactivat. Els treballs no s'executaran " +"automàticament." + +#: includes/Admin/ToolsPage.php:599 +msgid "WP-CLI commands to process scans manually:" +msgstr "Comandes WP-CLI per processar exploracions manualment:" + +#: includes/Admin/ToolsPage.php:616 +msgid "Schedule internal scan" +msgstr "Programar exploració interna" + +#: includes/Admin/ToolsPage.php:617 +msgid "" +"Queue a background job to index all unindexed media files and scan their " +"usage across posts, pages, and custom fields. Safe to run at any time — will " +"not re-process completed work." +msgstr "" +"Posa en cua un treball en segon pla per indexar tots els fitxers multimèdia " +"no indexats i explorar el seu ús en entrades, pàgines i camps personalitzats. " +"Es pot executar en qualsevol moment: no reprocessarà el treball ja completat." + +#: includes/Admin/ToolsPage.php:623 +msgid "Schedule external scan" +msgstr "Programar exploració externa" + +#: includes/Admin/ToolsPage.php:624 +msgid "" +"Mark all pending attachments as queued and launch a background reverse-image-" +"search via the configured providers. Requires API keys in Settings." +msgstr "" +"Marca tots els fitxers adjunts pendents com en cua i llança una cerca inversa " +"d'imatges en segon pla a través dels proveïdors configurats. Requereix claus " +"d'API a la Configuració." + +#: includes/Admin/ToolsPage.php:630 +msgid "Requeue scan errors" +msgstr "Reencolar errors d'exploració" + +#: includes/Admin/ToolsPage.php:631 +msgid "" +"Re-queues only the attachments that failed with an error on the last " +"external scan run. Does not affect already-scanned items. Useful to retry " +"after fixing API key issues." +msgstr "" +"Reencola només els fitxers adjunts que van fallar amb un error en l'última " +"execució de l'exploració externa. No afecta els elements ja explorats. " +"Útil per reintentar després de corregir problemes amb les claus d'API." + +#: includes/Admin/ToolsPage.php:640 +msgid "Reset usage data" +msgstr "Restablir dades d'ús" + +#: includes/Admin/ToolsPage.php:641 +msgid "" +"Clears all usage rows and resets the usage-scan flag for every indexed " +"attachment. File metadata in the index is preserved. A background usage scan " +"is scheduled immediately after." +msgstr "" +"Elimina totes les files d'ús i restableix l'indicador d'exploració d'ús per " +"a cada fitxer adjunt indexat. Els metadades del fitxer a l'índex es " +"conserven. Es programa una exploració en segon pla immediatament després." + +#: includes/Admin/ToolsPage.php:643 +msgid "This will delete all usage data. Continue?" +msgstr "Això eliminarà totes les dades d'ús. Voleu continuar?" + +#: includes/Admin/ToolsPage.php:647 +msgid "Re-index media library" +msgstr "Re-indexar la biblioteca de mitjans" + +#: includes/Admin/ToolsPage.php:648 +msgid "" +"Deletes all index and usage entries, then schedules a full fresh re-index. " +"External scan results are not affected. Useful if many media files have been " +"added or removed." +msgstr "" +"Elimina totes les entrades de l'índex i d'ús, i després programa una " +"re-indexació completa. Els resultats de l'exploració externa no es veuen " +"afectats. Útil si s'han afegit o eliminat molts fitxers multimèdia." + +#: includes/Admin/ToolsPage.php:650 +msgid "This will delete all index and usage data. Continue?" +msgstr "Això eliminarà totes les dades de l'índex i d'ús. Voleu continuar?" + +#: includes/Admin/ToolsPage.php:654 +msgid "Purge external data" +msgstr "Purgar dades externes" + +#: includes/Admin/ToolsPage.php:655 +msgid "" +"Deletes all external scan results and resets every attachment's external " +"status to pending. Use this before switching API providers or to force a " +"complete re-scan." +msgstr "" +"Elimina tots els resultats de l'exploració externa i restableix l'estat " +"extern de cada fitxer adjunt a pendent. Useu-ho abans de canviar de " +"proveïdors d'API o per forçar una exploració completa." + +#: includes/Admin/ToolsPage.php:657 +msgid "This will delete all external scan results. Continue?" +msgstr "" +"Això eliminarà tots els resultats de l'exploració externa. Voleu " +"continuar?" + +#: includes/Admin/ToolsPage.php:661 +msgid "Reset all data" +msgstr "Restablir totes les dades" + +#: includes/Admin/ToolsPage.php:662 +msgid "" +"Truncates all three plugin tables (index, usage, external results) and " +"schedules a fresh indexing run. Use this to start completely from scratch." +msgstr "" +"Buida les tres taules del connector (índex, ús, resultats externs) i " +"programa una nova indexació. Useu-ho per començar completament des de zero." + +#: includes/Admin/ToolsPage.php:664 +msgid "" +"This will permanently delete ALL plugin data and cannot be undone. Are you " +"absolutely sure?" +msgstr "" +"Això eliminarà TOTES les dades del connector de forma permanent i no es pot " +"desfer. Esteu absolutament segurs?" + +#: includes/Admin/ToolsPage.php:675 +msgid "Destructive operations" +msgstr "Operacions destructives" + +#: includes/Admin/ToolsPage.php:678 +msgid "" +"These operations permanently delete plugin data. Intended for forced " +"rescans, troubleshooting, or starting fresh. A confirmation prompt will " +"appear before any action is taken." +msgstr "" +"Aquestes operacions eliminen les dades del connector de forma permanent. " +"Pensades per forçar re-exploracions, solucionar problemes o començar des de " +"zero. Apareixerà una confirmació abans d'executar qualsevol acció." + +#: includes/Admin/ToolsPage.php:726 +msgid "Index media files" +msgstr "Indexar fitxers multimèdia" + +#: includes/Admin/ToolsPage.php:727 +msgid "" +"Discovers unindexed images and registers them in the plugin index. Run this " +"first." +msgstr "" +"Descobreix imatges no indexades i les registra a l'índex del connector. " +"Executeu-ho primer." + +#: includes/Admin/ToolsPage.php:731 +msgid "Scan usage" +msgstr "Explorar l'ús" + +#: includes/Admin/ToolsPage.php:732 +msgid "" +"Checks every indexed image for usage across posts, pages, and custom fields." +msgstr "" +"Comprova l'ús de cada imatge indexada en entrades, pàgines i camps " +"personalitzats." + +#: includes/Admin/ToolsPage.php:737 +msgid "" +"Sends images to external providers for reverse-image search. Requires API " +"keys in Settings." +msgstr "" +"Envia imatges a proveïdors externs per a la cerca inversa d'imatges. " +"Requereix claus d'API a la Configuració." + +#: includes/Admin/ToolsPage.php:754 +msgid "Run now" +msgstr "Executa ara" + +#: includes/Admin/ToolsPage.php:757 +msgid "Stop" +msgstr "Atura" + #. translators: %s: DB version number #: includes/CLI/Command.php:49 #, php-format @@ -494,7 +937,11 @@ 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:112 includes/Core/Plugin.php:113 +#: includes/Core/Plugin.php:122 includes/Core/Plugin.php:123 +msgid "Tools" +msgstr "Eines" + +#: includes/Core/Plugin.php:135 includes/Core/Plugin.php:136 msgid "Settings" msgstr "Configuració" @@ -522,14 +969,15 @@ msgstr "Última exploració (interna)" msgid "Last Scanned (external)" msgstr "Última exploració (externa)" -#: includes/Privacy/DataExporter.php:303 -msgid "Provider" -msgstr "Proveïdor" - -#: includes/Privacy/DataExporter.php:307 -msgid "Match Count" -msgstr "Nombre de coincidències" - #: includes/Privacy/DataExporter.php:311 msgid "Scanned At" msgstr "Explorat el" + +#~ msgid "https://robotstxt.es/plugins/media-audit/" +#~ msgstr "https://robotstxt.es/plugins/media-audit/" + +#~ msgid "Javier Casares" +#~ msgstr "Javier Casares" + +#~ msgid "https://javiercasares.com" +#~ msgstr "https://javiercasares.com" diff --git a/languages/robotstxt-mediaaudit-es_ES.mo b/languages/robotstxt-mediaaudit-es_ES.mo index aea4c2b..bd685f0 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 d120cf5..3b5e1d5 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.0.0\n" +"Project-Id-Version: Media Audit (by ROBOTSTXT) 1.2.0\n" "Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/robotstxt-" "mediaaudit\n" -"POT-Creation-Date: 2026-05-02T06:06:40+00:00\n" +"POT-Creation-Date: 2026-05-02T07:36:54+00:00\n" "PO-Revision-Date: 2026-05-01 07:41+0000\n" "Last-Translator: Javier Casares \n" "Language-Team: Spanish (Spain) \n" @@ -23,8 +23,8 @@ msgstr "Auditoría de medios (by ROBOTSTXT)" #. Plugin URI of the plugin #: robotstxt-mediaaudit.php -msgid "https://robotstxt.es/plugins/media-audit/" -msgstr "https://robotstxt.es/plugins/media-audit/" +msgid "https://git.robotstxt.es/ROBOTSTXT/robotstxt-mediaaudit" +msgstr "" #. Description of the plugin #: robotstxt-mediaaudit.php @@ -37,279 +37,311 @@ msgstr "" #. Author of the plugin #: robotstxt-mediaaudit.php -msgid "Javier Casares" -msgstr "Javier Casares" +msgid "ROBOTSTXT" +msgstr "" #. Author URI of the plugin #: robotstxt-mediaaudit.php -msgid "https://javiercasares.com" -msgstr "https://javiercasares.com" +msgid "https://www.robotstxt.es/" +msgstr "" -#: includes/Admin/AuditPage.php:112 +#: includes/Admin/AuditPage.php:114 msgid "Loading…" msgstr "Cargando…" -#: includes/Admin/AuditPage.php:113 +#: includes/Admin/AuditPage.php:115 msgid "Error loading details." msgstr "Error al cargar los detalles." -#: includes/Admin/AuditPage.php:114 includes/Admin/AuditPage.php:190 +#: includes/Admin/AuditPage.php:116 includes/Admin/AuditPage.php:192 msgid "This image is not used in any post." msgstr "Esta imagen no se usa en ninguna entrada." -#: includes/Admin/AuditPage.php:115 includes/Admin/AuditPage.php:152 +#: includes/Admin/AuditPage.php:117 includes/Admin/AuditPage.php:154 msgid "Usage Details" msgstr "Detalles de uso" -#: includes/Admin/AuditPage.php:131 includes/Admin/Settings.php:325 +#: includes/Admin/AuditPage.php:133 includes/Admin/Settings.php:325 +#: includes/Admin/ToolsPage.php:245 msgid "You do not have permission to access this page." msgstr "No tienes permiso para acceder a esta página." -#: includes/Admin/AuditPage.php:138 includes/Core/Plugin.php:89 -#: includes/Core/Plugin.php:90 includes/Core/Plugin.php:103 -#: includes/Core/Plugin.php:104 includes/Privacy/DataEraser.php:33 +#: includes/Admin/AuditPage.php:140 includes/Core/Plugin.php:99 +#: includes/Core/Plugin.php:100 includes/Core/Plugin.php:113 +#: includes/Core/Plugin.php:114 includes/Privacy/DataEraser.php:33 #: includes/Privacy/DataExporter.php:45 msgid "Media Audit" msgstr "Auditoría de medios" -#: includes/Admin/AuditPage.php:144 +#: includes/Admin/AuditPage.php:146 msgid "Search" msgstr "Buscar" -#: includes/Admin/AuditPage.php:153 +#: includes/Admin/AuditPage.php:155 msgid "Close" msgstr "Cerrar" -#: includes/Admin/AuditPage.php:171 includes/Admin/AuditPage.php:459 -#: includes/Admin/AuditPage.php:510 includes/Admin/AuditPage.php:542 +#: includes/Admin/AuditPage.php:173 includes/Admin/AuditPage.php:461 +#: includes/Admin/AuditPage.php:515 includes/Admin/AuditPage.php:648 +#: includes/Admin/AuditPage.php:680 includes/Admin/ToolsPage.php:105 +#: includes/Admin/ToolsPage.php:161 msgid "Insufficient permissions." msgstr "Permisos insuficientes." -#: includes/Admin/AuditPage.php:176 +#: includes/Admin/AuditPage.php:178 msgid "Invalid attachment ID." msgstr "ID de adjunto no válido." -#: includes/Admin/AuditPage.php:184 +#: includes/Admin/AuditPage.php:186 msgid "Featured Image" msgstr "Imagen destacada" -#: includes/Admin/AuditPage.php:185 +#: includes/Admin/AuditPage.php:187 msgid "Post Content" msgstr "Contenido de la entrada" -#: includes/Admin/AuditPage.php:186 +#: includes/Admin/AuditPage.php:188 msgid "Custom Field" msgstr "Campo personalizado" -#: includes/Admin/AuditPage.php:194 +#: includes/Admin/AuditPage.php:196 msgid "Post" msgstr "Entrada" -#: includes/Admin/AuditPage.php:195 +#: includes/Admin/AuditPage.php:197 msgid "Type" msgstr "Tipo" -#: includes/Admin/AuditPage.php:196 +#: includes/Admin/AuditPage.php:198 msgid "Context" msgstr "Contexto" -#: includes/Admin/AuditPage.php:197 +#: includes/Admin/AuditPage.php:199 msgid "Status" msgstr "Estado" -#: includes/Admin/AuditPage.php:240 +#: includes/Admin/AuditPage.php:242 msgid "Internal Usage" msgstr "Uso interno" -#: includes/Admin/AuditPage.php:288 +#: includes/Admin/AuditPage.php:290 msgid "External Scan Results" msgstr "Resultados del escaneo externo" #. translators: %d: number of external matches -#: includes/Admin/AuditPage.php:312 +#: includes/Admin/AuditPage.php:314 #, php-format msgid "%d match" msgid_plural "%d matches" msgstr[0] "%d coincidencia" msgstr[1] "%d coincidencias" -#: includes/Admin/AuditPage.php:317 +#: includes/Admin/AuditPage.php:319 msgid "No matches" msgstr "Sin coincidencias" -#: includes/Admin/AuditPage.php:328 +#: includes/Admin/AuditPage.php:330 msgid "Domain" msgstr "Dominio" -#: includes/Admin/AuditPage.php:329 +#: includes/Admin/AuditPage.php:331 msgid "Occurrences" msgstr "Apariciones" -#: includes/Admin/AuditPage.php:350 +#: includes/Admin/AuditPage.php:352 msgid "Consensus Domains" msgstr "Dominios de consenso" #. translators: %d: number of providers that agree -#: includes/Admin/AuditPage.php:356 +#: includes/Admin/AuditPage.php:358 #, php-format msgid "%d provider" msgid_plural "%d providers" msgstr[0] "%d proveedor" msgstr[1] "%d proveedores" -#: includes/Admin/AuditPage.php:388 +#: includes/Admin/AuditPage.php:390 msgid "In Index" msgstr "En el índice" #. translators: %d: count of images not yet indexed -#: includes/Admin/AuditPage.php:392 +#: includes/Admin/AuditPage.php:394 #, php-format msgid "%d not yet indexed" msgstr "%d aún no indexadas" -#: includes/Admin/AuditPage.php:395 +#: includes/Admin/AuditPage.php:397 msgid "All indexed" msgstr "Todas indexadas" -#: includes/Admin/AuditPage.php:400 includes/Admin/MediaListTable.php:275 +#: includes/Admin/AuditPage.php:402 includes/Admin/MediaListTable.php:278 msgid "Scanned" msgstr "Escaneadas" #. translators: %d: percentage -#: includes/Admin/AuditPage.php:403 +#: includes/Admin/AuditPage.php:405 #, php-format msgid "%d%% of index" msgstr "%d%% del índice" -#: includes/Admin/AuditPage.php:410 +#: includes/Admin/AuditPage.php:412 msgid "Used" msgstr "Usadas" -#: includes/Admin/AuditPage.php:416 includes/Admin/MediaListTable.php:200 +#: includes/Admin/AuditPage.php:418 includes/Admin/MediaListTable.php:203 msgid "Unused" msgstr "Sin usar" -#: includes/Admin/AuditPage.php:423 +#: includes/Admin/AuditPage.php:425 msgid "External Matches" msgstr "Coincidencias externas" -#: includes/Admin/AuditPage.php:473 +#: includes/Admin/AuditPage.php:475 includes/Admin/AuditPage.php:529 msgid "Could not open output stream." msgstr "No se pudo abrir el flujo de salida." -#: includes/Admin/AuditPage.php:482 includes/Privacy/DataExporter.php:257 -#: includes/Privacy/DataExporter.php:299 +#: includes/Admin/AuditPage.php:484 includes/Admin/AuditPage.php:537 +#: includes/Privacy/DataExporter.php:257 includes/Privacy/DataExporter.php:299 msgid "Attachment ID" msgstr "ID del adjunto" -#: includes/Admin/AuditPage.php:483 includes/Admin/MediaListTable.php:52 -#: includes/Privacy/DataExporter.php:261 +#: includes/Admin/AuditPage.php:485 includes/Admin/AuditPage.php:538 +#: includes/Admin/MediaListTable.php:52 includes/Privacy/DataExporter.php:261 msgid "Filename" msgstr "Nombre de archivo" -#: includes/Admin/AuditPage.php:484 includes/Privacy/DataExporter.php:265 +#: includes/Admin/AuditPage.php:486 includes/Admin/AuditPage.php:539 +#: includes/Privacy/DataExporter.php:265 msgid "File URL" msgstr "URL del archivo" -#: includes/Admin/AuditPage.php:485 +#: includes/Admin/AuditPage.php:487 msgid "MIME Type" msgstr "Tipo MIME" -#: includes/Admin/AuditPage.php:486 +#: includes/Admin/AuditPage.php:488 msgid "File Size (bytes)" msgstr "Tamaño del archivo (bytes)" -#: includes/Admin/AuditPage.php:487 includes/Admin/MediaListTable.php:56 +#: includes/Admin/AuditPage.php:489 includes/Admin/AuditPage.php:540 +#: includes/Admin/MediaListTable.php:56 msgid "External Status" msgstr "Estado externo" -#: includes/Admin/AuditPage.php:488 includes/Admin/MediaListTable.php:54 +#: includes/Admin/AuditPage.php:490 includes/Admin/MediaListTable.php:54 msgid "Usage Count" msgstr "Número de usos" -#: includes/Admin/AuditPage.php:489 +#: includes/Admin/AuditPage.php:491 msgid "Used In (post titles)" msgstr "Usado en (títulos de entradas)" +#: includes/Admin/AuditPage.php:541 includes/Admin/MediaListTable.php:57 +msgid "Last Scanned" +msgstr "Último escaneo" + +#: includes/Admin/AuditPage.php:542 includes/Privacy/DataExporter.php:303 +msgid "Provider" +msgstr "Proveedor" + +#: includes/Admin/AuditPage.php:543 includes/Privacy/DataExporter.php:307 +msgid "Match Count" +msgstr "Número de coincidencias" + +#: includes/Admin/AuditPage.php:544 +msgid "Top Domains" +msgstr "Dominios principales" + #: includes/Admin/MediaListTable.php:51 msgid "Thumbnail" msgstr "Miniatura" #: includes/Admin/MediaListTable.php:53 msgid "ID" -msgstr "" +msgstr "ID" #: includes/Admin/MediaListTable.php:55 msgid "Usages" msgstr "Usos" -#: includes/Admin/MediaListTable.php:79 +#: includes/Admin/MediaListTable.php:81 msgid "Run External Scan" msgstr "Ejecutar escaneo externo" -#: includes/Admin/MediaListTable.php:80 +#: includes/Admin/MediaListTable.php:82 msgid "Purge External Data" msgstr "Purgar datos externos" -#: includes/Admin/MediaListTable.php:81 +#: includes/Admin/MediaListTable.php:83 msgid "Export CSV" msgstr "Exportar CSV" -#: includes/Admin/MediaListTable.php:165 +#: includes/Admin/MediaListTable.php:84 +msgid "Export External Results CSV" +msgstr "Exportar CSV de resultados externos" + +#: includes/Admin/MediaListTable.php:168 msgid "Edit" msgstr "Editar" -#: includes/Admin/MediaListTable.php:170 +#: includes/Admin/MediaListTable.php:173 msgid "View Details" msgstr "Ver detalles" -#: includes/Admin/MediaListTable.php:236 +#: includes/Admin/MediaListTable.php:239 msgid "(no title)" msgstr "(sin título)" #. translators: %d: number of additional usages -#: includes/Admin/MediaListTable.php:251 +#: includes/Admin/MediaListTable.php:254 #, php-format msgid "+ %d more…" msgstr "+ %d más…" -#: includes/Admin/MediaListTable.php:273 +#: includes/Admin/MediaListTable.php:276 includes/Admin/ToolsPage.php:580 +#: includes/Admin/ToolsPage.php:581 includes/Admin/ToolsPage.php:582 msgid "Pending" msgstr "Pendiente" -#: includes/Admin/MediaListTable.php:274 +#: includes/Admin/MediaListTable.php:277 msgid "Queued" msgstr "En cola" -#: includes/Admin/MediaListTable.php:276 +#: includes/Admin/MediaListTable.php:279 msgid "Matches Found" msgstr "Coincidencias encontradas" -#: includes/Admin/MediaListTable.php:277 +#: includes/Admin/MediaListTable.php:280 msgid "Error" msgstr "Error" -#: includes/Admin/MediaListTable.php:305 +#: includes/Admin/MediaListTable.php:308 msgid "View Results" msgstr "Ver resultados" -#: includes/Admin/MediaListTable.php:336 +#. translators: %s: human-readable time difference, e.g. "3 days" +#: includes/Admin/MediaListTable.php:350 +#, php-format +msgid "%s ago" +msgstr "hace %s" + +#: includes/Admin/MediaListTable.php:380 msgid "All statuses" msgstr "Todos los estados" -#: includes/Admin/MediaListTable.php:350 +#: includes/Admin/MediaListTable.php:394 msgid "All post types" msgstr "Todos los tipos de entrada" -#: includes/Admin/MediaListTable.php:365 +#: includes/Admin/MediaListTable.php:409 msgid "Unused only" msgstr "Solo sin usar" -#: includes/Admin/MediaListTable.php:368 +#: includes/Admin/MediaListTable.php:412 msgid "Filter" msgstr "Filtrar" -#: includes/Admin/MediaListTable.php:378 +#: includes/Admin/MediaListTable.php:422 msgid "" "No indexed attachments found. Run wp mra scan-internal to populate the index." msgstr "" @@ -435,6 +467,412 @@ msgstr "" "Máximo de solicitudes de API por minuto por proveedor (1–60). " "Predeterminado: 10." +#: includes/Admin/ToolsPage.php:85 +msgid "Done." +msgstr "Listo." + +#: includes/Admin/ToolsPage.php:86 +msgid "Stopped." +msgstr "Detenido." + +#: includes/Admin/ToolsPage.php:87 +msgid "An error occurred." +msgstr "Se ha producido un error." + +#: includes/Admin/ToolsPage.php:139 +msgid "Invalid batch type." +msgstr "Tipo de lote no válido." + +#: includes/Admin/ToolsPage.php:256 +msgid "System Status" +msgstr "Estado del sistema" + +#: includes/Admin/ToolsPage.php:259 +msgid "Operations" +msgstr "Operaciones" + +#: includes/Admin/ToolsPage.php:262 +msgid "Direct scan runner" +msgstr "Ejecutor de escaneo directo" + +#: includes/Admin/ToolsPage.php:264 +msgid "" +"Run scan batches directly from your browser without relying on background " +"jobs. Useful when WP-Cron is disabled or Action Scheduler is not running. " +"Keep this tab open while processing." +msgstr "" +"Ejecuta lotes de escaneo directamente desde tu navegador sin depender de " +"trabajos en segundo plano. Útil cuando WP-Cron está desactivado o Action " +"Scheduler no está en ejecución. Mantén esta pestaña abierta mientras se " +"procesa." + +#: includes/Admin/ToolsPage.php:300 +msgid "not installed" +msgstr "no instalado" + +#: includes/Admin/ToolsPage.php:358 +msgid "Internal scan scheduled." +msgstr "Escaneo interno programado." + +#: includes/Admin/ToolsPage.php:359 +msgid "External scan scheduled." +msgstr "Escaneo externo programado." + +#: includes/Admin/ToolsPage.php:360 +msgid "Failed scans re-queued. Background scan scheduled." +msgstr "Escaneos fallidos reencolados. Escaneo en segundo plano programado." + +#: includes/Admin/ToolsPage.php:361 +msgid "Usage data cleared. Background scan scheduled." +msgstr "Datos de uso eliminados. Escaneo en segundo plano programado." + +#: includes/Admin/ToolsPage.php:362 +msgid "Index cleared. Fresh indexing scheduled." +msgstr "Índice eliminado. Re-indexación programada." + +#: includes/Admin/ToolsPage.php:363 +msgid "External scan data purged." +msgstr "Datos de escaneo externo eliminados." + +#: includes/Admin/ToolsPage.php:364 +msgid "All data reset. Fresh indexing scheduled." +msgstr "Todos los datos restablecidos. Re-indexación programada." + +#: includes/Admin/ToolsPage.php:367 +msgid "Operation completed." +msgstr "Operación completada." + +#: includes/Admin/ToolsPage.php:411 +msgid "Database schema" +msgstr "Esquema de base de datos" + +#. translators: %s: version number +#: includes/Admin/ToolsPage.php:417 +#, php-format +msgid "v%s – up to date" +msgstr "v%s – actualizado" + +#. translators: 1: stored version, 2: expected version +#: includes/Admin/ToolsPage.php:423 +#, php-format +msgid "Installed: %1$s — Required: %2$s" +msgstr "Instalado: %1$s — Requerido: %2$s" + +#: includes/Admin/ToolsPage.php:432 +msgid "Deactivate and reactivate the plugin to apply pending migrations." +msgstr "" +"Desactiva y reactiva el plugin para aplicar las migraciones pendientes." + +#: includes/Admin/ToolsPage.php:439 +msgid "Action Scheduler" +msgstr "Action Scheduler" + +#: includes/Admin/ToolsPage.php:442 +msgid "Available" +msgstr "Disponible" + +#: includes/Admin/ToolsPage.php:444 +msgid "Not available" +msgstr "No disponible" + +#: includes/Admin/ToolsPage.php:449 +msgid "" +"Install and activate the Action Scheduler plugin. Background scanning will " +"not work without it." +msgstr "" +"Instala y activa el plugin Action Scheduler. El escaneo en segundo plano no " +"funcionará sin él." + +#: includes/Admin/ToolsPage.php:456 +msgid "WP-Cron" +msgstr "WP-Cron" + +#: includes/Admin/ToolsPage.php:459 +msgid "Disabled (DISABLE_WP_CRON)" +msgstr "Desactivado (DISABLE_WP_CRON)" + +#: includes/Admin/ToolsPage.php:461 +msgid "Enabled" +msgstr "Activado" + +#: includes/Admin/ToolsPage.php:466 +msgid "" +"Background jobs will not fire automatically. Trigger the cron externally or " +"use WP-CLI:" +msgstr "" +"Los trabajos en segundo plano no se ejecutarán automáticamente. Activa el " +"cron externamente o usa WP-CLI:" + +#: includes/Admin/ToolsPage.php:474 +msgid "Internal index" +msgstr "Índice interno" + +#. translators: 1: indexed count, 2: total images +#: includes/Admin/ToolsPage.php:479 +#, php-format +msgid "%1$s / %2$s images indexed" +msgstr "%1$s / %2$s imágenes indexadas" + +#. translators: %s: formatted count +#: includes/Admin/ToolsPage.php:490 +#, php-format +msgid "%s image not yet indexed." +msgid_plural "%s images not yet indexed." +msgstr[0] "%s imagen aún no indexada." +msgstr[1] "%s imágenes aún no indexadas." + +#: includes/Admin/ToolsPage.php:494 +msgid "Schedule an internal scan below." +msgstr "Programa un escaneo interno más abajo." + +#: includes/Admin/ToolsPage.php:502 +msgid "Usage scan" +msgstr "Escaneo de uso" + +#. translators: 1: scanned count, 2: total indexed +#: includes/Admin/ToolsPage.php:507 +#, php-format +msgid "%1$s / %2$s scanned for usage" +msgstr "%1$s / %2$s escaneadas de uso" + +#. translators: %s: formatted count +#: includes/Admin/ToolsPage.php:518 +#, php-format +msgid "%s pending." +msgid_plural "%s pending." +msgstr[0] "%s pendiente." +msgstr[1] "%s pendientes." + +#: includes/Admin/ToolsPage.php:539 includes/Admin/ToolsPage.php:736 +msgid "External scan" +msgstr "Escaneo externo" + +#. translators: 1: scanned 2: matches 3: queued 4: pending 5: errors +#: includes/Admin/ToolsPage.php:544 +#, php-format +msgid "%1$s scanned · %2$s matches · %3$s queued · %4$s pending · %5$s errors" +msgstr "" +"%1$s escaneadas · %2$s coincidencias · %3$s en cola · %4$s pendientes · %5$s " +"errores" + +#: includes/Admin/ToolsPage.php:555 +msgid "" +"Some scans failed. Check that API keys are configured correctly in Settings." +msgstr "" +"Algunos escaneos fallaron. Verifica que las claves de API están configuradas " +"correctamente en Ajustes." + +#: includes/Admin/ToolsPage.php:557 +msgid "Schedule an external scan below to process pending items." +msgstr "" +"Programa un escaneo externo más abajo para procesar los elementos pendientes." + +#: includes/Admin/ToolsPage.php:575 +msgid "Scheduled jobs" +msgstr "Trabajos programados" + +#: includes/Admin/ToolsPage.php:578 +msgid "Action Scheduler not available." +msgstr "Action Scheduler no disponible." + +#: includes/Admin/ToolsPage.php:580 +msgid "Index:" +msgstr "Índice:" + +#: includes/Admin/ToolsPage.php:581 +msgid "Usage:" +msgstr "Uso:" + +#: includes/Admin/ToolsPage.php:582 +msgid "External:" +msgstr "Externo:" + +#: includes/Admin/ToolsPage.php:587 +msgid "" +"There is work to do but no background job is scheduled. Use the operations " +"below." +msgstr "" +"Hay trabajo pendiente pero no hay ningún trabajo en segundo plano " +"programado. Usa las operaciones de abajo." + +#: includes/Admin/ToolsPage.php:589 +msgid "WP-Cron is disabled. Jobs will not fire automatically." +msgstr "" +"WP-Cron está desactivado. Los trabajos no se ejecutarán automáticamente." + +#: includes/Admin/ToolsPage.php:599 +msgid "WP-CLI commands to process scans manually:" +msgstr "Comandos WP-CLI para procesar escaneos manualmente:" + +#: includes/Admin/ToolsPage.php:616 +msgid "Schedule internal scan" +msgstr "Programar escaneo interno" + +#: includes/Admin/ToolsPage.php:617 +msgid "" +"Queue a background job to index all unindexed media files and scan their " +"usage across posts, pages, and custom fields. Safe to run at any time — will " +"not re-process completed work." +msgstr "" +"Cola un trabajo en segundo plano para indexar todos los archivos multimedia " +"no indexados y escanear su uso en entradas, páginas y campos personalizados. " +"Es seguro ejecutarlo en cualquier momento: no reprocesará el trabajo ya " +"completado." + +#: includes/Admin/ToolsPage.php:623 +msgid "Schedule external scan" +msgstr "Programar escaneo externo" + +#: includes/Admin/ToolsPage.php:624 +msgid "" +"Mark all pending attachments as queued and launch a background reverse-image-" +"search via the configured providers. Requires API keys in Settings." +msgstr "" +"Marca todos los adjuntos pendientes como en cola y lanza una búsqueda " +"inversa de imágenes en segundo plano a través de los proveedores " +"configurados. Requiere claves de API en Ajustes." + +#: includes/Admin/ToolsPage.php:630 +msgid "Requeue scan errors" +msgstr "Reencolar errores de escaneo" + +#: includes/Admin/ToolsPage.php:631 +msgid "" +"Re-queues only the attachments that failed with an error on the last " +"external scan run. Does not affect already-scanned items. Useful to retry " +"after fixing API key issues." +msgstr "" +"Reencola solo los adjuntos que fallaron con un error en la última ejecución " +"del escaneo externo. No afecta a los elementos ya escaneados. Útil para " +"reintentar después de corregir problemas con las claves de API." + +#: includes/Admin/ToolsPage.php:640 +msgid "Reset usage data" +msgstr "Restablecer datos de uso" + +#: includes/Admin/ToolsPage.php:641 +msgid "" +"Clears all usage rows and resets the usage-scan flag for every indexed " +"attachment. File metadata in the index is preserved. A background usage scan " +"is scheduled immediately after." +msgstr "" +"Elimina todas las filas de uso y restablece el indicador de escaneo de uso " +"para cada adjunto indexado. Los metadatos del archivo en el índice se " +"conservan. Se programa un escaneo en segundo plano inmediatamente después." + +#: includes/Admin/ToolsPage.php:643 +msgid "This will delete all usage data. Continue?" +msgstr "Esto eliminará todos los datos de uso. ¿Continuar?" + +#: includes/Admin/ToolsPage.php:647 +msgid "Re-index media library" +msgstr "Re-indexar la biblioteca de medios" + +#: includes/Admin/ToolsPage.php:648 +msgid "" +"Deletes all index and usage entries, then schedules a full fresh re-index. " +"External scan results are not affected. Useful if many media files have been " +"added or removed." +msgstr "" +"Elimina todas las entradas del índice y de uso, luego programa una re-" +"indexación completa. Los resultados del escaneo externo no se ven afectados. " +"Útil si se han añadido o eliminado muchos archivos multimedia." + +#: includes/Admin/ToolsPage.php:650 +msgid "This will delete all index and usage data. Continue?" +msgstr "Esto eliminará todos los datos del índice y de uso. ¿Continuar?" + +#: includes/Admin/ToolsPage.php:654 +msgid "Purge external data" +msgstr "Purgar datos externos" + +#: includes/Admin/ToolsPage.php:655 +msgid "" +"Deletes all external scan results and resets every attachment's external " +"status to pending. Use this before switching API providers or to force a " +"complete re-scan." +msgstr "" +"Elimina todos los resultados del escaneo externo y restablece el estado " +"externo de cada adjunto a pendiente. Úsalo antes de cambiar de proveedores " +"de API o para forzar un escaneo completo." + +#: includes/Admin/ToolsPage.php:657 +msgid "This will delete all external scan results. Continue?" +msgstr "Esto eliminará todos los resultados del escaneo externo. ¿Continuar?" + +#: includes/Admin/ToolsPage.php:661 +msgid "Reset all data" +msgstr "Restablecer todos los datos" + +#: includes/Admin/ToolsPage.php:662 +msgid "" +"Truncates all three plugin tables (index, usage, external results) and " +"schedules a fresh indexing run. Use this to start completely from scratch." +msgstr "" +"Vacía las tres tablas del plugin (índice, uso, resultados externos) y " +"programa una nueva indexación. Úsalo para empezar completamente desde cero." + +#: includes/Admin/ToolsPage.php:664 +msgid "" +"This will permanently delete ALL plugin data and cannot be undone. Are you " +"absolutely sure?" +msgstr "" +"Esto eliminará TODOS los datos del plugin de forma permanente y no se puede " +"deshacer. ¿Estás absolutamente seguro?" + +#: includes/Admin/ToolsPage.php:675 +msgid "Destructive operations" +msgstr "Operaciones destructivas" + +#: includes/Admin/ToolsPage.php:678 +msgid "" +"These operations permanently delete plugin data. Intended for forced " +"rescans, troubleshooting, or starting fresh. A confirmation prompt will " +"appear before any action is taken." +msgstr "" +"Estas operaciones eliminan datos del plugin de forma permanente. Pensadas " +"para forzar re-escaneos, solucionar problemas o empezar desde cero. " +"Aparecerá una confirmación antes de ejecutar cualquier acción." + +#: includes/Admin/ToolsPage.php:726 +msgid "Index media files" +msgstr "Indexar archivos multimedia" + +#: includes/Admin/ToolsPage.php:727 +msgid "" +"Discovers unindexed images and registers them in the plugin index. Run this " +"first." +msgstr "" +"Descubre imágenes no indexadas y las registra en el índice del plugin. " +"Ejecútalo primero." + +#: includes/Admin/ToolsPage.php:731 +msgid "Scan usage" +msgstr "Escanear uso" + +#: includes/Admin/ToolsPage.php:732 +msgid "" +"Checks every indexed image for usage across posts, pages, and custom fields." +msgstr "" +"Comprueba el uso de cada imagen indexada en entradas, páginas y campos " +"personalizados." + +#: includes/Admin/ToolsPage.php:737 +msgid "" +"Sends images to external providers for reverse-image search. Requires API " +"keys in Settings." +msgstr "" +"Envía imágenes a proveedores externos para búsqueda inversa de imágenes. " +"Requiere claves de API en Ajustes." + +#: includes/Admin/ToolsPage.php:754 +msgid "Run now" +msgstr "Ejecutar ahora" + +#: includes/Admin/ToolsPage.php:757 +msgid "Stop" +msgstr "Detener" + #. translators: %s: DB version number #: includes/CLI/Command.php:49 #, php-format @@ -495,7 +933,11 @@ msgstr "Ejecutando escaneos externos…" msgid "External scan complete. %d attachments processed." msgstr "Escaneo externo completo. %d adjuntos procesados." -#: includes/Core/Plugin.php:112 includes/Core/Plugin.php:113 +#: includes/Core/Plugin.php:122 includes/Core/Plugin.php:123 +msgid "Tools" +msgstr "Herramientas" + +#: includes/Core/Plugin.php:135 includes/Core/Plugin.php:136 msgid "Settings" msgstr "Ajustes" @@ -523,14 +965,15 @@ msgstr "Último escaneo (interno)" msgid "Last Scanned (external)" msgstr "Último escaneo (externo)" -#: includes/Privacy/DataExporter.php:303 -msgid "Provider" -msgstr "Proveedor" - -#: includes/Privacy/DataExporter.php:307 -msgid "Match Count" -msgstr "Número de coincidencias" - #: includes/Privacy/DataExporter.php:311 msgid "Scanned At" msgstr "Escaneado el" + +#~ msgid "https://robotstxt.es/plugins/media-audit/" +#~ msgstr "https://robotstxt.es/plugins/media-audit/" + +#~ msgid "Javier Casares" +#~ msgstr "Javier Casares" + +#~ msgid "https://javiercasares.com" +#~ msgstr "https://javiercasares.com" diff --git a/languages/robotstxt-mediaaudit.pot b/languages/robotstxt-mediaaudit.pot index 3109190..5510e77 100644 --- a/languages/robotstxt-mediaaudit.pot +++ b/languages/robotstxt-mediaaudit.pot @@ -1,15 +1,15 @@ -# Copyright (C) 2026 Javier Casares +# Copyright (C) 2026 ROBOTSTXT # This file is distributed under the GPL-3.0-or-later. msgid "" msgstr "" -"Project-Id-Version: Media Audit (by ROBOTSTXT) 1.0.0\n" +"Project-Id-Version: Media Audit (by ROBOTSTXT) 1.1.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-02T06:06:40+00:00\n" +"POT-Creation-Date: 2026-05-02T07:36:54+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" @@ -21,7 +21,7 @@ msgstr "" #. Plugin URI of the plugin #: robotstxt-mediaaudit.php -msgid "https://robotstxt.es/plugins/media-audit/" +msgid "https://git.robotstxt.es/ROBOTSTXT/robotstxt-mediaaudit" msgstr "" #. Description of the plugin @@ -31,215 +31,243 @@ msgstr "" #. Author of the plugin #: robotstxt-mediaaudit.php -msgid "Javier Casares" +msgid "ROBOTSTXT" msgstr "" #. Author URI of the plugin #: robotstxt-mediaaudit.php -msgid "https://javiercasares.com" -msgstr "" - -#: includes/Admin/AuditPage.php:112 -msgid "Loading…" -msgstr "" - -#: includes/Admin/AuditPage.php:113 -msgid "Error loading details." +msgid "https://www.robotstxt.es/" msgstr "" #: includes/Admin/AuditPage.php:114 -#: includes/Admin/AuditPage.php:190 -msgid "This image is not used in any post." +msgid "Loading…" msgstr "" #: includes/Admin/AuditPage.php:115 -#: includes/Admin/AuditPage.php:152 +msgid "Error loading details." +msgstr "" + +#: includes/Admin/AuditPage.php:116 +#: includes/Admin/AuditPage.php:192 +msgid "This image is not used in any post." +msgstr "" + +#: includes/Admin/AuditPage.php:117 +#: includes/Admin/AuditPage.php:154 msgid "Usage Details" msgstr "" -#: includes/Admin/AuditPage.php:131 +#: includes/Admin/AuditPage.php:133 #: includes/Admin/Settings.php:325 +#: includes/Admin/ToolsPage.php:245 msgid "You do not have permission to access this page." msgstr "" -#: includes/Admin/AuditPage.php:138 -#: includes/Core/Plugin.php:89 -#: includes/Core/Plugin.php:90 -#: includes/Core/Plugin.php:103 -#: includes/Core/Plugin.php:104 +#: includes/Admin/AuditPage.php:140 +#: includes/Core/Plugin.php:99 +#: includes/Core/Plugin.php:100 +#: includes/Core/Plugin.php:113 +#: includes/Core/Plugin.php:114 #: includes/Privacy/DataEraser.php:33 #: includes/Privacy/DataExporter.php:45 msgid "Media Audit" msgstr "" -#: includes/Admin/AuditPage.php:144 +#: includes/Admin/AuditPage.php:146 msgid "Search" msgstr "" -#: includes/Admin/AuditPage.php:153 +#: includes/Admin/AuditPage.php:155 msgid "Close" msgstr "" -#: includes/Admin/AuditPage.php:171 -#: includes/Admin/AuditPage.php:459 -#: includes/Admin/AuditPage.php:510 -#: includes/Admin/AuditPage.php:542 +#: includes/Admin/AuditPage.php:173 +#: includes/Admin/AuditPage.php:461 +#: includes/Admin/AuditPage.php:515 +#: includes/Admin/AuditPage.php:648 +#: includes/Admin/AuditPage.php:680 +#: includes/Admin/ToolsPage.php:105 +#: includes/Admin/ToolsPage.php:161 msgid "Insufficient permissions." msgstr "" -#: includes/Admin/AuditPage.php:176 +#: includes/Admin/AuditPage.php:178 msgid "Invalid attachment ID." msgstr "" -#: includes/Admin/AuditPage.php:184 +#: includes/Admin/AuditPage.php:186 msgid "Featured Image" msgstr "" -#: includes/Admin/AuditPage.php:185 +#: includes/Admin/AuditPage.php:187 msgid "Post Content" msgstr "" -#: includes/Admin/AuditPage.php:186 +#: includes/Admin/AuditPage.php:188 msgid "Custom Field" msgstr "" -#: includes/Admin/AuditPage.php:194 +#: includes/Admin/AuditPage.php:196 msgid "Post" msgstr "" -#: includes/Admin/AuditPage.php:195 +#: includes/Admin/AuditPage.php:197 msgid "Type" msgstr "" -#: includes/Admin/AuditPage.php:196 +#: includes/Admin/AuditPage.php:198 msgid "Context" msgstr "" -#: includes/Admin/AuditPage.php:197 +#: includes/Admin/AuditPage.php:199 msgid "Status" msgstr "" -#: includes/Admin/AuditPage.php:240 +#: includes/Admin/AuditPage.php:242 msgid "Internal Usage" msgstr "" -#: includes/Admin/AuditPage.php:288 +#: includes/Admin/AuditPage.php:290 msgid "External Scan Results" msgstr "" #. translators: %d: number of external matches -#: includes/Admin/AuditPage.php:312 +#: includes/Admin/AuditPage.php:314 #, php-format msgid "%d match" msgid_plural "%d matches" msgstr[0] "" msgstr[1] "" -#: includes/Admin/AuditPage.php:317 +#: includes/Admin/AuditPage.php:319 msgid "No matches" msgstr "" -#: includes/Admin/AuditPage.php:328 +#: includes/Admin/AuditPage.php:330 msgid "Domain" msgstr "" -#: includes/Admin/AuditPage.php:329 +#: includes/Admin/AuditPage.php:331 msgid "Occurrences" msgstr "" -#: includes/Admin/AuditPage.php:350 +#: includes/Admin/AuditPage.php:352 msgid "Consensus Domains" msgstr "" #. translators: %d: number of providers that agree -#: includes/Admin/AuditPage.php:356 +#: includes/Admin/AuditPage.php:358 #, php-format msgid "%d provider" msgid_plural "%d providers" msgstr[0] "" msgstr[1] "" -#: includes/Admin/AuditPage.php:388 +#: includes/Admin/AuditPage.php:390 msgid "In Index" msgstr "" #. translators: %d: count of images not yet indexed -#: includes/Admin/AuditPage.php:392 +#: includes/Admin/AuditPage.php:394 #, php-format msgid "%d not yet indexed" msgstr "" -#: includes/Admin/AuditPage.php:395 +#: includes/Admin/AuditPage.php:397 msgid "All indexed" msgstr "" -#: includes/Admin/AuditPage.php:400 -#: includes/Admin/MediaListTable.php:275 +#: includes/Admin/AuditPage.php:402 +#: includes/Admin/MediaListTable.php:278 msgid "Scanned" msgstr "" #. translators: %d: percentage -#: includes/Admin/AuditPage.php:403 +#: includes/Admin/AuditPage.php:405 #, php-format msgid "%d%% of index" msgstr "" -#: includes/Admin/AuditPage.php:410 +#: includes/Admin/AuditPage.php:412 msgid "Used" msgstr "" -#: includes/Admin/AuditPage.php:416 -#: includes/Admin/MediaListTable.php:200 +#: includes/Admin/AuditPage.php:418 +#: includes/Admin/MediaListTable.php:203 msgid "Unused" msgstr "" -#: includes/Admin/AuditPage.php:423 +#: includes/Admin/AuditPage.php:425 msgid "External Matches" msgstr "" -#: includes/Admin/AuditPage.php:473 +#: includes/Admin/AuditPage.php:475 +#: includes/Admin/AuditPage.php:529 msgid "Could not open output stream." msgstr "" -#: includes/Admin/AuditPage.php:482 +#: includes/Admin/AuditPage.php:484 +#: includes/Admin/AuditPage.php:537 #: includes/Privacy/DataExporter.php:257 #: includes/Privacy/DataExporter.php:299 msgid "Attachment ID" msgstr "" -#: includes/Admin/AuditPage.php:483 +#: includes/Admin/AuditPage.php:485 +#: includes/Admin/AuditPage.php:538 #: includes/Admin/MediaListTable.php:52 #: includes/Privacy/DataExporter.php:261 msgid "Filename" msgstr "" -#: includes/Admin/AuditPage.php:484 +#: includes/Admin/AuditPage.php:486 +#: includes/Admin/AuditPage.php:539 #: includes/Privacy/DataExporter.php:265 msgid "File URL" msgstr "" -#: includes/Admin/AuditPage.php:485 +#: includes/Admin/AuditPage.php:487 msgid "MIME Type" msgstr "" -#: includes/Admin/AuditPage.php:486 +#: includes/Admin/AuditPage.php:488 msgid "File Size (bytes)" msgstr "" -#: includes/Admin/AuditPage.php:487 +#: includes/Admin/AuditPage.php:489 +#: includes/Admin/AuditPage.php:540 #: includes/Admin/MediaListTable.php:56 msgid "External Status" msgstr "" -#: includes/Admin/AuditPage.php:488 +#: includes/Admin/AuditPage.php:490 #: includes/Admin/MediaListTable.php:54 msgid "Usage Count" msgstr "" -#: includes/Admin/AuditPage.php:489 +#: includes/Admin/AuditPage.php:491 msgid "Used In (post titles)" msgstr "" +#: includes/Admin/AuditPage.php:541 +#: includes/Admin/MediaListTable.php:57 +msgid "Last Scanned" +msgstr "" + +#: includes/Admin/AuditPage.php:542 +#: includes/Privacy/DataExporter.php:303 +msgid "Provider" +msgstr "" + +#: includes/Admin/AuditPage.php:543 +#: includes/Privacy/DataExporter.php:307 +msgid "Match Count" +msgstr "" + +#: includes/Admin/AuditPage.php:544 +msgid "Top Domains" +msgstr "" + #: includes/Admin/MediaListTable.php:51 msgid "Thumbnail" msgstr "" @@ -252,73 +280,86 @@ msgstr "" msgid "Usages" msgstr "" -#: includes/Admin/MediaListTable.php:79 +#: includes/Admin/MediaListTable.php:81 msgid "Run External Scan" msgstr "" -#: includes/Admin/MediaListTable.php:80 +#: includes/Admin/MediaListTable.php:82 msgid "Purge External Data" msgstr "" -#: includes/Admin/MediaListTable.php:81 +#: includes/Admin/MediaListTable.php:83 msgid "Export CSV" msgstr "" -#: includes/Admin/MediaListTable.php:165 +#: includes/Admin/MediaListTable.php:84 +msgid "Export External Results CSV" +msgstr "" + +#: includes/Admin/MediaListTable.php:168 msgid "Edit" msgstr "" -#: includes/Admin/MediaListTable.php:170 +#: includes/Admin/MediaListTable.php:173 msgid "View Details" msgstr "" -#: includes/Admin/MediaListTable.php:236 +#: includes/Admin/MediaListTable.php:239 msgid "(no title)" msgstr "" #. translators: %d: number of additional usages -#: includes/Admin/MediaListTable.php:251 +#: includes/Admin/MediaListTable.php:254 #, php-format msgid "+ %d more…" msgstr "" -#: includes/Admin/MediaListTable.php:273 +#: includes/Admin/MediaListTable.php:276 +#: includes/Admin/ToolsPage.php:580 +#: includes/Admin/ToolsPage.php:581 +#: includes/Admin/ToolsPage.php:582 msgid "Pending" msgstr "" -#: includes/Admin/MediaListTable.php:274 +#: includes/Admin/MediaListTable.php:277 msgid "Queued" msgstr "" -#: includes/Admin/MediaListTable.php:276 +#: includes/Admin/MediaListTable.php:279 msgid "Matches Found" msgstr "" -#: includes/Admin/MediaListTable.php:277 +#: includes/Admin/MediaListTable.php:280 msgid "Error" msgstr "" -#: includes/Admin/MediaListTable.php:305 +#: includes/Admin/MediaListTable.php:308 msgid "View Results" msgstr "" -#: includes/Admin/MediaListTable.php:336 +#. translators: %s: human-readable time difference, e.g. "3 days" +#: includes/Admin/MediaListTable.php:350 +#, php-format +msgid "%s ago" +msgstr "" + +#: includes/Admin/MediaListTable.php:380 msgid "All statuses" msgstr "" -#: includes/Admin/MediaListTable.php:350 +#: includes/Admin/MediaListTable.php:394 msgid "All post types" msgstr "" -#: includes/Admin/MediaListTable.php:365 +#: includes/Admin/MediaListTable.php:409 msgid "Unused only" msgstr "" -#: includes/Admin/MediaListTable.php:368 +#: includes/Admin/MediaListTable.php:412 msgid "Filter" msgstr "" -#: includes/Admin/MediaListTable.php:378 +#: includes/Admin/MediaListTable.php:422 msgid "No indexed attachments found. Run wp mra scan-internal to populate the index." msgstr "" @@ -423,6 +464,325 @@ msgstr "" msgid "Maximum API requests per minute per provider (1–60). Default: 10." msgstr "" +#: includes/Admin/ToolsPage.php:85 +msgid "Done." +msgstr "" + +#: includes/Admin/ToolsPage.php:86 +msgid "Stopped." +msgstr "" + +#: includes/Admin/ToolsPage.php:87 +msgid "An error occurred." +msgstr "" + +#: includes/Admin/ToolsPage.php:139 +msgid "Invalid batch type." +msgstr "" + +#: includes/Admin/ToolsPage.php:256 +msgid "System Status" +msgstr "" + +#: includes/Admin/ToolsPage.php:259 +msgid "Operations" +msgstr "" + +#: includes/Admin/ToolsPage.php:262 +msgid "Direct scan runner" +msgstr "" + +#: includes/Admin/ToolsPage.php:264 +msgid "Run scan batches directly from your browser without relying on background jobs. Useful when WP-Cron is disabled or Action Scheduler is not running. Keep this tab open while processing." +msgstr "" + +#: includes/Admin/ToolsPage.php:300 +msgid "not installed" +msgstr "" + +#: includes/Admin/ToolsPage.php:358 +msgid "Internal scan scheduled." +msgstr "" + +#: includes/Admin/ToolsPage.php:359 +msgid "External scan scheduled." +msgstr "" + +#: includes/Admin/ToolsPage.php:360 +msgid "Failed scans re-queued. Background scan scheduled." +msgstr "" + +#: includes/Admin/ToolsPage.php:361 +msgid "Usage data cleared. Background scan scheduled." +msgstr "" + +#: includes/Admin/ToolsPage.php:362 +msgid "Index cleared. Fresh indexing scheduled." +msgstr "" + +#: includes/Admin/ToolsPage.php:363 +msgid "External scan data purged." +msgstr "" + +#: includes/Admin/ToolsPage.php:364 +msgid "All data reset. Fresh indexing scheduled." +msgstr "" + +#: includes/Admin/ToolsPage.php:367 +msgid "Operation completed." +msgstr "" + +#: includes/Admin/ToolsPage.php:411 +msgid "Database schema" +msgstr "" + +#. translators: %s: version number +#: includes/Admin/ToolsPage.php:417 +#, php-format +msgid "v%s – up to date" +msgstr "" + +#. translators: 1: stored version, 2: expected version +#: includes/Admin/ToolsPage.php:423 +#, php-format +msgid "Installed: %1$s — Required: %2$s" +msgstr "" + +#: includes/Admin/ToolsPage.php:432 +msgid "Deactivate and reactivate the plugin to apply pending migrations." +msgstr "" + +#: includes/Admin/ToolsPage.php:439 +msgid "Action Scheduler" +msgstr "" + +#: includes/Admin/ToolsPage.php:442 +msgid "Available" +msgstr "" + +#: includes/Admin/ToolsPage.php:444 +msgid "Not available" +msgstr "" + +#: includes/Admin/ToolsPage.php:449 +msgid "Install and activate the Action Scheduler plugin. Background scanning will not work without it." +msgstr "" + +#: includes/Admin/ToolsPage.php:456 +msgid "WP-Cron" +msgstr "" + +#: includes/Admin/ToolsPage.php:459 +msgid "Disabled (DISABLE_WP_CRON)" +msgstr "" + +#: includes/Admin/ToolsPage.php:461 +msgid "Enabled" +msgstr "" + +#: includes/Admin/ToolsPage.php:466 +msgid "Background jobs will not fire automatically. Trigger the cron externally or use WP-CLI:" +msgstr "" + +#: includes/Admin/ToolsPage.php:474 +msgid "Internal index" +msgstr "" + +#. translators: 1: indexed count, 2: total images +#: includes/Admin/ToolsPage.php:479 +#, php-format +msgid "%1$s / %2$s images indexed" +msgstr "" + +#. translators: %s: formatted count +#: includes/Admin/ToolsPage.php:490 +#, php-format +msgid "%s image not yet indexed." +msgid_plural "%s images not yet indexed." +msgstr[0] "" +msgstr[1] "" + +#: includes/Admin/ToolsPage.php:494 +msgid "Schedule an internal scan below." +msgstr "" + +#: includes/Admin/ToolsPage.php:502 +msgid "Usage scan" +msgstr "" + +#. translators: 1: scanned count, 2: total indexed +#: includes/Admin/ToolsPage.php:507 +#, php-format +msgid "%1$s / %2$s scanned for usage" +msgstr "" + +#. translators: %s: formatted count +#: includes/Admin/ToolsPage.php:518 +#, php-format +msgid "%s pending." +msgid_plural "%s pending." +msgstr[0] "" +msgstr[1] "" + +#: includes/Admin/ToolsPage.php:539 +#: includes/Admin/ToolsPage.php:736 +msgid "External scan" +msgstr "" + +#. translators: 1: scanned 2: matches 3: queued 4: pending 5: errors +#: includes/Admin/ToolsPage.php:544 +#, php-format +msgid "%1$s scanned · %2$s matches · %3$s queued · %4$s pending · %5$s errors" +msgstr "" + +#: includes/Admin/ToolsPage.php:555 +msgid "Some scans failed. Check that API keys are configured correctly in Settings." +msgstr "" + +#: includes/Admin/ToolsPage.php:557 +msgid "Schedule an external scan below to process pending items." +msgstr "" + +#: includes/Admin/ToolsPage.php:575 +msgid "Scheduled jobs" +msgstr "" + +#: includes/Admin/ToolsPage.php:578 +msgid "Action Scheduler not available." +msgstr "" + +#: includes/Admin/ToolsPage.php:580 +msgid "Index:" +msgstr "" + +#: includes/Admin/ToolsPage.php:581 +msgid "Usage:" +msgstr "" + +#: includes/Admin/ToolsPage.php:582 +msgid "External:" +msgstr "" + +#: includes/Admin/ToolsPage.php:587 +msgid "There is work to do but no background job is scheduled. Use the operations below." +msgstr "" + +#: includes/Admin/ToolsPage.php:589 +msgid "WP-Cron is disabled. Jobs will not fire automatically." +msgstr "" + +#: includes/Admin/ToolsPage.php:599 +msgid "WP-CLI commands to process scans manually:" +msgstr "" + +#: includes/Admin/ToolsPage.php:616 +msgid "Schedule internal scan" +msgstr "" + +#: includes/Admin/ToolsPage.php:617 +msgid "Queue a background job to index all unindexed media files and scan their usage across posts, pages, and custom fields. Safe to run at any time — will not re-process completed work." +msgstr "" + +#: includes/Admin/ToolsPage.php:623 +msgid "Schedule external scan" +msgstr "" + +#: includes/Admin/ToolsPage.php:624 +msgid "Mark all pending attachments as queued and launch a background reverse-image-search via the configured providers. Requires API keys in Settings." +msgstr "" + +#: includes/Admin/ToolsPage.php:630 +msgid "Requeue scan errors" +msgstr "" + +#: includes/Admin/ToolsPage.php:631 +msgid "Re-queues only the attachments that failed with an error on the last external scan run. Does not affect already-scanned items. Useful to retry after fixing API key issues." +msgstr "" + +#: includes/Admin/ToolsPage.php:640 +msgid "Reset usage data" +msgstr "" + +#: includes/Admin/ToolsPage.php:641 +msgid "Clears all usage rows and resets the usage-scan flag for every indexed attachment. File metadata in the index is preserved. A background usage scan is scheduled immediately after." +msgstr "" + +#: includes/Admin/ToolsPage.php:643 +msgid "This will delete all usage data. Continue?" +msgstr "" + +#: includes/Admin/ToolsPage.php:647 +msgid "Re-index media library" +msgstr "" + +#: includes/Admin/ToolsPage.php:648 +msgid "Deletes all index and usage entries, then schedules a full fresh re-index. External scan results are not affected. Useful if many media files have been added or removed." +msgstr "" + +#: includes/Admin/ToolsPage.php:650 +msgid "This will delete all index and usage data. Continue?" +msgstr "" + +#: includes/Admin/ToolsPage.php:654 +msgid "Purge external data" +msgstr "" + +#: includes/Admin/ToolsPage.php:655 +msgid "Deletes all external scan results and resets every attachment's external status to pending. Use this before switching API providers or to force a complete re-scan." +msgstr "" + +#: includes/Admin/ToolsPage.php:657 +msgid "This will delete all external scan results. Continue?" +msgstr "" + +#: includes/Admin/ToolsPage.php:661 +msgid "Reset all data" +msgstr "" + +#: includes/Admin/ToolsPage.php:662 +msgid "Truncates all three plugin tables (index, usage, external results) and schedules a fresh indexing run. Use this to start completely from scratch." +msgstr "" + +#: includes/Admin/ToolsPage.php:664 +msgid "This will permanently delete ALL plugin data and cannot be undone. Are you absolutely sure?" +msgstr "" + +#: includes/Admin/ToolsPage.php:675 +msgid "Destructive operations" +msgstr "" + +#: includes/Admin/ToolsPage.php:678 +msgid "These operations permanently delete plugin data. Intended for forced rescans, troubleshooting, or starting fresh. A confirmation prompt will appear before any action is taken." +msgstr "" + +#: includes/Admin/ToolsPage.php:726 +msgid "Index media files" +msgstr "" + +#: includes/Admin/ToolsPage.php:727 +msgid "Discovers unindexed images and registers them in the plugin index. Run this first." +msgstr "" + +#: includes/Admin/ToolsPage.php:731 +msgid "Scan usage" +msgstr "" + +#: includes/Admin/ToolsPage.php:732 +msgid "Checks every indexed image for usage across posts, pages, and custom fields." +msgstr "" + +#: includes/Admin/ToolsPage.php:737 +msgid "Sends images to external providers for reverse-image search. Requires API keys in Settings." +msgstr "" + +#: includes/Admin/ToolsPage.php:754 +msgid "Run now" +msgstr "" + +#: includes/Admin/ToolsPage.php:757 +msgid "Stop" +msgstr "" + #. translators: %s: DB version number #: includes/CLI/Command.php:49 #, php-format @@ -484,8 +844,13 @@ msgstr "" msgid "External scan complete. %d attachments processed." msgstr "" -#: includes/Core/Plugin.php:112 -#: includes/Core/Plugin.php:113 +#: includes/Core/Plugin.php:122 +#: includes/Core/Plugin.php:123 +msgid "Tools" +msgstr "" + +#: includes/Core/Plugin.php:135 +#: includes/Core/Plugin.php:136 msgid "Settings" msgstr "" @@ -513,14 +878,6 @@ msgstr "" msgid "Last Scanned (external)" msgstr "" -#: includes/Privacy/DataExporter.php:303 -msgid "Provider" -msgstr "" - -#: includes/Privacy/DataExporter.php:307 -msgid "Match Count" -msgstr "" - #: includes/Privacy/DataExporter.php:311 msgid "Scanned At" msgstr "" diff --git a/messages.mo b/messages.mo deleted file mode 100644 index 3c3d11f..0000000 Binary files a/messages.mo and /dev/null differ diff --git a/readme.txt b/readme.txt index 646b45e..b9c4a00 100644 --- a/readme.txt +++ b/readme.txt @@ -1,11 +1,11 @@ === Media Audit (by ROBOTSTXT) === -Contributors: javiercasares +Contributors: javiercasares, robotstxt Tags: media, copyright, images, reverse image search, media library Requires at least: 6.8 Tested up to: 7.0 Requires PHP: 8.2 Requires Plugins: action-scheduler -Stable tag: 1.0.0 +Stable tag: 1.2.0 License: GPL-3.0-or-later License URI: https://www.gnu.org/licenses/gpl-3.0.txt @@ -98,6 +98,17 @@ By default, no. Enable **Settings → Delete data on uninstall** if you want all Only the 3 latest versions. The full changelog is in [changelog.txt](changelog.txt). += 1.2.0 = + +* Added browser-based AJAX scan runner in the Tools page (runs without WP-Cron or Action Scheduler). +* Added "Requeue scan errors" operation to retry failed external scans after fixing API key issues. +* Added "Last Scanned" column in the audit list, sortable with NULLs-last ordering. +* Added bulk action to export external scan results as a CSV file. + += 1.1.0 = + +* Updated plugin URI, author, and contributor metadata. + = 1.0.0 = * First stable release. Internal usage scanning, external reverse image search (Google Vision + TinEye), consensus detection, WP-CLI commands, GDPR privacy tools, and Spanish/Catalan translations. diff --git a/robotstxt-mediaaudit.php b/robotstxt-mediaaudit.php index 589cab1..9874634 100644 --- a/robotstxt-mediaaudit.php +++ b/robotstxt-mediaaudit.php @@ -1,15 +1,16 @@ $baseDir . '/includes/Admin/AuditPage.php', 'MediaRightsAudit\\Admin\\MediaListTable' => $baseDir . '/includes/Admin/MediaListTable.php', 'MediaRightsAudit\\Admin\\Settings' => $baseDir . '/includes/Admin/Settings.php', + 'MediaRightsAudit\\Admin\\ToolsPage' => $baseDir . '/includes/Admin/ToolsPage.php', 'MediaRightsAudit\\CLI\\Command' => $baseDir . '/includes/CLI/Command.php', 'MediaRightsAudit\\Core\\Activator' => $baseDir . '/includes/Core/Activator.php', 'MediaRightsAudit\\Core\\Database' => $baseDir . '/includes/Core/Database.php', diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php index 7b57174..5fcb1d6 100644 --- a/vendor/composer/autoload_static.php +++ b/vendor/composer/autoload_static.php @@ -25,6 +25,7 @@ class ComposerStaticInit957728ab3efa005f456e5f9df13a19d2 'MediaRightsAudit\\Admin\\AuditPage' => __DIR__ . '/../..' . '/includes/Admin/AuditPage.php', 'MediaRightsAudit\\Admin\\MediaListTable' => __DIR__ . '/../..' . '/includes/Admin/MediaListTable.php', 'MediaRightsAudit\\Admin\\Settings' => __DIR__ . '/../..' . '/includes/Admin/Settings.php', + 'MediaRightsAudit\\Admin\\ToolsPage' => __DIR__ . '/../..' . '/includes/Admin/ToolsPage.php', 'MediaRightsAudit\\CLI\\Command' => __DIR__ . '/../..' . '/includes/CLI/Command.php', 'MediaRightsAudit\\Core\\Activator' => __DIR__ . '/../..' . '/includes/Core/Activator.php', 'MediaRightsAudit\\Core\\Database' => __DIR__ . '/../..' . '/includes/Core/Database.php',