v1.5.0
This commit is contained in:
parent
672d0f295f
commit
83d629568a
20 changed files with 1321 additions and 15 deletions
|
|
@ -102,6 +102,21 @@
|
||||||
color: #856404;
|
color: #856404;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mra-picrisk-high {
|
||||||
|
background: #f8d7da;
|
||||||
|
color: #721c24;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mra-picrisk-medium {
|
||||||
|
background: #fff3cd;
|
||||||
|
color: #856404;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mra-picrisk-low {
|
||||||
|
background: #d4edda;
|
||||||
|
color: #155724;
|
||||||
|
}
|
||||||
|
|
||||||
.mra-badge-match-count {
|
.mra-badge-match-count {
|
||||||
background: #721c24;
|
background: #721c24;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,57 @@
|
||||||
== Changelog ==
|
== Changelog ==
|
||||||
|
|
||||||
|
= 1.5.0 =
|
||||||
|
|
||||||
|
_Release date: 2026-05-05_
|
||||||
|
|
||||||
|
**Added**
|
||||||
|
|
||||||
|
* PicDefense as a third external scan provider: POST-based copyright risk analysis via `POST /checkImageRisk`, authenticated with `X-API-TOKEN: {userId}:{apiKey}` header.
|
||||||
|
* PicDefense results include risk classification (`picrisk`: high/medium/low), backlinks with similarity score and image URL, risk flags (face, logo, landmark, stock, EXIF copyright), and label detection.
|
||||||
|
* Full Report attachment detail page: PicDefense section with picRisk badge, risk flags list, backlinks table sorted by similarity score (highest first), and detected labels.
|
||||||
|
* Audit list quick-view modal: picRisk badge shown inline next to PicDefense match count.
|
||||||
|
* Settings page: PicDefense User ID and API Key fields with credential status indicator.
|
||||||
|
|
||||||
|
**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.4.0 =
|
||||||
|
|
||||||
|
_Release date: 2026-05-05_
|
||||||
|
|
||||||
|
**Added**
|
||||||
|
|
||||||
|
* Full attachment detail page accessible via "Full Report" row action in the audit list and "View full report →" link in the quick-view modal. Shows file metadata, internal usage, and complete raw external scan results per provider:
|
||||||
|
* Google Vision: pages with matching images (URL + page title), full image matches, and partial image matches — all as clickable links.
|
||||||
|
* TinEye: backlinks table with webpage URL, direct image URL, and crawl date — sorted by crawl date, newest first.
|
||||||
|
|
||||||
|
**Fixed**
|
||||||
|
|
||||||
|
* `rate_limit_per_minute` setting (Settings page) was stored but never read by providers — both Google Vision and TinEye used a hardcoded limit of 10 req/min regardless of configuration.
|
||||||
|
* `external_batch_size` setting was ignored by Action Scheduler batch processing and by the browser AJAX runner — both always processed 10 attachments per batch.
|
||||||
|
* TinEye domain extraction used the wrong API field: `backlink.url` is the direct image URL (CDN), while `backlink.backlink` is the webpage URL. The `top_domains` summary and the detail page now show the correct page domains and URLs.
|
||||||
|
|
||||||
|
**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.3.0 =
|
= 1.3.0 =
|
||||||
|
|
||||||
_Release date: 2026-05-05_
|
_Release date: 2026-05-05_
|
||||||
|
|
|
||||||
595
includes/Admin/AttachmentDetailPage.php
Normal file
595
includes/Admin/AttachmentDetailPage.php
Normal file
|
|
@ -0,0 +1,595 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Admin page controller for the Attachment Detail (full scan report) view.
|
||||||
|
*
|
||||||
|
* @package MediaRightsAudit\Admin
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace MediaRightsAudit\Admin;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers and renders the hidden attachment detail admin page.
|
||||||
|
*
|
||||||
|
* Displays the full scan report for a single attachment, including:
|
||||||
|
* - File metadata from mra_media_index.
|
||||||
|
* - Internal usage (post references).
|
||||||
|
* - Raw external scan results per provider.
|
||||||
|
*/
|
||||||
|
class AttachmentDetailPage {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the URL for the attachment detail page.
|
||||||
|
*
|
||||||
|
* @param int $attachment_id WordPress attachment post ID.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function url( int $attachment_id ): string {
|
||||||
|
return admin_url( 'admin.php?page=robotstxt-mediaaudit-detail&attachment_id=' . $attachment_id );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders the full attachment detail page.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function render(): void {
|
||||||
|
if ( ! current_user_can( 'edit_posts' ) ) {
|
||||||
|
wp_die( esc_html__( 'You do not have permission to access this page.', 'robotstxt-mediaaudit' ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
// phpcs:disable WordPress.Security.NonceVerification.Recommended -- read-only display page.
|
||||||
|
$raw_id = isset( $_GET['attachment_id'] ) && is_string( $_GET['attachment_id'] )
|
||||||
|
? (int) sanitize_text_field( wp_unslash( $_GET['attachment_id'] ) )
|
||||||
|
: 0;
|
||||||
|
// phpcs:enable WordPress.Security.NonceVerification.Recommended
|
||||||
|
$attachment_id = $raw_id;
|
||||||
|
|
||||||
|
if ( $attachment_id <= 0 ) {
|
||||||
|
wp_die( esc_html__( 'Invalid attachment ID.', 'robotstxt-mediaaudit' ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
$post = get_post( $attachment_id );
|
||||||
|
if ( null === $post || 'attachment' !== $post->post_type ) {
|
||||||
|
wp_die( esc_html__( 'Invalid attachment ID.', 'robotstxt-mediaaudit' ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
$filename = esc_html( '' !== $post->post_title ? $post->post_title : sprintf( '#%d', $attachment_id ) );
|
||||||
|
$back_url = admin_url( 'admin.php?page=robotstxt-mediaaudit' );
|
||||||
|
$index_row = $this->get_index_row( $attachment_id );
|
||||||
|
|
||||||
|
echo '<div class="wrap">';
|
||||||
|
echo '<h1><a href="' . esc_url( $back_url ) . '">' . esc_html__( '← Media Audit', 'robotstxt-mediaaudit' ) . '</a> — ' . $filename . '</h1>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- $filename is already escaped via esc_html() above.
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// File Information
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
echo '<h2>' . esc_html__( 'File Information', 'robotstxt-mediaaudit' ) . '</h2>';
|
||||||
|
echo '<table class="widefat mra-detail-meta"><tbody>';
|
||||||
|
|
||||||
|
$fn_raw = isset( $index_row['file_name'] ) && is_string( $index_row['file_name'] ) ? $index_row['file_name'] : '';
|
||||||
|
$url_raw = isset( $index_row['file_url'] ) && is_string( $index_row['file_url'] ) ? $index_row['file_url'] : '';
|
||||||
|
$mt_raw = isset( $index_row['mime_type'] ) && is_string( $index_row['mime_type'] ) ? $index_row['mime_type'] : '';
|
||||||
|
$fs_raw = isset( $index_row['file_size'] ) ? $index_row['file_size'] : null;
|
||||||
|
$is_raw = isset( $index_row['internal_scanned_at'] ) && is_string( $index_row['internal_scanned_at'] ) ? $index_row['internal_scanned_at'] : '';
|
||||||
|
$es_raw = isset( $index_row['external_status'] ) && is_string( $index_row['external_status'] ) ? $index_row['external_status'] : '';
|
||||||
|
|
||||||
|
printf(
|
||||||
|
'<tr><th>%s</th><td>%s</td></tr>',
|
||||||
|
esc_html__( 'ID', 'robotstxt-mediaaudit' ),
|
||||||
|
esc_html( (string) $attachment_id )
|
||||||
|
);
|
||||||
|
|
||||||
|
printf(
|
||||||
|
'<tr><th>%s</th><td>%s</td></tr>',
|
||||||
|
esc_html__( 'Filename', 'robotstxt-mediaaudit' ),
|
||||||
|
esc_html( $fn_raw )
|
||||||
|
);
|
||||||
|
|
||||||
|
if ( '' !== $url_raw ) {
|
||||||
|
printf(
|
||||||
|
'<tr><th>%s</th><td><a href="%s" target="_blank">%s</a></td></tr>',
|
||||||
|
esc_html__( 'File URL', 'robotstxt-mediaaudit' ),
|
||||||
|
esc_url( $url_raw ),
|
||||||
|
esc_html( $url_raw )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
printf(
|
||||||
|
'<tr><th>%s</th><td>%s</td></tr>',
|
||||||
|
esc_html__( 'MIME Type', 'robotstxt-mediaaudit' ),
|
||||||
|
esc_html( $mt_raw )
|
||||||
|
);
|
||||||
|
|
||||||
|
$file_size_str = '';
|
||||||
|
if ( null !== $fs_raw && is_numeric( $fs_raw ) && (int) $fs_raw > 0 ) {
|
||||||
|
$file_size_str = size_format( (int) $fs_raw );
|
||||||
|
}
|
||||||
|
printf(
|
||||||
|
'<tr><th>%s</th><td>%s</td></tr>',
|
||||||
|
esc_html__( 'File size', 'robotstxt-mediaaudit' ),
|
||||||
|
esc_html( $file_size_str )
|
||||||
|
);
|
||||||
|
|
||||||
|
printf(
|
||||||
|
'<tr><th>%s</th><td>%s</td></tr>',
|
||||||
|
esc_html__( 'Internal scan date', 'robotstxt-mediaaudit' ),
|
||||||
|
esc_html( $is_raw )
|
||||||
|
);
|
||||||
|
|
||||||
|
printf(
|
||||||
|
'<tr><th>%s</th><td>%s</td></tr>',
|
||||||
|
esc_html__( 'External Status', 'robotstxt-mediaaudit' ),
|
||||||
|
esc_html( $es_raw )
|
||||||
|
);
|
||||||
|
|
||||||
|
echo '</tbody></table>';
|
||||||
|
|
||||||
|
// Thumbnail.
|
||||||
|
$thumb = wp_get_attachment_image( $attachment_id, 'medium', false, array( 'class' => 'mra-detail-thumb' ) );
|
||||||
|
if ( $thumb ) {
|
||||||
|
echo '<p>' . $thumb . '</p>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- wp_get_attachment_image is safe.
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// Internal Usage
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
echo '<h2>' . esc_html__( 'Internal Usage', 'robotstxt-mediaaudit' ) . '</h2>';
|
||||||
|
|
||||||
|
$usages = MediaListTable::fetch_usages( array( $attachment_id ) );
|
||||||
|
|
||||||
|
$context_labels = array(
|
||||||
|
'featured' => __( 'Featured Image', 'robotstxt-mediaaudit' ),
|
||||||
|
'content' => __( 'Post Content', 'robotstxt-mediaaudit' ),
|
||||||
|
'meta' => __( 'Custom Field', 'robotstxt-mediaaudit' ),
|
||||||
|
);
|
||||||
|
|
||||||
|
if ( empty( $usages ) ) {
|
||||||
|
echo '<p>' . esc_html__( 'Not used in any post.', 'robotstxt-mediaaudit' ) . '</p>';
|
||||||
|
} else {
|
||||||
|
echo '<table class="widefat mra-usages-table">';
|
||||||
|
echo '<thead><tr>';
|
||||||
|
echo '<th>' . esc_html__( 'Post', 'robotstxt-mediaaudit' ) . '</th>';
|
||||||
|
echo '<th>' . esc_html__( 'Type', 'robotstxt-mediaaudit' ) . '</th>';
|
||||||
|
echo '<th>' . esc_html__( 'Context', 'robotstxt-mediaaudit' ) . '</th>';
|
||||||
|
echo '<th>' . esc_html__( 'Status', 'robotstxt-mediaaudit' ) . '</th>';
|
||||||
|
echo '</tr></thead><tbody>';
|
||||||
|
|
||||||
|
foreach ( $usages as $u ) {
|
||||||
|
$pid_val = $u['post_id'] ?? null;
|
||||||
|
$post_id = is_numeric( $pid_val ) ? (int) $pid_val : 0;
|
||||||
|
$t_raw = $u['post_title'] ?? '';
|
||||||
|
$post_title = is_string( $t_raw ) && '' !== $t_raw ? $t_raw : sprintf( '#%d', $post_id );
|
||||||
|
$pt_val = $u['post_type'] ?? null;
|
||||||
|
$post_type = is_string( $pt_val ) ? $pt_val : '';
|
||||||
|
$ctx_val = $u['context'] ?? null;
|
||||||
|
$context = is_string( $ctx_val ) ? $ctx_val : '';
|
||||||
|
$status_val = $u['post_status'] ?? null;
|
||||||
|
$status = is_string( $status_val ) ? $status_val : '';
|
||||||
|
$mk_val = $u['meta_key'] ?? null;
|
||||||
|
$meta_key = is_string( $mk_val ) ? $mk_val : '';
|
||||||
|
|
||||||
|
$ctx_label = isset( $context_labels[ $context ] ) ? $context_labels[ $context ] : esc_html( $context );
|
||||||
|
if ( '' !== $meta_key ) {
|
||||||
|
$ctx_label .= ' <code>' . esc_html( $meta_key ) . '</code>';
|
||||||
|
}
|
||||||
|
|
||||||
|
$edit_link = get_edit_post_link( $post_id );
|
||||||
|
$post_cell = $edit_link
|
||||||
|
? sprintf( '<a href="%s">%s</a>', esc_url( $edit_link ), esc_html( $post_title ) )
|
||||||
|
: esc_html( $post_title );
|
||||||
|
|
||||||
|
echo '<tr>';
|
||||||
|
echo '<td>' . $post_cell . '</td>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- $post_cell already escaped above.
|
||||||
|
echo '<td>' . esc_html( $post_type ) . '</td>';
|
||||||
|
echo '<td>' . $ctx_label . '</td>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- $ctx_label already escaped above.
|
||||||
|
echo '<td>' . esc_html( $status ) . '</td>';
|
||||||
|
echo '</tr>';
|
||||||
|
}
|
||||||
|
|
||||||
|
echo '</tbody></table>';
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// External Scan Results
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
echo '<h2>' . esc_html__( 'External Scan Results', 'robotstxt-mediaaudit' ) . '</h2>';
|
||||||
|
$this->render_external_results( $attachment_id );
|
||||||
|
|
||||||
|
echo '</div>'; // .wrap
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// Private helpers
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches the mra_media_index row for a given attachment ID.
|
||||||
|
*
|
||||||
|
* @param int $attachment_id Attachment post ID.
|
||||||
|
*
|
||||||
|
* @return array<array-key, mixed>|null
|
||||||
|
*/
|
||||||
|
private function get_index_row( int $attachment_id ): ?array {
|
||||||
|
global $wpdb;
|
||||||
|
|
||||||
|
// phpcs:ignore WordPress.DB.DirectDatabaseQuery
|
||||||
|
$row = $wpdb->get_row(
|
||||||
|
$wpdb->prepare(
|
||||||
|
"SELECT file_url, file_name, mime_type, file_size, internal_scanned_at, external_status, external_scanned_at
|
||||||
|
FROM {$wpdb->prefix}mra_media_index
|
||||||
|
WHERE attachment_id = %d
|
||||||
|
LIMIT 1",
|
||||||
|
$attachment_id
|
||||||
|
),
|
||||||
|
ARRAY_A
|
||||||
|
);
|
||||||
|
|
||||||
|
return is_array( $row ) ? $row : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches and renders the external scan results for a given attachment.
|
||||||
|
*
|
||||||
|
* @param int $attachment_id Attachment post ID.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function render_external_results( int $attachment_id ): void {
|
||||||
|
global $wpdb;
|
||||||
|
|
||||||
|
// phpcs:ignore WordPress.DB.DirectDatabaseQuery
|
||||||
|
$rows = $wpdb->get_results(
|
||||||
|
$wpdb->prepare(
|
||||||
|
"SELECT provider, match_count, raw_response, created_at
|
||||||
|
FROM {$wpdb->prefix}mra_external_results
|
||||||
|
WHERE attachment_id = %d
|
||||||
|
ORDER BY provider ASC",
|
||||||
|
$attachment_id
|
||||||
|
),
|
||||||
|
ARRAY_A
|
||||||
|
);
|
||||||
|
|
||||||
|
if ( empty( $rows ) ) {
|
||||||
|
echo '<p>' . esc_html__( 'No external scan data available.', 'robotstxt-mediaaudit' ) . '</p>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$provider_names = array(
|
||||||
|
'google_vision' => 'Google Cloud Vision',
|
||||||
|
'tineye' => 'TinEye',
|
||||||
|
'picdefense' => 'PicDefense',
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach ( $rows as $row ) {
|
||||||
|
if ( ! is_array( $row ) ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$slug_val = $row['provider'] ?? null;
|
||||||
|
$slug = is_string( $slug_val ) ? $slug_val : '';
|
||||||
|
$name = isset( $provider_names[ $slug ] ) ? $provider_names[ $slug ] : ucwords( str_replace( '_', ' ', $slug ) );
|
||||||
|
$mc_val = $row['match_count'] ?? null;
|
||||||
|
$match_count = is_numeric( $mc_val ) ? (int) $mc_val : 0;
|
||||||
|
$at_val = $row['created_at'] ?? null;
|
||||||
|
$scanned_at = is_string( $at_val ) ? $at_val : '';
|
||||||
|
$rr_val = $row['raw_response'] ?? null;
|
||||||
|
$raw = null;
|
||||||
|
if ( is_string( $rr_val ) && '' !== $rr_val ) {
|
||||||
|
$decoded = json_decode( $rr_val, true );
|
||||||
|
$raw = is_array( $decoded ) ? $decoded : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
echo '<h3>';
|
||||||
|
echo esc_html( $name );
|
||||||
|
echo ' — ';
|
||||||
|
echo esc_html(
|
||||||
|
sprintf(
|
||||||
|
/* translators: %d: number of external matches */
|
||||||
|
_n( '%d match', '%d matches', $match_count, 'robotstxt-mediaaudit' ),
|
||||||
|
$match_count
|
||||||
|
)
|
||||||
|
);
|
||||||
|
if ( '' !== $scanned_at ) {
|
||||||
|
echo ' — ' . esc_html(
|
||||||
|
sprintf(
|
||||||
|
/* translators: %s: scan date string */
|
||||||
|
__( 'scanned %s', 'robotstxt-mediaaudit' ),
|
||||||
|
$scanned_at
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
echo '</h3>';
|
||||||
|
|
||||||
|
if ( null !== $raw ) {
|
||||||
|
if ( 'google_vision' === $slug ) {
|
||||||
|
$this->render_google_vision_tables( $raw );
|
||||||
|
} elseif ( 'tineye' === $slug ) {
|
||||||
|
$this->render_tineye_table( $raw );
|
||||||
|
} elseif ( 'picdefense' === $slug ) {
|
||||||
|
$this->render_picdefense_table( $raw );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders the Google Cloud Vision result tables.
|
||||||
|
*
|
||||||
|
* @param array<mixed> $raw Decoded raw_response array.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function render_google_vision_tables( array $raw ): void {
|
||||||
|
$responses_val = $raw['responses'] ?? null;
|
||||||
|
if ( ! is_array( $responses_val ) || empty( $responses_val ) ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$first = $responses_val[0] ?? null;
|
||||||
|
if ( ! is_array( $first ) ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$web_val = $first['webDetection'] ?? null;
|
||||||
|
$web = is_array( $web_val ) ? $web_val : array();
|
||||||
|
|
||||||
|
// Pages with matching images.
|
||||||
|
$pages_val = $web['pagesWithMatchingImages'] ?? null;
|
||||||
|
$pages = is_array( $pages_val ) ? $pages_val : array();
|
||||||
|
if ( ! empty( $pages ) ) {
|
||||||
|
echo '<table class="widefat mra-detail-table">';
|
||||||
|
echo '<caption>' . esc_html__( 'Pages with this image', 'robotstxt-mediaaudit' ) . '</caption>';
|
||||||
|
echo '<thead><tr>';
|
||||||
|
echo '<th>' . esc_html__( 'Page URL', 'robotstxt-mediaaudit' ) . '</th>';
|
||||||
|
echo '<th>' . esc_html__( 'Page Title', 'robotstxt-mediaaudit' ) . '</th>';
|
||||||
|
echo '</tr></thead><tbody>';
|
||||||
|
foreach ( $pages as $page ) {
|
||||||
|
if ( ! is_array( $page ) ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$page_url_val = $page['url'] ?? null;
|
||||||
|
$page_url = is_string( $page_url_val ) ? $page_url_val : '';
|
||||||
|
$page_title_val = $page['pageTitle'] ?? null;
|
||||||
|
$page_title = is_string( $page_title_val ) ? $page_title_val : '';
|
||||||
|
echo '<tr>';
|
||||||
|
echo '<td><a href="' . esc_url( $page_url ) . '" target="_blank">' . esc_html( $page_url ) . '</a></td>';
|
||||||
|
echo '<td>' . esc_html( $page_title ) . '</td>';
|
||||||
|
echo '</tr>';
|
||||||
|
}
|
||||||
|
echo '</tbody></table>';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Full matching images.
|
||||||
|
$full_val = $web['fullMatchingImages'] ?? null;
|
||||||
|
$full = is_array( $full_val ) ? $full_val : array();
|
||||||
|
if ( ! empty( $full ) ) {
|
||||||
|
echo '<table class="widefat mra-detail-table">';
|
||||||
|
echo '<caption>' . esc_html__( 'Full image matches', 'robotstxt-mediaaudit' ) . '</caption>';
|
||||||
|
echo '<thead><tr><th>' . esc_html__( 'Image URL', 'robotstxt-mediaaudit' ) . '</th></tr></thead><tbody>';
|
||||||
|
foreach ( $full as $img ) {
|
||||||
|
if ( ! is_array( $img ) ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$img_url_val = $img['url'] ?? null;
|
||||||
|
$img_url = is_string( $img_url_val ) ? $img_url_val : '';
|
||||||
|
echo '<tr><td><a href="' . esc_url( $img_url ) . '" target="_blank">' . esc_html( $img_url ) . '</a></td></tr>';
|
||||||
|
}
|
||||||
|
echo '</tbody></table>';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Partial matching images.
|
||||||
|
$partial_val = $web['partialMatchingImages'] ?? null;
|
||||||
|
$partial = is_array( $partial_val ) ? $partial_val : array();
|
||||||
|
if ( ! empty( $partial ) ) {
|
||||||
|
echo '<table class="widefat mra-detail-table">';
|
||||||
|
echo '<caption>' . esc_html__( 'Partial image matches', 'robotstxt-mediaaudit' ) . '</caption>';
|
||||||
|
echo '<thead><tr><th>' . esc_html__( 'Image URL', 'robotstxt-mediaaudit' ) . '</th></tr></thead><tbody>';
|
||||||
|
foreach ( $partial as $img ) {
|
||||||
|
if ( ! is_array( $img ) ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$img_url_val = $img['url'] ?? null;
|
||||||
|
$img_url = is_string( $img_url_val ) ? $img_url_val : '';
|
||||||
|
echo '<tr><td><a href="' . esc_url( $img_url ) . '" target="_blank">' . esc_html( $img_url ) . '</a></td></tr>';
|
||||||
|
}
|
||||||
|
echo '</tbody></table>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders the TinEye backlinks table, sorted by crawl_date DESC.
|
||||||
|
*
|
||||||
|
* @param array<mixed> $raw Decoded raw_response array.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function render_tineye_table( array $raw ): void {
|
||||||
|
$results_val = $raw['results'] ?? null;
|
||||||
|
$results = is_array( $results_val ) ? $results_val : array();
|
||||||
|
$matches_val = $results['matches'] ?? null;
|
||||||
|
$matches = is_array( $matches_val ) ? $matches_val : array();
|
||||||
|
|
||||||
|
// Flatten all backlinks from all matches.
|
||||||
|
$backlinks = array();
|
||||||
|
foreach ( $matches as $match ) {
|
||||||
|
if ( ! is_array( $match ) ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$bl_val = $match['backlinks'] ?? null;
|
||||||
|
$bls = is_array( $bl_val ) ? $bl_val : array();
|
||||||
|
foreach ( $bls as $bl ) {
|
||||||
|
if ( is_array( $bl ) ) {
|
||||||
|
$backlinks[] = $bl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( empty( $backlinks ) ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort by crawl_date DESC.
|
||||||
|
usort(
|
||||||
|
$backlinks,
|
||||||
|
static function ( array $a, array $b ): int {
|
||||||
|
$da = isset( $a['crawl_date'] ) && is_string( $a['crawl_date'] ) ? $a['crawl_date'] : '';
|
||||||
|
$db = isset( $b['crawl_date'] ) && is_string( $b['crawl_date'] ) ? $b['crawl_date'] : '';
|
||||||
|
return strcmp( $db, $da );
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
echo '<table class="widefat mra-detail-table">';
|
||||||
|
echo '<caption>' . esc_html__( 'Backlinks found by TinEye (sorted by crawl date, newest first)', 'robotstxt-mediaaudit' ) . '</caption>';
|
||||||
|
echo '<thead><tr>';
|
||||||
|
echo '<th>' . esc_html__( 'Page URL', 'robotstxt-mediaaudit' ) . '</th>';
|
||||||
|
echo '<th>' . esc_html__( 'Image URL', 'robotstxt-mediaaudit' ) . '</th>';
|
||||||
|
echo '<th>' . esc_html__( 'Crawl date', 'robotstxt-mediaaudit' ) . '</th>';
|
||||||
|
echo '</tr></thead><tbody>';
|
||||||
|
|
||||||
|
foreach ( $backlinks as $bl ) {
|
||||||
|
// 'backlink' = page URL; 'url' = direct image URL.
|
||||||
|
$page_url_val = $bl['backlink'] ?? null;
|
||||||
|
$page_url = is_string( $page_url_val ) ? $page_url_val : '';
|
||||||
|
$img_url_val = $bl['url'] ?? null;
|
||||||
|
$img_url = is_string( $img_url_val ) ? $img_url_val : '';
|
||||||
|
$bl_date_val = $bl['crawl_date'] ?? null;
|
||||||
|
$bl_date = is_string( $bl_date_val ) ? $bl_date_val : '';
|
||||||
|
echo '<tr>';
|
||||||
|
echo '<td><a href="' . esc_url( $page_url ) . '" target="_blank">' . esc_html( $page_url ) . '</a></td>';
|
||||||
|
echo '<td><a href="' . esc_url( $img_url ) . '" target="_blank">' . esc_html( $img_url ) . '</a></td>';
|
||||||
|
echo '<td>' . esc_html( $bl_date ) . '</td>';
|
||||||
|
echo '</tr>';
|
||||||
|
}
|
||||||
|
|
||||||
|
echo '</tbody></table>';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders the PicDefense scan result sections.
|
||||||
|
*
|
||||||
|
* @param array<mixed> $raw Decoded raw_response array.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function render_picdefense_table( array $raw ): void {
|
||||||
|
$data_raw = $raw['data'] ?? null;
|
||||||
|
$data_arr = is_array( $data_raw ) ? $data_raw : array();
|
||||||
|
$data = isset( $data_arr[0] ) && is_array( $data_arr[0] ) ? $data_arr[0] : array();
|
||||||
|
|
||||||
|
if ( empty( $data ) ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// picRisk badge.
|
||||||
|
$picrisk_val = $data['picrisk'] ?? null;
|
||||||
|
$picrisk = is_string( $picrisk_val ) ? strtolower( $picrisk_val ) : '';
|
||||||
|
if ( '' !== $picrisk ) {
|
||||||
|
echo '<p><span class="mra-badge mra-picrisk-' . esc_attr( $picrisk ) . '">' . esc_html( ucfirst( $picrisk ) ) . '</span></p>';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Risk flags.
|
||||||
|
$flags = array();
|
||||||
|
if ( true === ( $data['face'] ?? null ) ) {
|
||||||
|
$flags[] = esc_html__( 'Face detected', 'robotstxt-mediaaudit' );
|
||||||
|
}
|
||||||
|
if ( true === ( $data['logo'] ?? null ) ) {
|
||||||
|
$flags[] = esc_html__( 'Logo detected', 'robotstxt-mediaaudit' );
|
||||||
|
}
|
||||||
|
if ( true === ( $data['landmark'] ?? null ) ) {
|
||||||
|
$flags[] = esc_html__( 'Landmark detected', 'robotstxt-mediaaudit' );
|
||||||
|
}
|
||||||
|
if ( true === ( $data['stock'] ?? null ) ) {
|
||||||
|
$flags[] = esc_html__( 'Stock image', 'robotstxt-mediaaudit' );
|
||||||
|
}
|
||||||
|
if ( true === ( $data['exif_copyrighted'] ?? null ) ) {
|
||||||
|
$holder_val = $data['exif_copyrightHolder'] ?? '';
|
||||||
|
$holder = is_string( $holder_val ) ? $holder_val : '';
|
||||||
|
$flags[] = sprintf(
|
||||||
|
/* translators: %s: copyright holder name */
|
||||||
|
esc_html__( 'EXIF copyright: %s', 'robotstxt-mediaaudit' ),
|
||||||
|
esc_html( $holder )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
echo '<h4>' . esc_html__( 'Risk flags', 'robotstxt-mediaaudit' ) . '</h4>';
|
||||||
|
if ( empty( $flags ) ) {
|
||||||
|
echo '<p>' . esc_html__( 'No risk flags detected.', 'robotstxt-mediaaudit' ) . '</p>';
|
||||||
|
} else {
|
||||||
|
echo '<ul>';
|
||||||
|
foreach ( $flags as $flag ) {
|
||||||
|
echo '<li>' . $flag . '</li>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- already escaped above.
|
||||||
|
}
|
||||||
|
echo '</ul>';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Backlinks table.
|
||||||
|
$backlinks_raw = $data['backlinks'] ?? null;
|
||||||
|
$backlinks = is_array( $backlinks_raw ) ? $backlinks_raw : array();
|
||||||
|
|
||||||
|
// Filter to valid entries only.
|
||||||
|
$valid_backlinks = array();
|
||||||
|
foreach ( $backlinks as $bl ) {
|
||||||
|
if ( is_array( $bl ) ) {
|
||||||
|
$valid_backlinks[] = $bl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ! empty( $valid_backlinks ) ) {
|
||||||
|
// Sort by similarity_score DESC.
|
||||||
|
usort(
|
||||||
|
$valid_backlinks,
|
||||||
|
static function ( array $a, array $b ): int {
|
||||||
|
$sa = isset( $a['similarity_score'] ) && is_numeric( $a['similarity_score'] ) ? (float) $a['similarity_score'] : 0.0;
|
||||||
|
$sb = isset( $b['similarity_score'] ) && is_numeric( $b['similarity_score'] ) ? (float) $b['similarity_score'] : 0.0;
|
||||||
|
if ( $sb > $sa ) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if ( $sb < $sa ) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
echo '<table class="widefat mra-detail-table">';
|
||||||
|
echo '<caption>' . esc_html__( 'Backlinks found by PicDefense', 'robotstxt-mediaaudit' ) . '</caption>';
|
||||||
|
echo '<thead><tr>';
|
||||||
|
echo '<th>' . esc_html__( 'Page URL', 'robotstxt-mediaaudit' ) . '</th>';
|
||||||
|
echo '<th>' . esc_html__( 'Image URL', 'robotstxt-mediaaudit' ) . '</th>';
|
||||||
|
echo '<th>' . esc_html__( 'Similarity', 'robotstxt-mediaaudit' ) . '</th>';
|
||||||
|
echo '</tr></thead><tbody>';
|
||||||
|
|
||||||
|
foreach ( $valid_backlinks as $bl ) {
|
||||||
|
$page_url_val = $bl['url'] ?? null;
|
||||||
|
$page_url = is_string( $page_url_val ) ? $page_url_val : '';
|
||||||
|
$img_url_val = $bl['backlink_image_url'] ?? null;
|
||||||
|
$img_url = is_string( $img_url_val ) ? $img_url_val : '';
|
||||||
|
$score_val = $bl['similarity_score'] ?? null;
|
||||||
|
$score = is_numeric( $score_val ) ? number_format( (float) $score_val, 2 ) : '';
|
||||||
|
echo '<tr>';
|
||||||
|
echo '<td><a href="' . esc_url( $page_url ) . '" target="_blank">' . esc_html( $page_url ) . '</a></td>';
|
||||||
|
echo '<td><a href="' . esc_url( $img_url ) . '" target="_blank">' . esc_html( $img_url ) . '</a></td>';
|
||||||
|
echo '<td>' . esc_html( $score ) . '</td>';
|
||||||
|
echo '</tr>';
|
||||||
|
}
|
||||||
|
|
||||||
|
echo '</tbody></table>';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Labels.
|
||||||
|
$labels_val = $data['labels'] ?? null;
|
||||||
|
if ( is_array( $labels_val ) ) {
|
||||||
|
$labels_with_score_val = $labels_val['labelsWithScore'] ?? null;
|
||||||
|
$labels_with_score = is_array( $labels_with_score_val ) ? $labels_with_score_val : array();
|
||||||
|
if ( ! empty( $labels_with_score ) ) {
|
||||||
|
$label_str_val = $labels_with_score[0] ?? null;
|
||||||
|
$label_str = is_string( $label_str_val ) ? $label_str_val : '';
|
||||||
|
if ( '' !== $label_str ) {
|
||||||
|
echo '<h4>' . esc_html__( 'Labels', 'robotstxt-mediaaudit' ) . '</h4>';
|
||||||
|
echo '<p>' . esc_html( $label_str ) . '</p>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
namespace MediaRightsAudit\Admin;
|
namespace MediaRightsAudit\Admin;
|
||||||
|
|
||||||
|
use MediaRightsAudit\Admin\AttachmentDetailPage;
|
||||||
use MediaRightsAudit\External\ExternalScanner;
|
use MediaRightsAudit\External\ExternalScanner;
|
||||||
use MediaRightsAudit\External\ResultsConsolidator;
|
use MediaRightsAudit\External\ResultsConsolidator;
|
||||||
use MediaRightsAudit\Internal\AttachmentIndexer;
|
use MediaRightsAudit\Internal\AttachmentIndexer;
|
||||||
|
|
@ -269,7 +270,7 @@ class AuditPage {
|
||||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
// phpcs:ignore WordPress.DB.DirectDatabaseQuery,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||||
$rows = $wpdb->get_results(
|
$rows = $wpdb->get_results(
|
||||||
$wpdb->prepare(
|
$wpdb->prepare(
|
||||||
"SELECT provider, match_count, top_domains, created_at
|
"SELECT provider, match_count, top_domains, raw_response, created_at
|
||||||
FROM {$wpdb->prefix}mra_external_results
|
FROM {$wpdb->prefix}mra_external_results
|
||||||
WHERE attachment_id = %d
|
WHERE attachment_id = %d
|
||||||
ORDER BY provider ASC",
|
ORDER BY provider ASC",
|
||||||
|
|
@ -285,6 +286,7 @@ class AuditPage {
|
||||||
$provider_names = array(
|
$provider_names = array(
|
||||||
'google_vision' => 'Google Cloud Vision',
|
'google_vision' => 'Google Cloud Vision',
|
||||||
'tineye' => 'TinEye',
|
'tineye' => 'TinEye',
|
||||||
|
'picdefense' => 'PicDefense',
|
||||||
);
|
);
|
||||||
|
|
||||||
$html = '<h4 class="mra-section-heading">' . esc_html__( 'External Scan Results', 'robotstxt-mediaaudit' ) . '</h4>';
|
$html = '<h4 class="mra-section-heading">' . esc_html__( 'External Scan Results', 'robotstxt-mediaaudit' ) . '</h4>';
|
||||||
|
|
@ -303,6 +305,12 @@ class AuditPage {
|
||||||
$td_val = $row['top_domains'] ?? null;
|
$td_val = $row['top_domains'] ?? null;
|
||||||
$td_raw = is_string( $td_val ) ? json_decode( $td_val, true ) : null;
|
$td_raw = is_string( $td_val ) ? json_decode( $td_val, true ) : null;
|
||||||
$domains = is_array( $td_raw ) ? $td_raw : array();
|
$domains = is_array( $td_raw ) ? $td_raw : array();
|
||||||
|
$rr_val = $row['raw_response'] ?? null;
|
||||||
|
$raw = null;
|
||||||
|
if ( is_string( $rr_val ) && '' !== $rr_val ) {
|
||||||
|
$rr_decoded = json_decode( $rr_val, true );
|
||||||
|
$raw = is_array( $rr_decoded ) ? $rr_decoded : null;
|
||||||
|
}
|
||||||
|
|
||||||
$html .= '<div class="mra-provider-result">';
|
$html .= '<div class="mra-provider-result">';
|
||||||
$html .= '<strong>' . esc_html( $name ) . '</strong> — ';
|
$html .= '<strong>' . esc_html( $name ) . '</strong> — ';
|
||||||
|
|
@ -319,6 +327,19 @@ class AuditPage {
|
||||||
$html .= esc_html__( 'No matches', 'robotstxt-mediaaudit' );
|
$html .= esc_html__( 'No matches', 'robotstxt-mediaaudit' );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ( 'picdefense' === $slug && null !== $raw ) {
|
||||||
|
$pd_data_raw = $raw['data'] ?? null;
|
||||||
|
$pd_arr = is_array( $pd_data_raw ) ? $pd_data_raw : array();
|
||||||
|
$pd_data = isset( $pd_arr[0] ) && is_array( $pd_arr[0] ) ? $pd_arr[0] : null;
|
||||||
|
if ( is_array( $pd_data ) ) {
|
||||||
|
$pr_val = $pd_data['picrisk'] ?? null;
|
||||||
|
$picrisk = is_string( $pr_val ) ? strtolower( $pr_val ) : '';
|
||||||
|
if ( '' !== $picrisk ) {
|
||||||
|
$html .= ' <span class="mra-badge mra-picrisk-' . esc_attr( $picrisk ) . '">' . esc_html( ucfirst( $picrisk ) ) . '</span>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if ( '' !== $scanned_at ) {
|
if ( '' !== $scanned_at ) {
|
||||||
$html .= ' <small>' . esc_html( $scanned_at ) . '</small>';
|
$html .= ' <small>' . esc_html( $scanned_at ) . '</small>';
|
||||||
}
|
}
|
||||||
|
|
@ -364,6 +385,10 @@ class AuditPage {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$html .= '<p class="mra-detail-link"><a href="' . esc_url( AttachmentDetailPage::url( $attachment_id ) ) . '">'
|
||||||
|
. esc_html__( 'View full report →', 'robotstxt-mediaaudit' )
|
||||||
|
. '</a></p>';
|
||||||
|
|
||||||
return $html;
|
return $html;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,8 @@
|
||||||
|
|
||||||
namespace MediaRightsAudit\Admin;
|
namespace MediaRightsAudit\Admin;
|
||||||
|
|
||||||
|
use MediaRightsAudit\Admin\AttachmentDetailPage;
|
||||||
|
|
||||||
if ( ! class_exists( 'WP_List_Table' ) ) {
|
if ( ! class_exists( 'WP_List_Table' ) ) {
|
||||||
require_once ABSPATH . 'wp-admin/includes/class-wp-list-table.php';
|
require_once ABSPATH . 'wp-admin/includes/class-wp-list-table.php';
|
||||||
}
|
}
|
||||||
|
|
@ -172,6 +174,11 @@ class MediaListTable extends \WP_List_Table {
|
||||||
$id,
|
$id,
|
||||||
__( 'View Details', 'robotstxt-mediaaudit' )
|
__( 'View Details', 'robotstxt-mediaaudit' )
|
||||||
),
|
),
|
||||||
|
'full_report' => sprintf(
|
||||||
|
'<a href="%s">%s</a>',
|
||||||
|
esc_url( AttachmentDetailPage::url( $id ) ),
|
||||||
|
__( 'Full Report', 'robotstxt-mediaaudit' )
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
return $title . $this->row_actions( $actions );
|
return $title . $this->row_actions( $actions );
|
||||||
|
|
|
||||||
|
|
@ -80,6 +80,22 @@ class Settings {
|
||||||
'mra_api_credentials'
|
'mra_api_credentials'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
add_settings_field(
|
||||||
|
'picdefense_user_id',
|
||||||
|
__( 'PicDefense User ID', 'robotstxt-mediaaudit' ),
|
||||||
|
array( $this, 'field_picdefense_user_id' ),
|
||||||
|
'robotstxt-mediaaudit-settings',
|
||||||
|
'mra_api_credentials'
|
||||||
|
);
|
||||||
|
|
||||||
|
add_settings_field(
|
||||||
|
'picdefense_api_key',
|
||||||
|
__( 'PicDefense API Key', 'robotstxt-mediaaudit' ),
|
||||||
|
array( $this, 'field_picdefense_api_key' ),
|
||||||
|
'robotstxt-mediaaudit-settings',
|
||||||
|
'mra_api_credentials'
|
||||||
|
);
|
||||||
|
|
||||||
add_settings_section(
|
add_settings_section(
|
||||||
'mra_external_scanning',
|
'mra_external_scanning',
|
||||||
__( 'External Scanning', 'robotstxt-mediaaudit' ),
|
__( 'External Scanning', 'robotstxt-mediaaudit' ),
|
||||||
|
|
@ -126,6 +142,12 @@ class Settings {
|
||||||
$tineye_val = $input['tineye_api_key'] ?? null;
|
$tineye_val = $input['tineye_api_key'] ?? null;
|
||||||
$output['tineye_api_key'] = is_string( $tineye_val ) ? sanitize_text_field( $tineye_val ) : '';
|
$output['tineye_api_key'] = is_string( $tineye_val ) ? sanitize_text_field( $tineye_val ) : '';
|
||||||
|
|
||||||
|
$pd_uid_val = $input['picdefense_user_id'] ?? null;
|
||||||
|
$output['picdefense_user_id'] = is_string( $pd_uid_val ) ? sanitize_text_field( $pd_uid_val ) : '';
|
||||||
|
|
||||||
|
$pd_key_val = $input['picdefense_api_key'] ?? null;
|
||||||
|
$output['picdefense_api_key'] = is_string( $pd_key_val ) ? sanitize_text_field( $pd_key_val ) : '';
|
||||||
|
|
||||||
$batch_val = $input['external_batch_size'] ?? null;
|
$batch_val = $input['external_batch_size'] ?? null;
|
||||||
$batch = is_numeric( $batch_val ) ? (int) $batch_val : 10;
|
$batch = is_numeric( $batch_val ) ? (int) $batch_val : 10;
|
||||||
$output['external_batch_size'] = max( 1, min( 100, $batch ) );
|
$output['external_batch_size'] = max( 1, min( 100, $batch ) );
|
||||||
|
|
@ -265,6 +287,58 @@ class Settings {
|
||||||
<?php
|
<?php
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders the PicDefense User ID field.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function field_picdefense_user_id(): void {
|
||||||
|
$options = (array) get_option( self::OPTION_NAME, array() );
|
||||||
|
$val = $options['picdefense_user_id'] ?? '';
|
||||||
|
$value = is_string( $val ) ? $val : '';
|
||||||
|
?>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="picdefense_user_id"
|
||||||
|
name="<?php echo esc_attr( self::OPTION_NAME ); ?>[picdefense_user_id]"
|
||||||
|
value="<?php echo esc_attr( $value ); ?>"
|
||||||
|
class="regular-text"
|
||||||
|
autocomplete="off"
|
||||||
|
/>
|
||||||
|
<p class="description">
|
||||||
|
<?php esc_html_e( 'Required for PicDefense copyright risk analysis. Your PicDefense account user ID.', 'robotstxt-mediaaudit' ); ?>
|
||||||
|
</p>
|
||||||
|
<?php
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders the PicDefense API key field.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function field_picdefense_api_key(): void {
|
||||||
|
$options = (array) get_option( self::OPTION_NAME, array() );
|
||||||
|
$val = $options['picdefense_api_key'] ?? '';
|
||||||
|
$value = is_string( $val ) ? $val : '';
|
||||||
|
?>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
id="picdefense_api_key"
|
||||||
|
name="<?php echo esc_attr( self::OPTION_NAME ); ?>[picdefense_api_key]"
|
||||||
|
value="<?php echo esc_attr( $value ); ?>"
|
||||||
|
class="regular-text"
|
||||||
|
autocomplete="off"
|
||||||
|
/>
|
||||||
|
<p class="description">
|
||||||
|
<?php esc_html_e( 'Required for PicDefense copyright risk analysis. Your PicDefense API key.', 'robotstxt-mediaaudit' ); ?>
|
||||||
|
—
|
||||||
|
<a href="https://picdefense.io" target="_blank" rel="noopener noreferrer">
|
||||||
|
<?php esc_html_e( 'View pricing', 'robotstxt-mediaaudit' ); ?>
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
<?php
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders the external batch size field.
|
* Renders the external batch size field.
|
||||||
*
|
*
|
||||||
|
|
@ -315,6 +389,43 @@ class Settings {
|
||||||
<?php
|
<?php
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether Google Cloud Vision is configured.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
private function is_google_vision_configured(): bool {
|
||||||
|
$raw = get_option( self::OPTION_NAME, array() );
|
||||||
|
$opts = is_array( $raw ) ? $raw : array();
|
||||||
|
$key = $opts['google_vision_api_key'] ?? '';
|
||||||
|
return is_string( $key ) && '' !== trim( $key );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether TinEye is configured.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
private function is_tineye_configured(): bool {
|
||||||
|
$raw = get_option( self::OPTION_NAME, array() );
|
||||||
|
$opts = is_array( $raw ) ? $raw : array();
|
||||||
|
$key = $opts['tineye_api_key'] ?? '';
|
||||||
|
return is_string( $key ) && '' !== trim( $key );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether PicDefense is configured.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
private function is_picdefense_configured(): bool {
|
||||||
|
$raw = get_option( self::OPTION_NAME, array() );
|
||||||
|
$opts = is_array( $raw ) ? $raw : array();
|
||||||
|
$uid = $opts['picdefense_user_id'] ?? '';
|
||||||
|
$key = $opts['picdefense_api_key'] ?? '';
|
||||||
|
return is_string( $uid ) && '' !== trim( $uid ) && is_string( $key ) && '' !== trim( $key );
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders the Settings admin page.
|
* Renders the Settings admin page.
|
||||||
*
|
*
|
||||||
|
|
@ -324,9 +435,44 @@ class Settings {
|
||||||
if ( ! current_user_can( 'manage_options' ) ) {
|
if ( ! current_user_can( 'manage_options' ) ) {
|
||||||
wp_die( esc_html__( 'You do not have permission to access this page.', 'robotstxt-mediaaudit' ) );
|
wp_die( esc_html__( 'You do not have permission to access this page.', 'robotstxt-mediaaudit' ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$gv_ok = $this->is_google_vision_configured();
|
||||||
|
$te_ok = $this->is_tineye_configured();
|
||||||
|
$pd_ok = $this->is_picdefense_configured();
|
||||||
?>
|
?>
|
||||||
<div class="wrap">
|
<div class="wrap">
|
||||||
<h1><?php echo esc_html( get_admin_page_title() ); ?></h1>
|
<h1><?php echo esc_html( get_admin_page_title() ); ?></h1>
|
||||||
|
|
||||||
|
<div class="mra-provider-status">
|
||||||
|
<strong><?php esc_html_e( 'API Credentials Status', 'robotstxt-mediaaudit' ); ?></strong>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<?php if ( $gv_ok ) : ?>
|
||||||
|
<span class="dashicons dashicons-yes-alt" style="color:green;"></span>
|
||||||
|
<?php else : ?>
|
||||||
|
<span class="dashicons dashicons-warning" style="color:orange;"></span>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php esc_html_e( 'Google Cloud Vision', 'robotstxt-mediaaudit' ); ?>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<?php if ( $te_ok ) : ?>
|
||||||
|
<span class="dashicons dashicons-yes-alt" style="color:green;"></span>
|
||||||
|
<?php else : ?>
|
||||||
|
<span class="dashicons dashicons-warning" style="color:orange;"></span>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php esc_html_e( 'TinEye', 'robotstxt-mediaaudit' ); ?>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<?php if ( $pd_ok ) : ?>
|
||||||
|
<span class="dashicons dashicons-yes-alt" style="color:green;"></span>
|
||||||
|
<?php else : ?>
|
||||||
|
<span class="dashicons dashicons-warning" style="color:orange;"></span>
|
||||||
|
<?php endif; ?>
|
||||||
|
PicDefense
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
<form method="post" action="options.php">
|
<form method="post" action="options.php">
|
||||||
<?php
|
<?php
|
||||||
settings_fields( self::OPTION_GROUP );
|
settings_fields( self::OPTION_GROUP );
|
||||||
|
|
|
||||||
|
|
@ -128,7 +128,11 @@ class ToolsPage {
|
||||||
|
|
||||||
case 'external':
|
case 'external':
|
||||||
ExternalScanner::queue_all_pending();
|
ExternalScanner::queue_all_pending();
|
||||||
ExternalScanner::scan_batch();
|
$raw_opts = get_option( 'robotstxt_mediaaudit_settings', array() );
|
||||||
|
$opts_arr = is_array( $raw_opts ) ? $raw_opts : array();
|
||||||
|
$bs_setting = $opts_arr['external_batch_size'] ?? null;
|
||||||
|
$ajax_bs = is_numeric( $bs_setting ) ? max( 1, (int) $bs_setting ) : 10;
|
||||||
|
ExternalScanner::scan_batch( $ajax_bs );
|
||||||
$counts = ExternalScanner::get_status_counts();
|
$counts = ExternalScanner::get_status_counts();
|
||||||
$remaining = $counts['queued'] ?? 0;
|
$remaining = $counts['queued'] ?? 0;
|
||||||
$done = ( $counts['scanned'] ?? 0 ) + ( $counts['matches'] ?? 0 ) + ( $counts['error'] ?? 0 );
|
$done = ( $counts['scanned'] ?? 0 ) + ( $counts['matches'] ?? 0 ) + ( $counts['error'] ?? 0 );
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
namespace MediaRightsAudit\Core;
|
namespace MediaRightsAudit\Core;
|
||||||
|
|
||||||
|
use MediaRightsAudit\Admin\AttachmentDetailPage;
|
||||||
use MediaRightsAudit\Admin\AuditPage;
|
use MediaRightsAudit\Admin\AuditPage;
|
||||||
use MediaRightsAudit\Admin\Settings;
|
use MediaRightsAudit\Admin\Settings;
|
||||||
use MediaRightsAudit\Admin\ToolsPage;
|
use MediaRightsAudit\Admin\ToolsPage;
|
||||||
|
|
@ -14,6 +15,7 @@ use MediaRightsAudit\CLI\Command;
|
||||||
use MediaRightsAudit\External\AbstractProvider;
|
use MediaRightsAudit\External\AbstractProvider;
|
||||||
use MediaRightsAudit\External\ExternalScanner;
|
use MediaRightsAudit\External\ExternalScanner;
|
||||||
use MediaRightsAudit\External\GoogleVisionProvider;
|
use MediaRightsAudit\External\GoogleVisionProvider;
|
||||||
|
use MediaRightsAudit\External\PicDefenseProvider;
|
||||||
use MediaRightsAudit\External\TinEyeProvider;
|
use MediaRightsAudit\External\TinEyeProvider;
|
||||||
use MediaRightsAudit\Internal\AttachmentIndexer;
|
use MediaRightsAudit\Internal\AttachmentIndexer;
|
||||||
use MediaRightsAudit\Internal\UsageScanner;
|
use MediaRightsAudit\Internal\UsageScanner;
|
||||||
|
|
@ -46,6 +48,13 @@ class Plugin {
|
||||||
*/
|
*/
|
||||||
private ToolsPage $tools_page;
|
private ToolsPage $tools_page;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attachment detail page controller.
|
||||||
|
*
|
||||||
|
* @var AttachmentDetailPage
|
||||||
|
*/
|
||||||
|
private AttachmentDetailPage $detail_page;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialises dependencies.
|
* Initialises dependencies.
|
||||||
*/
|
*/
|
||||||
|
|
@ -53,6 +62,7 @@ class Plugin {
|
||||||
$this->settings = new Settings();
|
$this->settings = new Settings();
|
||||||
$this->audit_page = new AuditPage();
|
$this->audit_page = new AuditPage();
|
||||||
$this->tools_page = new ToolsPage();
|
$this->tools_page = new ToolsPage();
|
||||||
|
$this->detail_page = new AttachmentDetailPage();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -138,6 +148,15 @@ class Plugin {
|
||||||
'robotstxt-mediaaudit-settings',
|
'robotstxt-mediaaudit-settings',
|
||||||
array( $this->settings, 'render' )
|
array( $this->settings, 'render' )
|
||||||
);
|
);
|
||||||
|
|
||||||
|
add_submenu_page(
|
||||||
|
'', // hidden from menu.
|
||||||
|
__( 'Attachment Report', 'robotstxt-mediaaudit' ),
|
||||||
|
'',
|
||||||
|
'edit_posts',
|
||||||
|
'robotstxt-mediaaudit-detail',
|
||||||
|
array( $this->detail_page, 'render' )
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -172,9 +191,34 @@ class Plugin {
|
||||||
}
|
}
|
||||||
$list[] = new GoogleVisionProvider();
|
$list[] = new GoogleVisionProvider();
|
||||||
$list[] = new TinEyeProvider();
|
$list[] = new TinEyeProvider();
|
||||||
|
if ( $this->is_provider_configured( 'picdefense' ) ) {
|
||||||
|
$list[] = new PicDefenseProvider();
|
||||||
|
}
|
||||||
return $list;
|
return $list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether a provider has its credentials configured.
|
||||||
|
*
|
||||||
|
* Checks provider-specific option keys in the plugin settings.
|
||||||
|
*
|
||||||
|
* @param string $slug Provider slug (e.g. 'picdefense').
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
private function is_provider_configured( string $slug ): bool {
|
||||||
|
$raw = get_option( Settings::OPTION_NAME, array() );
|
||||||
|
$opts = is_array( $raw ) ? $raw : array();
|
||||||
|
|
||||||
|
if ( 'picdefense' === $slug ) {
|
||||||
|
$uid = $opts['picdefense_user_id'] ?? '';
|
||||||
|
$key = $opts['picdefense_api_key'] ?? '';
|
||||||
|
return is_string( $uid ) && '' !== trim( $uid ) && is_string( $key ) && '' !== trim( $key );
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registers WP-CLI commands when running in CLI context.
|
* Registers WP-CLI commands when running in CLI context.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
5
includes/External/AbstractProvider.php
vendored
5
includes/External/AbstractProvider.php
vendored
|
|
@ -37,7 +37,10 @@ abstract class AbstractProvider {
|
||||||
* @return positive-int
|
* @return positive-int
|
||||||
*/
|
*/
|
||||||
public function rate_limit(): int {
|
public function rate_limit(): int {
|
||||||
return 10;
|
$raw = get_option( 'robotstxt_mediaaudit_settings', array() );
|
||||||
|
$opts = is_array( $raw ) ? $raw : array();
|
||||||
|
$val = $opts['rate_limit_per_minute'] ?? null;
|
||||||
|
return is_numeric( $val ) ? max( 1, (int) $val ) : 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
7
includes/External/ExternalScanner.php
vendored
7
includes/External/ExternalScanner.php
vendored
|
|
@ -138,7 +138,12 @@ class ExternalScanner {
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public static function process_scheduled_batch(): void {
|
public static function process_scheduled_batch(): void {
|
||||||
self::scan_batch();
|
$raw = get_option( 'robotstxt_mediaaudit_settings', array() );
|
||||||
|
$opts = is_array( $raw ) ? $raw : array();
|
||||||
|
$bs_val = $opts['external_batch_size'] ?? null;
|
||||||
|
$batch_size = is_numeric( $bs_val ) ? max( 1, (int) $bs_val ) : 10;
|
||||||
|
|
||||||
|
self::scan_batch( $batch_size );
|
||||||
|
|
||||||
if ( self::get_pending_count() > 0 ) {
|
if ( self::get_pending_count() > 0 ) {
|
||||||
Scheduler::schedule_single( self::AS_HOOK );
|
Scheduler::schedule_single( self::AS_HOOK );
|
||||||
|
|
|
||||||
156
includes/External/PicDefenseProvider.php
vendored
Normal file
156
includes/External/PicDefenseProvider.php
vendored
Normal file
|
|
@ -0,0 +1,156 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* PicDefense copyright risk analysis provider.
|
||||||
|
*
|
||||||
|
* @package MediaRightsAudit\External
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace MediaRightsAudit\External;
|
||||||
|
|
||||||
|
use MediaRightsAudit\Admin\Settings;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Submits images to the PicDefense API and normalises the response
|
||||||
|
* into a ScanResult.
|
||||||
|
*
|
||||||
|
* Uses POST-based image URL submission with X-API-TOKEN header authentication.
|
||||||
|
* Reads credentials (picdefense_user_id and picdefense_api_key) from plugin settings.
|
||||||
|
* Match count reflects backlink_count from the API response; top domains are
|
||||||
|
* extracted from the found_at array.
|
||||||
|
*/
|
||||||
|
class PicDefenseProvider extends AbstractProvider {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PicDefense API endpoint.
|
||||||
|
*/
|
||||||
|
const ENDPOINT = 'https://app.picdefense.io/apiRouterv2/checkImageRisk';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the machine-readable provider identifier.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function provider_slug(): string {
|
||||||
|
return 'picdefense';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the human-readable provider display name.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function provider_name(): string {
|
||||||
|
return 'PicDefense';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Submits the image URL to PicDefense and returns a ScanResult.
|
||||||
|
*
|
||||||
|
* @param int $attachment_id WordPress attachment ID.
|
||||||
|
* @param string $file_url Public URL of the image.
|
||||||
|
*
|
||||||
|
* @return ScanResult
|
||||||
|
*
|
||||||
|
* @throws \RuntimeException On configuration, HTTP, or parse errors.
|
||||||
|
*/
|
||||||
|
protected function do_scan( int $attachment_id, string $file_url ): ScanResult {
|
||||||
|
list( $user_id, $api_key ) = $this->get_credentials();
|
||||||
|
|
||||||
|
if ( '' === $user_id || '' === $api_key ) {
|
||||||
|
throw new \RuntimeException( 'PicDefense credentials are not configured.' );
|
||||||
|
}
|
||||||
|
|
||||||
|
$body = wp_json_encode( array( 'url' => $file_url ) );
|
||||||
|
if ( false === $body ) {
|
||||||
|
throw new \RuntimeException( 'Failed to encode PicDefense request body.' );
|
||||||
|
}
|
||||||
|
|
||||||
|
$response = wp_remote_post(
|
||||||
|
self::ENDPOINT,
|
||||||
|
array(
|
||||||
|
'headers' => array(
|
||||||
|
'Content-Type' => 'application/json; charset=utf-8',
|
||||||
|
'X-API-TOKEN' => $user_id . ':' . $api_key,
|
||||||
|
),
|
||||||
|
'body' => $body,
|
||||||
|
'timeout' => 60,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
if ( is_wp_error( $response ) ) {
|
||||||
|
throw new \RuntimeException( $response->get_error_message() ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
|
||||||
|
}
|
||||||
|
|
||||||
|
$http_code = (int) wp_remote_retrieve_response_code( $response );
|
||||||
|
if ( 200 !== $http_code ) {
|
||||||
|
throw new \RuntimeException(
|
||||||
|
sprintf( 'PicDefense API returned HTTP %d.', $http_code ) // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$raw_body = wp_remote_retrieve_body( $response );
|
||||||
|
$decoded = json_decode( $raw_body, true );
|
||||||
|
|
||||||
|
if ( ! is_array( $decoded ) ) {
|
||||||
|
throw new \RuntimeException( 'Failed to parse PicDefense API response.' );
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->parse_response( $decoded );
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// Private helpers
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads PicDefense credentials from plugin settings.
|
||||||
|
*
|
||||||
|
* @return array{string, string} [user_id, api_key] — empty strings if not configured.
|
||||||
|
*/
|
||||||
|
private function get_credentials(): array {
|
||||||
|
$raw = get_option( Settings::OPTION_NAME, array() );
|
||||||
|
$opts = is_array( $raw ) ? $raw : array();
|
||||||
|
$uid_val = $opts['picdefense_user_id'] ?? null;
|
||||||
|
$key_val = $opts['picdefense_api_key'] ?? null;
|
||||||
|
$user_id = is_string( $uid_val ) ? trim( $uid_val ) : '';
|
||||||
|
$api_key = is_string( $key_val ) ? trim( $key_val ) : '';
|
||||||
|
return array( $user_id, $api_key );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses a decoded PicDefense API response into a ScanResult.
|
||||||
|
*
|
||||||
|
* @param array<mixed, mixed> $decoded json_decode()'d API response.
|
||||||
|
*
|
||||||
|
* @return ScanResult
|
||||||
|
*
|
||||||
|
* @throws \RuntimeException When the API response indicates failure.
|
||||||
|
*/
|
||||||
|
private function parse_response( array $decoded ): ScanResult {
|
||||||
|
$status_val = $decoded['status'] ?? null;
|
||||||
|
$status = is_numeric( $status_val ) ? (int) $status_val : 0;
|
||||||
|
if ( 1 !== $status ) {
|
||||||
|
throw new \RuntimeException( 'PicDefense API returned non-success status.' );
|
||||||
|
}
|
||||||
|
|
||||||
|
$data_raw = $decoded['data'] ?? null;
|
||||||
|
$data_arr = is_array( $data_raw ) ? $data_raw : array();
|
||||||
|
$data = isset( $data_arr[0] ) && is_array( $data_arr[0] ) ? $data_arr[0] : array();
|
||||||
|
|
||||||
|
$bc_val = $data['backlink_count'] ?? null;
|
||||||
|
$match_count = is_numeric( $bc_val ) ? (int) $bc_val : 0;
|
||||||
|
|
||||||
|
$found_at_raw = $data['found_at'] ?? null;
|
||||||
|
$found_at = is_array( $found_at_raw ) ? $found_at_raw : array();
|
||||||
|
$found_at = array_slice( $found_at, 0, 10 );
|
||||||
|
|
||||||
|
$top_domains = array();
|
||||||
|
foreach ( $found_at as $domain ) {
|
||||||
|
if ( is_string( $domain ) && '' !== $domain ) {
|
||||||
|
$top_domains[ $domain ] = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ScanResult( $match_count, $top_domains, $decoded );
|
||||||
|
}
|
||||||
|
}
|
||||||
7
includes/External/TinEyeProvider.php
vendored
7
includes/External/TinEyeProvider.php
vendored
|
|
@ -136,9 +136,10 @@ class TinEyeProvider extends AbstractProvider {
|
||||||
if ( ! is_array( $bl ) ) {
|
if ( ! is_array( $bl ) ) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
$url_val = $bl['url'] ?? null;
|
// 'backlink' is the page URL; 'url' is the direct image URL.
|
||||||
if ( is_string( $url_val ) && '' !== $url_val ) {
|
$page_url_val = $bl['backlink'] ?? null;
|
||||||
$backlink_items[] = array( 'url' => $url_val );
|
if ( is_string( $page_url_val ) && '' !== $page_url_val ) {
|
||||||
|
$backlink_items[] = array( 'url' => $page_url_val );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -2,7 +2,7 @@
|
||||||
# This file is distributed under the GPL-3.0-or-later.
|
# This file is distributed under the GPL-3.0-or-later.
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: Media Audit (by ROBOTSTXT) 1.2.0\n"
|
"Project-Id-Version: Media Audit (by ROBOTSTXT) 1.5.0\n"
|
||||||
"Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/robotstxt-"
|
"Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/robotstxt-"
|
||||||
"mediaaudit\n"
|
"mediaaudit\n"
|
||||||
"POT-Creation-Date: 2026-05-02T07:36:54+00:00\n"
|
"POT-Creation-Date: 2026-05-02T07:36:54+00:00\n"
|
||||||
|
|
@ -985,6 +985,124 @@ msgstr "Sincronitza el registre de proveïdors"
|
||||||
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."
|
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."
|
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."
|
||||||
|
|
||||||
|
#: includes/Admin/AttachmentDetailPage.php
|
||||||
|
msgid "Full Report"
|
||||||
|
msgstr "Informe complet"
|
||||||
|
|
||||||
|
#: includes/Admin/AttachmentDetailPage.php
|
||||||
|
msgid "View full report →"
|
||||||
|
msgstr "Veure l'informe complet →"
|
||||||
|
|
||||||
|
#: includes/Core/Plugin.php
|
||||||
|
msgid "Attachment Report"
|
||||||
|
msgstr "Informe del fitxer adjunt"
|
||||||
|
|
||||||
|
#: includes/Admin/AttachmentDetailPage.php
|
||||||
|
msgid "File Information"
|
||||||
|
msgstr "Informació del fitxer"
|
||||||
|
|
||||||
|
#: includes/Admin/AttachmentDetailPage.php
|
||||||
|
msgid "Pages with this image"
|
||||||
|
msgstr "Pàgines amb aquesta imatge"
|
||||||
|
|
||||||
|
#: includes/Admin/AttachmentDetailPage.php
|
||||||
|
msgid "Full image matches"
|
||||||
|
msgstr "Coincidències exactes d'imatge"
|
||||||
|
|
||||||
|
#: includes/Admin/AttachmentDetailPage.php
|
||||||
|
msgid "Partial image matches"
|
||||||
|
msgstr "Coincidències parcials d'imatge"
|
||||||
|
|
||||||
|
#: includes/Admin/AttachmentDetailPage.php
|
||||||
|
msgid "Backlinks found by TinEye (sorted by crawl date, newest first)"
|
||||||
|
msgstr "Backlinks trobats per TinEye (ordenats per data de rastreig, el més recent primer)"
|
||||||
|
|
||||||
|
#: includes/Admin/AttachmentDetailPage.php
|
||||||
|
msgid "Page URL"
|
||||||
|
msgstr "URL de la pàgina"
|
||||||
|
|
||||||
|
#: includes/Admin/AttachmentDetailPage.php
|
||||||
|
msgid "Crawl date"
|
||||||
|
msgstr "Data de rastreig"
|
||||||
|
|
||||||
|
#: includes/Admin/AttachmentDetailPage.php
|
||||||
|
msgid "Page Title"
|
||||||
|
msgstr "Títol de la pàgina"
|
||||||
|
|
||||||
|
#: includes/Admin/AttachmentDetailPage.php
|
||||||
|
msgid "Image URL"
|
||||||
|
msgstr "URL de la imatge"
|
||||||
|
|
||||||
|
#: includes/Admin/AttachmentDetailPage.php
|
||||||
|
msgid "No external scan data available."
|
||||||
|
msgstr "No hi ha dades d'exploració externa disponibles."
|
||||||
|
|
||||||
|
#: includes/Admin/AttachmentDetailPage.php
|
||||||
|
msgid "File size"
|
||||||
|
msgstr "Mida del fitxer"
|
||||||
|
|
||||||
|
#: includes/Admin/AttachmentDetailPage.php
|
||||||
|
msgid "← Media Audit"
|
||||||
|
msgstr "← Auditoria de mitjans"
|
||||||
|
|
||||||
|
#: includes/Admin/AttachmentDetailPage.php
|
||||||
|
msgid "Internal scan date"
|
||||||
|
msgstr "Data de l'exploració interna"
|
||||||
|
|
||||||
|
#: includes/Admin/AttachmentDetailPage.php
|
||||||
|
#, php-format
|
||||||
|
msgid "scanned %s"
|
||||||
|
msgstr "explorat el %s"
|
||||||
|
|
||||||
|
#: includes/Admin/Settings.php
|
||||||
|
msgid "PicDefense User ID"
|
||||||
|
msgstr "ID d'usuari PicDefense"
|
||||||
|
|
||||||
|
#: includes/Admin/Settings.php
|
||||||
|
msgid "PicDefense API Key"
|
||||||
|
msgstr "Clau API de PicDefense"
|
||||||
|
|
||||||
|
#: includes/Admin/AttachmentDetailPage.php
|
||||||
|
msgid "Face detected"
|
||||||
|
msgstr "Cara detectada"
|
||||||
|
|
||||||
|
#: includes/Admin/AttachmentDetailPage.php
|
||||||
|
msgid "Logo detected"
|
||||||
|
msgstr "Logotip detectat"
|
||||||
|
|
||||||
|
#: includes/Admin/AttachmentDetailPage.php
|
||||||
|
msgid "Landmark detected"
|
||||||
|
msgstr "Monument detectat"
|
||||||
|
|
||||||
|
#: includes/Admin/AttachmentDetailPage.php
|
||||||
|
msgid "Stock image"
|
||||||
|
msgstr "Imatge de banc"
|
||||||
|
|
||||||
|
#: includes/Admin/AttachmentDetailPage.php
|
||||||
|
#, php-format
|
||||||
|
msgid "EXIF copyright: %s"
|
||||||
|
msgstr "Copyright EXIF: %s"
|
||||||
|
|
||||||
|
#: includes/Admin/AttachmentDetailPage.php
|
||||||
|
msgid "Similarity"
|
||||||
|
msgstr "Similitud"
|
||||||
|
|
||||||
|
#: includes/Admin/AttachmentDetailPage.php
|
||||||
|
msgid "Risk flags"
|
||||||
|
msgstr "Indicadors de risc"
|
||||||
|
|
||||||
|
#: includes/Admin/AttachmentDetailPage.php
|
||||||
|
msgid "Labels"
|
||||||
|
msgstr "Etiquetes"
|
||||||
|
|
||||||
|
#: includes/Admin/AttachmentDetailPage.php
|
||||||
|
msgid "Backlinks found by PicDefense"
|
||||||
|
msgstr "Backlinks trobats per PicDefense"
|
||||||
|
|
||||||
|
#: includes/Admin/AttachmentDetailPage.php
|
||||||
|
msgid "No risk flags detected."
|
||||||
|
msgstr "No s'han detectat indicadors de risc."
|
||||||
|
|
||||||
#~ 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.
|
|
@ -2,7 +2,7 @@
|
||||||
# This file is distributed under the GPL-3.0-or-later.
|
# This file is distributed under the GPL-3.0-or-later.
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: Media Audit (by ROBOTSTXT) 1.2.0\n"
|
"Project-Id-Version: Media Audit (by ROBOTSTXT) 1.5.0\n"
|
||||||
"Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/robotstxt-"
|
"Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/robotstxt-"
|
||||||
"mediaaudit\n"
|
"mediaaudit\n"
|
||||||
"POT-Creation-Date: 2026-05-02T07:36:54+00:00\n"
|
"POT-Creation-Date: 2026-05-02T07:36:54+00:00\n"
|
||||||
|
|
@ -981,6 +981,124 @@ msgstr "Sincronizar registro de proveedores"
|
||||||
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."
|
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."
|
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."
|
||||||
|
|
||||||
|
#: includes/Admin/AttachmentDetailPage.php
|
||||||
|
msgid "Full Report"
|
||||||
|
msgstr "Informe completo"
|
||||||
|
|
||||||
|
#: includes/Admin/AttachmentDetailPage.php
|
||||||
|
msgid "View full report →"
|
||||||
|
msgstr "Ver informe completo →"
|
||||||
|
|
||||||
|
#: includes/Core/Plugin.php
|
||||||
|
msgid "Attachment Report"
|
||||||
|
msgstr "Informe del archivo adjunto"
|
||||||
|
|
||||||
|
#: includes/Admin/AttachmentDetailPage.php
|
||||||
|
msgid "File Information"
|
||||||
|
msgstr "Información del archivo"
|
||||||
|
|
||||||
|
#: includes/Admin/AttachmentDetailPage.php
|
||||||
|
msgid "Pages with this image"
|
||||||
|
msgstr "Páginas con esta imagen"
|
||||||
|
|
||||||
|
#: includes/Admin/AttachmentDetailPage.php
|
||||||
|
msgid "Full image matches"
|
||||||
|
msgstr "Coincidencias exactas de imagen"
|
||||||
|
|
||||||
|
#: includes/Admin/AttachmentDetailPage.php
|
||||||
|
msgid "Partial image matches"
|
||||||
|
msgstr "Coincidencias parciales de imagen"
|
||||||
|
|
||||||
|
#: includes/Admin/AttachmentDetailPage.php
|
||||||
|
msgid "Backlinks found by TinEye (sorted by crawl date, newest first)"
|
||||||
|
msgstr "Backlinks encontrados por TinEye (ordenados por fecha de rastreo, más reciente primero)"
|
||||||
|
|
||||||
|
#: includes/Admin/AttachmentDetailPage.php
|
||||||
|
msgid "Page URL"
|
||||||
|
msgstr "URL de la página"
|
||||||
|
|
||||||
|
#: includes/Admin/AttachmentDetailPage.php
|
||||||
|
msgid "Crawl date"
|
||||||
|
msgstr "Fecha de rastreo"
|
||||||
|
|
||||||
|
#: includes/Admin/AttachmentDetailPage.php
|
||||||
|
msgid "Page Title"
|
||||||
|
msgstr "Título de la página"
|
||||||
|
|
||||||
|
#: includes/Admin/AttachmentDetailPage.php
|
||||||
|
msgid "Image URL"
|
||||||
|
msgstr "URL de la imagen"
|
||||||
|
|
||||||
|
#: includes/Admin/AttachmentDetailPage.php
|
||||||
|
msgid "No external scan data available."
|
||||||
|
msgstr "No hay datos de escaneo externo disponibles."
|
||||||
|
|
||||||
|
#: includes/Admin/AttachmentDetailPage.php
|
||||||
|
msgid "File size"
|
||||||
|
msgstr "Tamaño del archivo"
|
||||||
|
|
||||||
|
#: includes/Admin/AttachmentDetailPage.php
|
||||||
|
msgid "← Media Audit"
|
||||||
|
msgstr "← Auditoría de medios"
|
||||||
|
|
||||||
|
#: includes/Admin/AttachmentDetailPage.php
|
||||||
|
msgid "Internal scan date"
|
||||||
|
msgstr "Fecha del escaneo interno"
|
||||||
|
|
||||||
|
#: includes/Admin/AttachmentDetailPage.php
|
||||||
|
#, php-format
|
||||||
|
msgid "scanned %s"
|
||||||
|
msgstr "escaneado el %s"
|
||||||
|
|
||||||
|
#: includes/Admin/Settings.php
|
||||||
|
msgid "PicDefense User ID"
|
||||||
|
msgstr "ID de usuario PicDefense"
|
||||||
|
|
||||||
|
#: includes/Admin/Settings.php
|
||||||
|
msgid "PicDefense API Key"
|
||||||
|
msgstr "Clave API de PicDefense"
|
||||||
|
|
||||||
|
#: includes/Admin/AttachmentDetailPage.php
|
||||||
|
msgid "Face detected"
|
||||||
|
msgstr "Cara detectada"
|
||||||
|
|
||||||
|
#: includes/Admin/AttachmentDetailPage.php
|
||||||
|
msgid "Logo detected"
|
||||||
|
msgstr "Logo detectado"
|
||||||
|
|
||||||
|
#: includes/Admin/AttachmentDetailPage.php
|
||||||
|
msgid "Landmark detected"
|
||||||
|
msgstr "Monumento detectado"
|
||||||
|
|
||||||
|
#: includes/Admin/AttachmentDetailPage.php
|
||||||
|
msgid "Stock image"
|
||||||
|
msgstr "Imagen de banco"
|
||||||
|
|
||||||
|
#: includes/Admin/AttachmentDetailPage.php
|
||||||
|
#, php-format
|
||||||
|
msgid "EXIF copyright: %s"
|
||||||
|
msgstr "Copyright EXIF: %s"
|
||||||
|
|
||||||
|
#: includes/Admin/AttachmentDetailPage.php
|
||||||
|
msgid "Similarity"
|
||||||
|
msgstr "Similitud"
|
||||||
|
|
||||||
|
#: includes/Admin/AttachmentDetailPage.php
|
||||||
|
msgid "Risk flags"
|
||||||
|
msgstr "Indicadores de riesgo"
|
||||||
|
|
||||||
|
#: includes/Admin/AttachmentDetailPage.php
|
||||||
|
msgid "Labels"
|
||||||
|
msgstr "Etiquetas"
|
||||||
|
|
||||||
|
#: includes/Admin/AttachmentDetailPage.php
|
||||||
|
msgid "Backlinks found by PicDefense"
|
||||||
|
msgstr "Backlinks encontrados por PicDefense"
|
||||||
|
|
||||||
|
#: includes/Admin/AttachmentDetailPage.php
|
||||||
|
msgid "No risk flags detected."
|
||||||
|
msgstr "No se detectaron indicadores de riesgo."
|
||||||
|
|
||||||
#~ 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/"
|
||||||
|
|
||||||
|
|
|
||||||
15
readme.txt
15
readme.txt
|
|
@ -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.3.0
|
Stable tag: 1.5.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,19 @@ 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.5.0 =
|
||||||
|
|
||||||
|
* Added PicDefense as a third external scan provider: POST-based reverse image search with risk classification (high/medium/low), backlinks with similarity scores, risk flags (face/logo/landmark/stock/EXIF copyright), and label detection.
|
||||||
|
* Full Report page now shows PicDefense results with picRisk badge, risk flags list, backlinks table sorted by similarity score, and detected labels.
|
||||||
|
* Audit list modal shows picRisk badge inline next to the PicDefense match count.
|
||||||
|
|
||||||
|
= 1.4.0 =
|
||||||
|
|
||||||
|
* Added full attachment detail page (Media Audit → Full Report) showing all raw external scan data: Google Vision pages/full/partial matches with clickable URLs; TinEye backlinks with page URL, image URL, and crawl date sorted newest-first.
|
||||||
|
* Fixed `rate_limit_per_minute` setting having no effect — providers were ignoring it and using a hardcoded value of 10.
|
||||||
|
* Fixed `external_batch_size` setting being ignored by Action Scheduler batch processing.
|
||||||
|
* Fixed TinEye domain extraction using the image CDN URL instead of the webpage URL (API field `backlink`, not `url`).
|
||||||
|
|
||||||
= 1.3.0 =
|
= 1.3.0 =
|
||||||
|
|
||||||
* Added per-provider scan tracking table (`mra_provider_status`) so each API is tracked independently.
|
* Added per-provider scan tracking table (`mra_provider_status`) so each API is tracked independently.
|
||||||
|
|
|
||||||
|
|
@ -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.3.0
|
* Version: 1.5.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,7 +23,7 @@ if ( ! defined( 'ABSPATH' ) ) {
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
define( 'ROBOTSTXT_MEDIAAUDIT_VERSION', '1.3.0' );
|
define( 'ROBOTSTXT_MEDIAAUDIT_VERSION', '1.5.0' );
|
||||||
define( 'ROBOTSTXT_MEDIAAUDIT_DB_VERSION', '1.1.0' );
|
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__ ) );
|
||||||
|
|
|
||||||
2
vendor/composer/autoload_classmap.php
vendored
2
vendor/composer/autoload_classmap.php
vendored
|
|
@ -7,6 +7,7 @@ $baseDir = dirname($vendorDir);
|
||||||
|
|
||||||
return array(
|
return array(
|
||||||
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
|
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
|
||||||
|
'MediaRightsAudit\\Admin\\AttachmentDetailPage' => $baseDir . '/includes/Admin/AttachmentDetailPage.php',
|
||||||
'MediaRightsAudit\\Admin\\AuditPage' => $baseDir . '/includes/Admin/AuditPage.php',
|
'MediaRightsAudit\\Admin\\AuditPage' => $baseDir . '/includes/Admin/AuditPage.php',
|
||||||
'MediaRightsAudit\\Admin\\MediaListTable' => $baseDir . '/includes/Admin/MediaListTable.php',
|
'MediaRightsAudit\\Admin\\MediaListTable' => $baseDir . '/includes/Admin/MediaListTable.php',
|
||||||
'MediaRightsAudit\\Admin\\Settings' => $baseDir . '/includes/Admin/Settings.php',
|
'MediaRightsAudit\\Admin\\Settings' => $baseDir . '/includes/Admin/Settings.php',
|
||||||
|
|
@ -20,6 +21,7 @@ return array(
|
||||||
'MediaRightsAudit\\External\\AbstractProvider' => $baseDir . '/includes/External/AbstractProvider.php',
|
'MediaRightsAudit\\External\\AbstractProvider' => $baseDir . '/includes/External/AbstractProvider.php',
|
||||||
'MediaRightsAudit\\External\\ExternalScanner' => $baseDir . '/includes/External/ExternalScanner.php',
|
'MediaRightsAudit\\External\\ExternalScanner' => $baseDir . '/includes/External/ExternalScanner.php',
|
||||||
'MediaRightsAudit\\External\\GoogleVisionProvider' => $baseDir . '/includes/External/GoogleVisionProvider.php',
|
'MediaRightsAudit\\External\\GoogleVisionProvider' => $baseDir . '/includes/External/GoogleVisionProvider.php',
|
||||||
|
'MediaRightsAudit\\External\\PicDefenseProvider' => $baseDir . '/includes/External/PicDefenseProvider.php',
|
||||||
'MediaRightsAudit\\External\\ResultsConsolidator' => $baseDir . '/includes/External/ResultsConsolidator.php',
|
'MediaRightsAudit\\External\\ResultsConsolidator' => $baseDir . '/includes/External/ResultsConsolidator.php',
|
||||||
'MediaRightsAudit\\External\\ScanResult' => $baseDir . '/includes/External/ScanResult.php',
|
'MediaRightsAudit\\External\\ScanResult' => $baseDir . '/includes/External/ScanResult.php',
|
||||||
'MediaRightsAudit\\External\\TinEyeProvider' => $baseDir . '/includes/External/TinEyeProvider.php',
|
'MediaRightsAudit\\External\\TinEyeProvider' => $baseDir . '/includes/External/TinEyeProvider.php',
|
||||||
|
|
|
||||||
2
vendor/composer/autoload_static.php
vendored
2
vendor/composer/autoload_static.php
vendored
|
|
@ -22,6 +22,7 @@ class ComposerStaticInit957728ab3efa005f456e5f9df13a19d2
|
||||||
|
|
||||||
public static $classMap = array (
|
public static $classMap = array (
|
||||||
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
|
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
|
||||||
|
'MediaRightsAudit\\Admin\\AttachmentDetailPage' => __DIR__ . '/../..' . '/includes/Admin/AttachmentDetailPage.php',
|
||||||
'MediaRightsAudit\\Admin\\AuditPage' => __DIR__ . '/../..' . '/includes/Admin/AuditPage.php',
|
'MediaRightsAudit\\Admin\\AuditPage' => __DIR__ . '/../..' . '/includes/Admin/AuditPage.php',
|
||||||
'MediaRightsAudit\\Admin\\MediaListTable' => __DIR__ . '/../..' . '/includes/Admin/MediaListTable.php',
|
'MediaRightsAudit\\Admin\\MediaListTable' => __DIR__ . '/../..' . '/includes/Admin/MediaListTable.php',
|
||||||
'MediaRightsAudit\\Admin\\Settings' => __DIR__ . '/../..' . '/includes/Admin/Settings.php',
|
'MediaRightsAudit\\Admin\\Settings' => __DIR__ . '/../..' . '/includes/Admin/Settings.php',
|
||||||
|
|
@ -35,6 +36,7 @@ class ComposerStaticInit957728ab3efa005f456e5f9df13a19d2
|
||||||
'MediaRightsAudit\\External\\AbstractProvider' => __DIR__ . '/../..' . '/includes/External/AbstractProvider.php',
|
'MediaRightsAudit\\External\\AbstractProvider' => __DIR__ . '/../..' . '/includes/External/AbstractProvider.php',
|
||||||
'MediaRightsAudit\\External\\ExternalScanner' => __DIR__ . '/../..' . '/includes/External/ExternalScanner.php',
|
'MediaRightsAudit\\External\\ExternalScanner' => __DIR__ . '/../..' . '/includes/External/ExternalScanner.php',
|
||||||
'MediaRightsAudit\\External\\GoogleVisionProvider' => __DIR__ . '/../..' . '/includes/External/GoogleVisionProvider.php',
|
'MediaRightsAudit\\External\\GoogleVisionProvider' => __DIR__ . '/../..' . '/includes/External/GoogleVisionProvider.php',
|
||||||
|
'MediaRightsAudit\\External\\PicDefenseProvider' => __DIR__ . '/../..' . '/includes/External/PicDefenseProvider.php',
|
||||||
'MediaRightsAudit\\External\\ResultsConsolidator' => __DIR__ . '/../..' . '/includes/External/ResultsConsolidator.php',
|
'MediaRightsAudit\\External\\ResultsConsolidator' => __DIR__ . '/../..' . '/includes/External/ResultsConsolidator.php',
|
||||||
'MediaRightsAudit\\External\\ScanResult' => __DIR__ . '/../..' . '/includes/External/ScanResult.php',
|
'MediaRightsAudit\\External\\ScanResult' => __DIR__ . '/../..' . '/includes/External/ScanResult.php',
|
||||||
'MediaRightsAudit\\External\\TinEyeProvider' => __DIR__ . '/../..' . '/includes/External/TinEyeProvider.php',
|
'MediaRightsAudit\\External\\TinEyeProvider' => __DIR__ . '/../..' . '/includes/External/TinEyeProvider.php',
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue