v1.3.0
This commit is contained in:
parent
650e69349f
commit
672d0f295f
11 changed files with 438 additions and 73 deletions
|
|
@ -1,5 +1,33 @@
|
||||||
== Changelog ==
|
== Changelog ==
|
||||||
|
|
||||||
|
= 1.3.0 =
|
||||||
|
|
||||||
|
_Release date: 2026-05-05_
|
||||||
|
|
||||||
|
**Added**
|
||||||
|
|
||||||
|
* Per-provider scan tracking table (`mra_provider_status`) with composite primary key `(attachment_id, provider)`. Each API provider now has an independent row tracking its scan status, so adding a new provider only queues the missing scans rather than resetting everything.
|
||||||
|
* "Sync providers" operation in the Tools page: inserts rows for any provider that has no existing entry for a given attachment, then schedules a background scan batch for the new work only.
|
||||||
|
|
||||||
|
**Changed**
|
||||||
|
|
||||||
|
* `mra_external_results.provider` column changed from `ENUM('google_vision','tineye')` to `varchar(100)` to allow third-party providers registered via the `mra/external/providers` filter to store results.
|
||||||
|
* External scan batch processing now reads from `mra_provider_status` and processes each provider independently. Aggregate `external_status` on `mra_media_index` is recomputed after each scan and used only for display.
|
||||||
|
* `ExternalScanner::get_pending_count()` counts queued rows from `mra_provider_status`.
|
||||||
|
* DB schema version bumped to `1.1.0`. Migration runs automatically on admin_init when updating from an older version; existing data is backfilled into `mra_provider_status`.
|
||||||
|
|
||||||
|
**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.2.0 =
|
= 1.2.0 =
|
||||||
|
|
||||||
_Release date: 2026-05-02_
|
_Release date: 2026-05-02_
|
||||||
|
|
|
||||||
|
|
@ -203,6 +203,10 @@ class ToolsPage {
|
||||||
$count = ExternalScanner::requeue_errors();
|
$count = ExternalScanner::requeue_errors();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'sync_providers':
|
||||||
|
$count = ExternalScanner::sync_providers();
|
||||||
|
break;
|
||||||
|
|
||||||
case 'rescan_usage':
|
case 'rescan_usage':
|
||||||
$count = UsageScanner::reset_all();
|
$count = UsageScanner::reset_all();
|
||||||
UsageScanner::schedule();
|
UsageScanner::schedule();
|
||||||
|
|
@ -358,6 +362,7 @@ class ToolsPage {
|
||||||
'schedule_internal' => __( 'Internal scan scheduled.', 'robotstxt-mediaaudit' ),
|
'schedule_internal' => __( 'Internal scan scheduled.', 'robotstxt-mediaaudit' ),
|
||||||
'schedule_external' => __( 'External scan scheduled.', 'robotstxt-mediaaudit' ),
|
'schedule_external' => __( 'External scan scheduled.', 'robotstxt-mediaaudit' ),
|
||||||
'requeue_errors' => __( 'Failed scans re-queued. Background scan scheduled.', 'robotstxt-mediaaudit' ),
|
'requeue_errors' => __( 'Failed scans re-queued. Background scan scheduled.', 'robotstxt-mediaaudit' ),
|
||||||
|
'sync_providers' => __( 'Provider registry synced. New scans scheduled.', 'robotstxt-mediaaudit' ),
|
||||||
'rescan_usage' => __( 'Usage data cleared. Background scan scheduled.', 'robotstxt-mediaaudit' ),
|
'rescan_usage' => __( 'Usage data cleared. Background scan scheduled.', 'robotstxt-mediaaudit' ),
|
||||||
'reindex_all' => __( 'Index cleared. Fresh indexing scheduled.', 'robotstxt-mediaaudit' ),
|
'reindex_all' => __( 'Index cleared. Fresh indexing scheduled.', 'robotstxt-mediaaudit' ),
|
||||||
'purge_external' => __( 'External scan data purged.', 'robotstxt-mediaaudit' ),
|
'purge_external' => __( 'External scan data purged.', 'robotstxt-mediaaudit' ),
|
||||||
|
|
@ -632,6 +637,13 @@ class ToolsPage {
|
||||||
'class' => 'button',
|
'class' => 'button',
|
||||||
'confirm' => '',
|
'confirm' => '',
|
||||||
),
|
),
|
||||||
|
array(
|
||||||
|
'op' => 'sync_providers',
|
||||||
|
'label' => __( 'Sync provider registry', 'robotstxt-mediaaudit' ),
|
||||||
|
'desc' => __( 'Creates scan entries for any newly registered external providers. Run this after adding a new provider via the mra/external/providers filter. Already-scanned attachments are not affected.', 'robotstxt-mediaaudit' ),
|
||||||
|
'class' => 'button',
|
||||||
|
'confirm' => '',
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
$destructive = array(
|
$destructive = array(
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ class Database {
|
||||||
public static function create_tables(): void {
|
public static function create_tables(): void {
|
||||||
self::migration_100();
|
self::migration_100();
|
||||||
self::migration_101();
|
self::migration_101();
|
||||||
|
self::migration_110();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -43,6 +44,10 @@ class Database {
|
||||||
if ( version_compare( $current, '1.0.1', '<' ) ) {
|
if ( version_compare( $current, '1.0.1', '<' ) ) {
|
||||||
self::migration_101();
|
self::migration_101();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ( version_compare( $current, '1.1.0', '<' ) ) {
|
||||||
|
self::migration_110();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -52,6 +57,7 @@ class Database {
|
||||||
*/
|
*/
|
||||||
public static function drop_tables(): void {
|
public static function drop_tables(): void {
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
|
$wpdb->query( "DROP TABLE IF EXISTS `{$wpdb->prefix}mra_provider_status`" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery
|
||||||
$wpdb->query( "DROP TABLE IF EXISTS `{$wpdb->prefix}mra_external_results`" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery
|
$wpdb->query( "DROP TABLE IF EXISTS `{$wpdb->prefix}mra_external_results`" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery
|
||||||
$wpdb->query( "DROP TABLE IF EXISTS `{$wpdb->prefix}mra_media_usage`" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery
|
$wpdb->query( "DROP TABLE IF EXISTS `{$wpdb->prefix}mra_media_usage`" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery
|
||||||
$wpdb->query( "DROP TABLE IF EXISTS `{$wpdb->prefix}mra_media_index`" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery
|
$wpdb->query( "DROP TABLE IF EXISTS `{$wpdb->prefix}mra_media_index`" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery
|
||||||
|
|
@ -68,6 +74,7 @@ class Database {
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
|
|
||||||
// phpcs:disable WordPress.DB.DirectDatabaseQuery
|
// phpcs:disable WordPress.DB.DirectDatabaseQuery
|
||||||
|
$wpdb->query( "TRUNCATE TABLE `{$wpdb->prefix}mra_provider_status`" );
|
||||||
$wpdb->query( "TRUNCATE TABLE `{$wpdb->prefix}mra_external_results`" );
|
$wpdb->query( "TRUNCATE TABLE `{$wpdb->prefix}mra_external_results`" );
|
||||||
$wpdb->query( "TRUNCATE TABLE `{$wpdb->prefix}mra_media_usage`" );
|
$wpdb->query( "TRUNCATE TABLE `{$wpdb->prefix}mra_media_usage`" );
|
||||||
$wpdb->query( "TRUNCATE TABLE `{$wpdb->prefix}mra_media_index`" );
|
$wpdb->query( "TRUNCATE TABLE `{$wpdb->prefix}mra_media_index`" );
|
||||||
|
|
@ -125,7 +132,7 @@ class Database {
|
||||||
"CREATE TABLE {$wpdb->prefix}mra_external_results (
|
"CREATE TABLE {$wpdb->prefix}mra_external_results (
|
||||||
result_id bigint(20) unsigned NOT NULL auto_increment,
|
result_id bigint(20) unsigned NOT NULL auto_increment,
|
||||||
attachment_id bigint(20) unsigned NOT NULL,
|
attachment_id bigint(20) unsigned NOT NULL,
|
||||||
provider enum('google_vision','tineye') NOT NULL,
|
provider varchar(100) NOT NULL,
|
||||||
raw_response json DEFAULT NULL,
|
raw_response json DEFAULT NULL,
|
||||||
match_count int(10) unsigned NOT NULL DEFAULT 0,
|
match_count int(10) unsigned NOT NULL DEFAULT 0,
|
||||||
top_domains json DEFAULT NULL,
|
top_domains json DEFAULT NULL,
|
||||||
|
|
@ -155,4 +162,57 @@ class Database {
|
||||||
);
|
);
|
||||||
// phpcs:enable WordPress.DB.DirectDatabaseQuery,WordPress.DB.DirectDatabaseQuery.SchemaChange
|
// phpcs:enable WordPress.DB.DirectDatabaseQuery,WordPress.DB.DirectDatabaseQuery.SchemaChange
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Migration 1.1.0 — widens provider column and adds mra_provider_status table.
|
||||||
|
*
|
||||||
|
* Changes:
|
||||||
|
* - ALTER mra_external_results.provider from ENUM to varchar(100) for extensibility.
|
||||||
|
* - CREATE mra_provider_status for per-provider scan tracking.
|
||||||
|
* - Backfill mra_provider_status from existing mra_external_results data.
|
||||||
|
*
|
||||||
|
* Safe to re-run: ALTER TABLE is a no-op if already varchar(100);
|
||||||
|
* dbDelta skips existing tables; INSERT IGNORE skips duplicate rows.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private static function migration_110(): void {
|
||||||
|
global $wpdb;
|
||||||
|
|
||||||
|
$charset_collate = $wpdb->get_charset_collate();
|
||||||
|
|
||||||
|
// phpcs:disable WordPress.DB.DirectDatabaseQuery,WordPress.DB.DirectDatabaseQuery.SchemaChange
|
||||||
|
$wpdb->query(
|
||||||
|
"ALTER TABLE `{$wpdb->prefix}mra_external_results`
|
||||||
|
MODIFY COLUMN provider varchar(100) NOT NULL"
|
||||||
|
);
|
||||||
|
// phpcs:enable WordPress.DB.DirectDatabaseQuery,WordPress.DB.DirectDatabaseQuery.SchemaChange
|
||||||
|
|
||||||
|
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
|
||||||
|
|
||||||
|
dbDelta(
|
||||||
|
"CREATE TABLE {$wpdb->prefix}mra_provider_status (
|
||||||
|
attachment_id bigint(20) unsigned NOT NULL,
|
||||||
|
provider varchar(100) NOT NULL,
|
||||||
|
status enum('pending','queued','scanned','error') NOT NULL DEFAULT 'pending',
|
||||||
|
scanned_at datetime DEFAULT NULL,
|
||||||
|
PRIMARY KEY (attachment_id,provider),
|
||||||
|
KEY status (status)
|
||||||
|
) {$charset_collate};"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Backfill from existing data. Idempotent: INSERT IGNORE skips duplicates.
|
||||||
|
// phpcs:disable WordPress.DB.DirectDatabaseQuery,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||||
|
$wpdb->query(
|
||||||
|
"INSERT IGNORE INTO {$wpdb->prefix}mra_provider_status (attachment_id, provider, status, scanned_at)
|
||||||
|
SELECT er.attachment_id, er.provider,
|
||||||
|
CASE WHEN mi.external_status = 'error' THEN 'error'
|
||||||
|
WHEN mi.external_status = 'queued' THEN 'queued'
|
||||||
|
ELSE 'scanned' END,
|
||||||
|
mi.external_scanned_at
|
||||||
|
FROM {$wpdb->prefix}mra_external_results er
|
||||||
|
JOIN {$wpdb->prefix}mra_media_index mi ON mi.attachment_id = er.attachment_id"
|
||||||
|
);
|
||||||
|
// phpcs:enable WordPress.DB.DirectDatabaseQuery,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
375
includes/External/ExternalScanner.php
vendored
375
includes/External/ExternalScanner.php
vendored
|
|
@ -13,6 +13,9 @@ use MediaRightsAudit\Core\Queue\Scheduler;
|
||||||
* Processes the external scan queue: runs each pending attachment through
|
* Processes the external scan queue: runs each pending attachment through
|
||||||
* all active providers and stores results in mra_external_results.
|
* all active providers and stores results in mra_external_results.
|
||||||
*
|
*
|
||||||
|
* Per-provider scan state is tracked in mra_provider_status. The aggregate
|
||||||
|
* external_status in mra_media_index is updated after each scan for display.
|
||||||
|
*
|
||||||
* Active providers are resolved at runtime via the mra/external/providers filter.
|
* Active providers are resolved at runtime via the mra/external/providers filter.
|
||||||
* Scanning is triggered manually by an administrator (Run External Scan bulk action
|
* Scanning is triggered manually by an administrator (Run External Scan bulk action
|
||||||
* or wp mra scan-external CLI) — never automatically, to control API costs.
|
* or wp mra scan-external CLI) — never automatically, to control API costs.
|
||||||
|
|
@ -27,11 +30,14 @@ class ExternalScanner {
|
||||||
const AS_HOOK = 'mra_external_scan_batch';
|
const AS_HOOK = 'mra_external_scan_batch';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Scans one batch of pending attachments with all active providers.
|
* Scans one batch of queued attachments with their pending providers.
|
||||||
*
|
*
|
||||||
* @param int $batch_size Maximum number of attachments to process.
|
* Queries mra_provider_status for queued entries and processes each
|
||||||
|
* attachment with the intersection of active providers and queued slugs.
|
||||||
*
|
*
|
||||||
* @return int Number of attachments attempted.
|
* @param int $batch_size Maximum number of attachment rows to process.
|
||||||
|
*
|
||||||
|
* @return int Number of attachment rows attempted.
|
||||||
*/
|
*/
|
||||||
public static function scan_batch( int $batch_size = 10 ): int {
|
public static function scan_batch( int $batch_size = 10 ): int {
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
|
|
@ -41,13 +47,20 @@ class ExternalScanner {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Index providers by slug for fast intersection lookup.
|
||||||
|
$provider_map = array();
|
||||||
|
foreach ( $providers as $provider ) {
|
||||||
|
$provider_map[ $provider->provider_slug() ] = $provider;
|
||||||
|
}
|
||||||
|
|
||||||
// phpcs:disable WordPress.DB.DirectDatabaseQuery,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
// phpcs:disable WordPress.DB.DirectDatabaseQuery,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||||
$rows = $wpdb->get_results(
|
$rows = $wpdb->get_results(
|
||||||
$wpdb->prepare(
|
$wpdb->prepare(
|
||||||
"SELECT attachment_id, file_url
|
"SELECT DISTINCT ps.attachment_id, mi.file_url
|
||||||
FROM {$wpdb->prefix}mra_media_index
|
FROM {$wpdb->prefix}mra_provider_status ps
|
||||||
WHERE external_status = 'queued'
|
JOIN {$wpdb->prefix}mra_media_index mi ON mi.attachment_id = ps.attachment_id
|
||||||
ORDER BY attachment_id ASC
|
WHERE ps.status = 'queued'
|
||||||
|
ORDER BY ps.attachment_id ASC
|
||||||
LIMIT %d",
|
LIMIT %d",
|
||||||
$batch_size
|
$batch_size
|
||||||
),
|
),
|
||||||
|
|
@ -69,14 +82,41 @@ class ExternalScanner {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
self::process_attachment( $aid, $url, $providers );
|
// Get queued provider slugs for this attachment.
|
||||||
|
// phpcs:disable WordPress.DB.DirectDatabaseQuery,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||||
|
$queued_slugs = $wpdb->get_col(
|
||||||
|
$wpdb->prepare(
|
||||||
|
"SELECT provider FROM {$wpdb->prefix}mra_provider_status
|
||||||
|
WHERE attachment_id = %d AND status = 'queued'",
|
||||||
|
$aid
|
||||||
|
)
|
||||||
|
);
|
||||||
|
// phpcs:enable WordPress.DB.DirectDatabaseQuery,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||||
|
|
||||||
|
if ( empty( $queued_slugs ) ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Intersect queued slugs with currently active providers.
|
||||||
|
$batch_providers = array();
|
||||||
|
foreach ( $queued_slugs as $slug ) {
|
||||||
|
if ( is_string( $slug ) && isset( $provider_map[ $slug ] ) ) {
|
||||||
|
$batch_providers[] = $provider_map[ $slug ];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( empty( $batch_providers ) ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
self::process_attachment( $aid, $url, $batch_providers );
|
||||||
}
|
}
|
||||||
|
|
||||||
return count( $rows );
|
return count( $rows );
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the count of attachments still waiting for external scanning.
|
* Returns the count of provider-attachment rows still waiting for external scanning.
|
||||||
*
|
*
|
||||||
* @return int
|
* @return int
|
||||||
*/
|
*/
|
||||||
|
|
@ -84,7 +124,7 @@ class ExternalScanner {
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
|
|
||||||
$result = $wpdb->get_var( // phpcs:ignore WordPress.DB.DirectDatabaseQuery
|
$result = $wpdb->get_var( // phpcs:ignore WordPress.DB.DirectDatabaseQuery
|
||||||
"SELECT COUNT(*) FROM {$wpdb->prefix}mra_media_index WHERE external_status = 'queued'"
|
"SELECT COUNT(*) FROM {$wpdb->prefix}mra_provider_status WHERE status = 'queued'"
|
||||||
);
|
);
|
||||||
|
|
||||||
return is_numeric( $result ) ? (int) $result : 0;
|
return is_numeric( $result ) ? (int) $result : 0;
|
||||||
|
|
@ -106,40 +146,22 @@ class ExternalScanner {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Promotes all attachments with external_status = 'pending' to 'queued'.
|
* Marks all pending provider rows as queued and schedules a background scan batch.
|
||||||
*
|
*
|
||||||
* Does not schedule an Action Scheduler action. Useful in CLI context where
|
* Calls queue_provider_attachments() for each active provider to populate
|
||||||
* processing runs synchronously in a loop rather than via background jobs.
|
* mra_provider_status, then syncs the aggregate status on mra_media_index.
|
||||||
*
|
* Only schedules the Action Scheduler action if there are queued items and
|
||||||
* @return void
|
* no batch is already running.
|
||||||
*/
|
|
||||||
public static function queue_all_pending(): void {
|
|
||||||
global $wpdb;
|
|
||||||
|
|
||||||
$wpdb->query( // phpcs:ignore WordPress.DB.DirectDatabaseQuery
|
|
||||||
"UPDATE {$wpdb->prefix}mra_media_index
|
|
||||||
SET external_status = 'queued'
|
|
||||||
WHERE external_status = 'pending'"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Marks all pending attachments as queued and schedules a background scan batch.
|
|
||||||
*
|
|
||||||
* Moves every attachment with external_status = 'pending' to 'queued' so the
|
|
||||||
* status change is visible immediately in the admin list. Only schedules the
|
|
||||||
* Action Scheduler action if there are now queued items and no batch is already running.
|
|
||||||
*
|
*
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public static function schedule(): void {
|
public static function schedule(): void {
|
||||||
global $wpdb;
|
$providers = self::get_active_providers();
|
||||||
|
foreach ( $providers as $provider ) {
|
||||||
|
self::queue_provider_attachments( $provider->provider_slug() );
|
||||||
|
}
|
||||||
|
|
||||||
$wpdb->query( // phpcs:ignore WordPress.DB.DirectDatabaseQuery
|
self::sync_aggregate_queued_status();
|
||||||
"UPDATE {$wpdb->prefix}mra_media_index
|
|
||||||
SET external_status = 'queued'
|
|
||||||
WHERE external_status = 'pending'"
|
|
||||||
);
|
|
||||||
|
|
||||||
if ( self::get_pending_count() > 0 && ! Scheduler::has_pending( self::AS_HOOK ) ) {
|
if ( self::get_pending_count() > 0 && ! Scheduler::has_pending( self::AS_HOOK ) ) {
|
||||||
Scheduler::schedule_single( self::AS_HOOK );
|
Scheduler::schedule_single( self::AS_HOOK );
|
||||||
|
|
@ -147,10 +169,75 @@ class ExternalScanner {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Marks specific attachments as pending and schedules a scan batch.
|
* Promotes all pending provider rows to queued for all active providers.
|
||||||
*
|
*
|
||||||
* Idempotent: already-pending attachments are left unchanged;
|
* Does not schedule an Action Scheduler action. Useful in CLI context where
|
||||||
* previously-scanned or error attachments are reset to pending.
|
* processing runs synchronously in a loop rather than via background jobs.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function queue_all_pending(): void {
|
||||||
|
$providers = self::get_active_providers();
|
||||||
|
foreach ( $providers as $provider ) {
|
||||||
|
self::queue_provider_attachments( $provider->provider_slug() );
|
||||||
|
}
|
||||||
|
|
||||||
|
self::sync_aggregate_queued_status();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates scan entries for newly registered providers.
|
||||||
|
*
|
||||||
|
* For each active provider, inserts mra_provider_status rows for attachments
|
||||||
|
* that have no existing row for that provider. Already-scanned or error rows
|
||||||
|
* are not touched. If new rows are created, syncs aggregate status and
|
||||||
|
* schedules a background scan batch.
|
||||||
|
*
|
||||||
|
* @return int Total number of new rows inserted.
|
||||||
|
*/
|
||||||
|
public static function sync_providers(): int {
|
||||||
|
global $wpdb;
|
||||||
|
|
||||||
|
$providers = self::get_active_providers();
|
||||||
|
$total = 0;
|
||||||
|
|
||||||
|
foreach ( $providers as $provider ) {
|
||||||
|
$slug = $provider->provider_slug();
|
||||||
|
|
||||||
|
// phpcs:disable WordPress.DB.DirectDatabaseQuery,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||||
|
$wpdb->query(
|
||||||
|
$wpdb->prepare(
|
||||||
|
"INSERT IGNORE INTO {$wpdb->prefix}mra_provider_status (attachment_id, provider, status)
|
||||||
|
SELECT mi.attachment_id, %s, 'queued'
|
||||||
|
FROM {$wpdb->prefix}mra_media_index mi
|
||||||
|
LEFT JOIN {$wpdb->prefix}mra_provider_status ps
|
||||||
|
ON ps.attachment_id = mi.attachment_id AND ps.provider = %s
|
||||||
|
WHERE ps.attachment_id IS NULL",
|
||||||
|
$slug,
|
||||||
|
$slug
|
||||||
|
)
|
||||||
|
);
|
||||||
|
// phpcs:enable WordPress.DB.DirectDatabaseQuery,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||||
|
|
||||||
|
$total += (int) $wpdb->rows_affected;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( $total > 0 ) {
|
||||||
|
self::sync_aggregate_queued_status();
|
||||||
|
|
||||||
|
if ( ! Scheduler::has_pending( self::AS_HOOK ) ) {
|
||||||
|
Scheduler::schedule_single( self::AS_HOOK );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $total;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marks specific attachments as queued for all active providers and schedules a scan batch.
|
||||||
|
*
|
||||||
|
* Idempotent: already-queued or scanned rows are left unchanged;
|
||||||
|
* error rows are reset to queued.
|
||||||
*
|
*
|
||||||
* @param array<int, int> $attachment_ids Attachment IDs to queue. Must be non-empty.
|
* @param array<int, int> $attachment_ids Attachment IDs to queue. Must be non-empty.
|
||||||
*
|
*
|
||||||
|
|
@ -165,49 +252,58 @@ class ExternalScanner {
|
||||||
|
|
||||||
$ids = array_map( 'intval', $attachment_ids );
|
$ids = array_map( 'intval', $attachment_ids );
|
||||||
$placeholders = implode( ',', array_fill( 0, count( $ids ), '%d' ) );
|
$placeholders = implode( ',', array_fill( 0, count( $ids ), '%d' ) );
|
||||||
|
$providers = self::get_active_providers();
|
||||||
|
|
||||||
|
foreach ( $providers as $provider ) {
|
||||||
|
$slug = $provider->provider_slug();
|
||||||
|
|
||||||
// phpcs:disable WordPress.DB.DirectDatabaseQuery,WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
|
// phpcs:disable WordPress.DB.DirectDatabaseQuery,WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
|
||||||
$wpdb->query(
|
$wpdb->query(
|
||||||
$wpdb->prepare(
|
$wpdb->prepare(
|
||||||
"UPDATE {$wpdb->prefix}mra_media_index
|
"INSERT INTO {$wpdb->prefix}mra_provider_status (attachment_id, provider, status)
|
||||||
SET external_status = 'queued', external_scanned_at = NULL
|
SELECT attachment_id, %s, 'queued'
|
||||||
|
FROM {$wpdb->prefix}mra_media_index
|
||||||
WHERE attachment_id IN ({$placeholders})
|
WHERE attachment_id IN ({$placeholders})
|
||||||
AND external_status != 'queued'",
|
ON DUPLICATE KEY UPDATE status = IF(status NOT IN ('scanned','queued'),'queued',status)",
|
||||||
...$ids
|
array_merge( array( $slug ), $ids )
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
// phpcs:enable WordPress.DB.DirectDatabaseQuery,WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
|
// phpcs:enable WordPress.DB.DirectDatabaseQuery,WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
|
||||||
|
}
|
||||||
|
|
||||||
|
self::sync_aggregate_queued_status();
|
||||||
|
|
||||||
// Schedule directly — do not call schedule() which would also mark all 'pending' items.
|
|
||||||
if ( ! Scheduler::has_pending( self::AS_HOOK ) ) {
|
if ( ! Scheduler::has_pending( self::AS_HOOK ) ) {
|
||||||
Scheduler::schedule_single( self::AS_HOOK );
|
Scheduler::schedule_single( self::AS_HOOK );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Re-queues all attachments that previously failed with external_status = 'error'.
|
* Re-queues all provider rows that previously failed with status = 'error'.
|
||||||
*
|
*
|
||||||
* Does not touch pending, queued, scanned, or matched attachments.
|
* Does not touch pending, queued, or scanned rows.
|
||||||
* Schedules a background scan batch if any items were re-queued and
|
* Schedules a background scan batch if any items were re-queued and
|
||||||
* no batch is already pending.
|
* no batch is already pending.
|
||||||
*
|
*
|
||||||
* @return int Number of attachments re-queued.
|
* @return int Number of provider rows re-queued.
|
||||||
*/
|
*/
|
||||||
public static function requeue_errors(): int {
|
public static function requeue_errors(): int {
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
|
|
||||||
// phpcs:disable WordPress.DB.DirectDatabaseQuery
|
// phpcs:disable WordPress.DB.DirectDatabaseQuery
|
||||||
$count = (int) $wpdb->get_var(
|
$count = (int) $wpdb->get_var(
|
||||||
"SELECT COUNT(*) FROM {$wpdb->prefix}mra_media_index WHERE external_status = 'error'"
|
"SELECT COUNT(*) FROM {$wpdb->prefix}mra_provider_status WHERE status = 'error'"
|
||||||
);
|
);
|
||||||
|
|
||||||
if ( $count > 0 ) {
|
if ( $count > 0 ) {
|
||||||
$wpdb->query(
|
$wpdb->query(
|
||||||
"UPDATE {$wpdb->prefix}mra_media_index
|
"UPDATE {$wpdb->prefix}mra_provider_status
|
||||||
SET external_status = 'queued', external_scanned_at = NULL
|
SET status = 'queued', scanned_at = NULL
|
||||||
WHERE external_status = 'error'"
|
WHERE status = 'error'"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
self::sync_aggregate_queued_status();
|
||||||
|
|
||||||
if ( ! Scheduler::has_pending( self::AS_HOOK ) ) {
|
if ( ! Scheduler::has_pending( self::AS_HOOK ) ) {
|
||||||
Scheduler::schedule_single( self::AS_HOOK );
|
Scheduler::schedule_single( self::AS_HOOK );
|
||||||
}
|
}
|
||||||
|
|
@ -243,6 +339,14 @@ class ExternalScanner {
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
$wpdb->query(
|
||||||
|
$wpdb->prepare(
|
||||||
|
"DELETE FROM {$wpdb->prefix}mra_provider_status
|
||||||
|
WHERE attachment_id IN ({$placeholders})",
|
||||||
|
...$ids
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
$wpdb->query(
|
$wpdb->query(
|
||||||
$wpdb->prepare(
|
$wpdb->prepare(
|
||||||
"UPDATE {$wpdb->prefix}mra_media_index
|
"UPDATE {$wpdb->prefix}mra_media_index
|
||||||
|
|
@ -257,7 +361,7 @@ class ExternalScanner {
|
||||||
/**
|
/**
|
||||||
* Deletes all external scan results and resets every attachment's external_status to pending.
|
* Deletes all external scan results and resets every attachment's external_status to pending.
|
||||||
*
|
*
|
||||||
* @return int Number of result rows deleted.
|
* @return int Number of result rows deleted from mra_external_results.
|
||||||
*/
|
*/
|
||||||
public static function purge_all(): int {
|
public static function purge_all(): int {
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
|
|
@ -265,6 +369,7 @@ class ExternalScanner {
|
||||||
// phpcs:disable WordPress.DB.DirectDatabaseQuery
|
// phpcs:disable WordPress.DB.DirectDatabaseQuery
|
||||||
$count = (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->prefix}mra_external_results" );
|
$count = (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->prefix}mra_external_results" );
|
||||||
$wpdb->query( "DELETE FROM {$wpdb->prefix}mra_external_results" );
|
$wpdb->query( "DELETE FROM {$wpdb->prefix}mra_external_results" );
|
||||||
|
$wpdb->query( "DELETE FROM {$wpdb->prefix}mra_provider_status" );
|
||||||
$wpdb->query(
|
$wpdb->query(
|
||||||
"UPDATE {$wpdb->prefix}mra_media_index
|
"UPDATE {$wpdb->prefix}mra_media_index
|
||||||
SET external_status = 'pending', external_scanned_at = NULL"
|
SET external_status = 'pending', external_scanned_at = NULL"
|
||||||
|
|
@ -277,6 +382,8 @@ class ExternalScanner {
|
||||||
/**
|
/**
|
||||||
* Returns a count of attachments grouped by external_status.
|
* Returns a count of attachments grouped by external_status.
|
||||||
*
|
*
|
||||||
|
* Reads from the mra_media_index aggregate column for display purposes.
|
||||||
|
*
|
||||||
* @return array<string, int>
|
* @return array<string, int>
|
||||||
*/
|
*/
|
||||||
public static function get_status_counts(): array {
|
public static function get_status_counts(): array {
|
||||||
|
|
@ -315,35 +422,55 @@ class ExternalScanner {
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Scans a single attachment with all providers and updates the index.
|
* Scans a single attachment with specified providers and updates per-provider status.
|
||||||
|
*
|
||||||
|
* After scanning all providers, calls update_aggregate_status() to sync
|
||||||
|
* the mra_media_index aggregate column.
|
||||||
*
|
*
|
||||||
* @param int $attachment_id WordPress attachment ID.
|
* @param int $attachment_id WordPress attachment ID.
|
||||||
* @param string $file_url Public URL of the image.
|
* @param string $file_url Public URL of the image.
|
||||||
* @param array<AbstractProvider> $providers Resolved provider list.
|
* @param array<AbstractProvider> $providers Resolved provider list for this attachment.
|
||||||
*
|
*
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
private static function process_attachment( int $attachment_id, string $file_url, array $providers ): void {
|
private static function process_attachment( int $attachment_id, string $file_url, array $providers ): void {
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
|
|
||||||
$any_success = false;
|
|
||||||
$any_match = false;
|
|
||||||
|
|
||||||
foreach ( $providers as $provider ) {
|
foreach ( $providers as $provider ) {
|
||||||
|
$slug = $provider->provider_slug();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$result = $provider->scan( $attachment_id, $file_url );
|
$result = $provider->scan( $attachment_id, $file_url );
|
||||||
self::save_result( $attachment_id, $provider->provider_slug(), $result );
|
self::save_result( $attachment_id, $slug, $result );
|
||||||
$any_success = true;
|
|
||||||
if ( $result->match_count() > 0 ) {
|
$wpdb->query( // phpcs:ignore WordPress.DB.DirectDatabaseQuery
|
||||||
$any_match = true;
|
$wpdb->prepare(
|
||||||
}
|
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||||
|
"UPDATE {$wpdb->prefix}mra_provider_status
|
||||||
|
SET status = 'scanned', scanned_at = NOW()
|
||||||
|
WHERE attachment_id = %d AND provider = %s",
|
||||||
|
$attachment_id,
|
||||||
|
$slug
|
||||||
|
)
|
||||||
|
);
|
||||||
} catch ( \RuntimeException $e ) {
|
} catch ( \RuntimeException $e ) {
|
||||||
|
$wpdb->query( // phpcs:ignore WordPress.DB.DirectDatabaseQuery
|
||||||
|
$wpdb->prepare(
|
||||||
|
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||||
|
"UPDATE {$wpdb->prefix}mra_provider_status
|
||||||
|
SET status = 'error', scanned_at = NOW()
|
||||||
|
WHERE attachment_id = %d AND provider = %s",
|
||||||
|
$attachment_id,
|
||||||
|
$slug
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
if ( defined( 'WP_DEBUG_LOG' ) && WP_DEBUG_LOG ) {
|
if ( defined( 'WP_DEBUG_LOG' ) && WP_DEBUG_LOG ) {
|
||||||
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
|
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
|
||||||
error_log(
|
error_log(
|
||||||
sprintf(
|
sprintf(
|
||||||
'[MRA] %s scan failed for attachment %d: %s',
|
'[MRA] %s scan failed for attachment %d: %s',
|
||||||
$provider->provider_slug(),
|
$slug,
|
||||||
$attachment_id,
|
$attachment_id,
|
||||||
$e->getMessage()
|
$e->getMessage()
|
||||||
)
|
)
|
||||||
|
|
@ -352,14 +479,69 @@ class ExternalScanner {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( ! $any_success ) {
|
self::update_aggregate_status( $attachment_id );
|
||||||
$new_status = 'error';
|
|
||||||
} elseif ( $any_match ) {
|
|
||||||
$new_status = 'matches';
|
|
||||||
} else {
|
|
||||||
$new_status = 'scanned';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recomputes and persists the aggregate external_status for a single attachment.
|
||||||
|
*
|
||||||
|
* Logic:
|
||||||
|
* - Any provider still queued → 'queued'
|
||||||
|
* - No provider succeeded → 'error'
|
||||||
|
* - At least one match found → 'matches'
|
||||||
|
* - Otherwise → 'scanned'
|
||||||
|
*
|
||||||
|
* @param int $attachment_id WordPress attachment ID.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private static function update_aggregate_status( int $attachment_id ): void {
|
||||||
|
global $wpdb;
|
||||||
|
|
||||||
|
// phpcs:disable WordPress.DB.DirectDatabaseQuery,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||||
|
$row = $wpdb->get_row(
|
||||||
|
$wpdb->prepare(
|
||||||
|
"SELECT
|
||||||
|
SUM(status = 'queued') AS queued_count,
|
||||||
|
SUM(status = 'error') AS error_count,
|
||||||
|
SUM(status = 'scanned') AS scanned_count
|
||||||
|
FROM {$wpdb->prefix}mra_provider_status
|
||||||
|
WHERE attachment_id = %d",
|
||||||
|
$attachment_id
|
||||||
|
),
|
||||||
|
ARRAY_A
|
||||||
|
);
|
||||||
|
// phpcs:enable WordPress.DB.DirectDatabaseQuery,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||||
|
|
||||||
|
if ( ! is_array( $row ) ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$queued_count = isset( $row['queued_count'] ) && is_numeric( $row['queued_count'] ) ? (int) $row['queued_count'] : 0;
|
||||||
|
$error_count = isset( $row['error_count'] ) && is_numeric( $row['error_count'] ) ? (int) $row['error_count'] : 0;
|
||||||
|
$scanned_count = isset( $row['scanned_count'] ) && is_numeric( $row['scanned_count'] ) ? (int) $row['scanned_count'] : 0;
|
||||||
|
|
||||||
|
if ( $queued_count > 0 ) {
|
||||||
|
$new_status = 'queued';
|
||||||
|
} elseif ( 0 === $scanned_count ) {
|
||||||
|
$new_status = 'error';
|
||||||
|
} else {
|
||||||
|
// Check for matches across scanned providers.
|
||||||
|
// phpcs:disable WordPress.DB.DirectDatabaseQuery,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||||
|
$match_sum = $wpdb->get_var(
|
||||||
|
$wpdb->prepare(
|
||||||
|
"SELECT SUM(match_count) FROM {$wpdb->prefix}mra_external_results WHERE attachment_id = %d",
|
||||||
|
$attachment_id
|
||||||
|
)
|
||||||
|
);
|
||||||
|
// phpcs:enable WordPress.DB.DirectDatabaseQuery,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||||
|
|
||||||
|
$new_status = ( is_numeric( $match_sum ) && (int) $match_sum > 0 ) ? 'matches' : 'scanned';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Suppress unused variable inspection: $error_count is available for future use.
|
||||||
|
unset( $error_count );
|
||||||
|
|
||||||
$wpdb->update( // phpcs:ignore WordPress.DB.DirectDatabaseQuery
|
$wpdb->update( // phpcs:ignore WordPress.DB.DirectDatabaseQuery
|
||||||
$wpdb->prefix . 'mra_media_index',
|
$wpdb->prefix . 'mra_media_index',
|
||||||
array(
|
array(
|
||||||
|
|
@ -372,6 +554,57 @@ class ExternalScanner {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates mra_media_index.external_status to 'queued' for all attachments
|
||||||
|
* that have at least one queued row in mra_provider_status.
|
||||||
|
*
|
||||||
|
* Only updates rows not already marked 'queued', to avoid unnecessary writes.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private static function sync_aggregate_queued_status(): void {
|
||||||
|
global $wpdb;
|
||||||
|
|
||||||
|
$wpdb->query( // phpcs:ignore WordPress.DB.DirectDatabaseQuery
|
||||||
|
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||||
|
"UPDATE {$wpdb->prefix}mra_media_index mi
|
||||||
|
INNER JOIN (
|
||||||
|
SELECT DISTINCT attachment_id
|
||||||
|
FROM {$wpdb->prefix}mra_provider_status
|
||||||
|
WHERE status = 'queued'
|
||||||
|
) AS q ON q.attachment_id = mi.attachment_id
|
||||||
|
SET mi.external_status = 'queued'
|
||||||
|
WHERE mi.external_status != 'queued'"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inserts or updates mra_provider_status rows for all indexed attachments.
|
||||||
|
*
|
||||||
|
* For attachments without an existing row for this provider, inserts 'queued'.
|
||||||
|
* For attachments with an existing non-scanned, non-queued row, resets to 'queued'.
|
||||||
|
* Scanned and already-queued rows are left unchanged.
|
||||||
|
*
|
||||||
|
* @param string $provider_slug Provider machine-readable slug.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private static function queue_provider_attachments( string $provider_slug ): void {
|
||||||
|
global $wpdb;
|
||||||
|
|
||||||
|
// phpcs:disable WordPress.DB.DirectDatabaseQuery,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||||
|
$wpdb->query(
|
||||||
|
$wpdb->prepare(
|
||||||
|
"INSERT INTO {$wpdb->prefix}mra_provider_status (attachment_id, provider, status)
|
||||||
|
SELECT attachment_id, %s, 'queued'
|
||||||
|
FROM {$wpdb->prefix}mra_media_index
|
||||||
|
ON DUPLICATE KEY UPDATE status = IF(status NOT IN ('scanned','queued'),'queued',status)",
|
||||||
|
$provider_slug
|
||||||
|
)
|
||||||
|
);
|
||||||
|
// phpcs:enable WordPress.DB.DirectDatabaseQuery,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Persists a provider's scan result (upsert via REPLACE INTO).
|
* Persists a provider's scan result (upsert via REPLACE INTO).
|
||||||
*
|
*
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -973,6 +973,18 @@ msgstr "Última exploració (externa)"
|
||||||
msgid "Scanned At"
|
msgid "Scanned At"
|
||||||
msgstr "Explorat el"
|
msgstr "Explorat el"
|
||||||
|
|
||||||
|
#: includes/Admin/ToolsPage.php:365
|
||||||
|
msgid "Provider registry synced. New scans scheduled."
|
||||||
|
msgstr "Registre de proveïdors sincronitzat. Nous escanejos programats."
|
||||||
|
|
||||||
|
#: includes/Admin/ToolsPage.php:642
|
||||||
|
msgid "Sync provider registry"
|
||||||
|
msgstr "Sincronitza el registre de proveïdors"
|
||||||
|
|
||||||
|
#: includes/Admin/ToolsPage.php:643
|
||||||
|
msgid "Creates scan entries for any newly registered external providers. Run this after adding a new provider via the mra/external/providers filter. Already-scanned attachments are not affected."
|
||||||
|
msgstr "Crea entrades d'escaneig per als proveïdors externs registrats recentment. Executa-ho després d'afegir un nou proveïdor mitjançant el filtre mra/external/providers. Els fitxers ja escanejats no es veuen afectats."
|
||||||
|
|
||||||
#~ msgid "https://robotstxt.es/plugins/media-audit/"
|
#~ msgid "https://robotstxt.es/plugins/media-audit/"
|
||||||
#~ msgstr "https://robotstxt.es/plugins/media-audit/"
|
#~ msgstr "https://robotstxt.es/plugins/media-audit/"
|
||||||
|
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -969,6 +969,18 @@ msgstr "Último escaneo (externo)"
|
||||||
msgid "Scanned At"
|
msgid "Scanned At"
|
||||||
msgstr "Escaneado el"
|
msgstr "Escaneado el"
|
||||||
|
|
||||||
|
#: includes/Admin/ToolsPage.php:365
|
||||||
|
msgid "Provider registry synced. New scans scheduled."
|
||||||
|
msgstr "Registro de proveedores sincronizado. Nuevos escaneos programados."
|
||||||
|
|
||||||
|
#: includes/Admin/ToolsPage.php:642
|
||||||
|
msgid "Sync provider registry"
|
||||||
|
msgstr "Sincronizar registro de proveedores"
|
||||||
|
|
||||||
|
#: includes/Admin/ToolsPage.php:643
|
||||||
|
msgid "Creates scan entries for any newly registered external providers. Run this after adding a new provider via the mra/external/providers filter. Already-scanned attachments are not affected."
|
||||||
|
msgstr "Crea entradas de escaneo para los proveedores externos recién registrados. Ejecútalo después de añadir un nuevo proveedor mediante el filtro mra/external/providers. Los archivos ya escaneados no se ven afectados."
|
||||||
|
|
||||||
#~ msgid "https://robotstxt.es/plugins/media-audit/"
|
#~ msgid "https://robotstxt.es/plugins/media-audit/"
|
||||||
#~ msgstr "https://robotstxt.es/plugins/media-audit/"
|
#~ msgstr "https://robotstxt.es/plugins/media-audit/"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ Requires at least: 6.8
|
||||||
Tested up to: 7.0
|
Tested up to: 7.0
|
||||||
Requires PHP: 8.2
|
Requires PHP: 8.2
|
||||||
Requires Plugins: action-scheduler
|
Requires Plugins: action-scheduler
|
||||||
Stable tag: 1.2.0
|
Stable tag: 1.3.0
|
||||||
License: GPL-3.0-or-later
|
License: GPL-3.0-or-later
|
||||||
License URI: https://www.gnu.org/licenses/gpl-3.0.txt
|
License URI: https://www.gnu.org/licenses/gpl-3.0.txt
|
||||||
|
|
||||||
|
|
@ -98,6 +98,13 @@ 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).
|
Only the 3 latest versions. The full changelog is in [changelog.txt](changelog.txt).
|
||||||
|
|
||||||
|
= 1.3.0 =
|
||||||
|
|
||||||
|
* Added per-provider scan tracking table (`mra_provider_status`) so each API is tracked independently.
|
||||||
|
* Added "Sync providers" operation: registers newly configured providers for all existing attachments without rescanning already-completed work.
|
||||||
|
* Changed `mra_external_results.provider` from a fixed ENUM to `varchar(100)` for extensibility.
|
||||||
|
* DB schema bumped to 1.1.0 with an automatic migration on update.
|
||||||
|
|
||||||
= 1.2.0 =
|
= 1.2.0 =
|
||||||
|
|
||||||
* Added browser-based AJAX scan runner in the Tools page (runs without WP-Cron or Action Scheduler).
|
* Added browser-based AJAX scan runner in the Tools page (runs without WP-Cron or Action Scheduler).
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
* Plugin Name: Media Audit (by ROBOTSTXT)
|
* Plugin Name: Media Audit (by ROBOTSTXT)
|
||||||
* Plugin URI: https://git.robotstxt.es/ROBOTSTXT/robotstxt-mediaaudit
|
* Plugin URI: https://git.robotstxt.es/ROBOTSTXT/robotstxt-mediaaudit
|
||||||
* Description: Internal media library usage auditing and external reverse image search to detect potential copyright issues.
|
* Description: Internal media library usage auditing and external reverse image search to detect potential copyright issues.
|
||||||
* Version: 1.2.0
|
* Version: 1.3.0
|
||||||
* Requires at least: 6.8
|
* Requires at least: 6.8
|
||||||
* Tested up to: 7.0
|
* Tested up to: 7.0
|
||||||
* Requires PHP: 8.2
|
* Requires PHP: 8.2
|
||||||
|
|
@ -23,8 +23,8 @@ if ( ! defined( 'ABSPATH' ) ) {
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
define( 'ROBOTSTXT_MEDIAAUDIT_VERSION', '1.2.0' );
|
define( 'ROBOTSTXT_MEDIAAUDIT_VERSION', '1.3.0' );
|
||||||
define( 'ROBOTSTXT_MEDIAAUDIT_DB_VERSION', '1.0.1' );
|
define( 'ROBOTSTXT_MEDIAAUDIT_DB_VERSION', '1.1.0' );
|
||||||
define( 'ROBOTSTXT_MEDIAAUDIT_PLUGIN_FILE', __FILE__ );
|
define( 'ROBOTSTXT_MEDIAAUDIT_PLUGIN_FILE', __FILE__ );
|
||||||
define( 'ROBOTSTXT_MEDIAAUDIT_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
|
define( 'ROBOTSTXT_MEDIAAUDIT_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
|
||||||
define( 'ROBOTSTXT_MEDIAAUDIT_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
|
define( 'ROBOTSTXT_MEDIAAUDIT_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ if ( ! $delete ) {
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
|
|
||||||
// Drop tables in reverse dependency order.
|
// Drop tables in reverse dependency order.
|
||||||
|
$wpdb->query( "DROP TABLE IF EXISTS `{$wpdb->prefix}mra_provider_status`" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery
|
||||||
$wpdb->query( "DROP TABLE IF EXISTS `{$wpdb->prefix}mra_external_results`" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery
|
$wpdb->query( "DROP TABLE IF EXISTS `{$wpdb->prefix}mra_external_results`" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery
|
||||||
$wpdb->query( "DROP TABLE IF EXISTS `{$wpdb->prefix}mra_media_usage`" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery
|
$wpdb->query( "DROP TABLE IF EXISTS `{$wpdb->prefix}mra_media_usage`" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery
|
||||||
$wpdb->query( "DROP TABLE IF EXISTS `{$wpdb->prefix}mra_media_index`" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery
|
$wpdb->query( "DROP TABLE IF EXISTS `{$wpdb->prefix}mra_media_index`" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue