From 83d629568a2768d86c74cd70d3f078a524d972ad Mon Sep 17 00:00:00 2001 From: Javier Casares Date: Wed, 3 Jun 2026 06:28:34 +0000 Subject: [PATCH] v1.5.0 --- assets/css/media-audit-admin.css | 15 + changelog.txt | 52 ++ includes/Admin/AttachmentDetailPage.php | 595 +++++++++++++++++++++++ includes/Admin/AuditPage.php | 27 +- includes/Admin/MediaListTable.php | 7 + includes/Admin/Settings.php | 146 ++++++ includes/Admin/ToolsPage.php | 6 +- includes/Core/Plugin.php | 50 +- includes/External/AbstractProvider.php | 5 +- includes/External/ExternalScanner.php | 7 +- includes/External/PicDefenseProvider.php | 156 ++++++ includes/External/TinEyeProvider.php | 7 +- languages/robotstxt-mediaaudit-ca.mo | Bin 18925 -> 20806 bytes languages/robotstxt-mediaaudit-ca.po | 120 ++++- languages/robotstxt-mediaaudit-es_ES.mo | Bin 18722 -> 20618 bytes languages/robotstxt-mediaaudit-es_ES.po | 120 ++++- readme.txt | 15 +- robotstxt-mediaaudit.php | 4 +- vendor/composer/autoload_classmap.php | 2 + vendor/composer/autoload_static.php | 2 + 20 files changed, 1321 insertions(+), 15 deletions(-) create mode 100644 includes/Admin/AttachmentDetailPage.php create mode 100644 includes/External/PicDefenseProvider.php diff --git a/assets/css/media-audit-admin.css b/assets/css/media-audit-admin.css index 0c4fa33..8250db8 100644 --- a/assets/css/media-audit-admin.css +++ b/assets/css/media-audit-admin.css @@ -102,6 +102,21 @@ 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 { background: #721c24; color: #fff; diff --git a/changelog.txt b/changelog.txt index 701c3f2..4905d32 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,57 @@ == 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 = _Release date: 2026-05-05_ diff --git a/includes/Admin/AttachmentDetailPage.php b/includes/Admin/AttachmentDetailPage.php new file mode 100644 index 0000000..c08b7ec --- /dev/null +++ b/includes/Admin/AttachmentDetailPage.php @@ -0,0 +1,595 @@ +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 '
'; + echo '

' . esc_html__( '← Media Audit', 'robotstxt-mediaaudit' ) . ' — ' . $filename . '

'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- $filename is already escaped via esc_html() above. + + // ----------------------------------------------------------------------- + // File Information + // ----------------------------------------------------------------------- + echo '

' . esc_html__( 'File Information', 'robotstxt-mediaaudit' ) . '

'; + echo ''; + + $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( + '', + esc_html__( 'ID', 'robotstxt-mediaaudit' ), + esc_html( (string) $attachment_id ) + ); + + printf( + '', + esc_html__( 'Filename', 'robotstxt-mediaaudit' ), + esc_html( $fn_raw ) + ); + + if ( '' !== $url_raw ) { + printf( + '', + esc_html__( 'File URL', 'robotstxt-mediaaudit' ), + esc_url( $url_raw ), + esc_html( $url_raw ) + ); + } + + printf( + '', + 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( + '', + esc_html__( 'File size', 'robotstxt-mediaaudit' ), + esc_html( $file_size_str ) + ); + + printf( + '', + esc_html__( 'Internal scan date', 'robotstxt-mediaaudit' ), + esc_html( $is_raw ) + ); + + printf( + '', + esc_html__( 'External Status', 'robotstxt-mediaaudit' ), + esc_html( $es_raw ) + ); + + echo '
%s%s
%s%s
%s%s
%s%s
%s%s
%s%s
%s%s
'; + + // Thumbnail. + $thumb = wp_get_attachment_image( $attachment_id, 'medium', false, array( 'class' => 'mra-detail-thumb' ) ); + if ( $thumb ) { + echo '

' . $thumb . '

'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- wp_get_attachment_image is safe. + } + + // ----------------------------------------------------------------------- + // Internal Usage + // ----------------------------------------------------------------------- + echo '

' . esc_html__( 'Internal Usage', 'robotstxt-mediaaudit' ) . '

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

' . esc_html__( 'Not used in any post.', 'robotstxt-mediaaudit' ) . '

'; + } else { + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + + 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 .= ' ' . esc_html( $meta_key ) . ''; + } + + $edit_link = get_edit_post_link( $post_id ); + $post_cell = $edit_link + ? sprintf( '%s', esc_url( $edit_link ), esc_html( $post_title ) ) + : esc_html( $post_title ); + + echo ''; + echo ''; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- $post_cell already escaped above. + echo ''; + echo ''; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- $ctx_label already escaped above. + echo ''; + echo ''; + } + + echo '
' . esc_html__( 'Post', 'robotstxt-mediaaudit' ) . '' . esc_html__( 'Type', 'robotstxt-mediaaudit' ) . '' . esc_html__( 'Context', 'robotstxt-mediaaudit' ) . '' . esc_html__( 'Status', 'robotstxt-mediaaudit' ) . '
' . $post_cell . '' . esc_html( $post_type ) . '' . $ctx_label . '' . esc_html( $status ) . '
'; + } + + // ----------------------------------------------------------------------- + // External Scan Results + // ----------------------------------------------------------------------- + echo '

' . esc_html__( 'External Scan Results', 'robotstxt-mediaaudit' ) . '

'; + $this->render_external_results( $attachment_id ); + + echo '
'; // .wrap + } + + // ------------------------------------------------------------------------- + // Private helpers + // ------------------------------------------------------------------------- + + /** + * Fetches the mra_media_index row for a given attachment ID. + * + * @param int $attachment_id Attachment post ID. + * + * @return array|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 '

' . esc_html__( 'No external scan data available.', 'robotstxt-mediaaudit' ) . '

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

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

'; + + 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 $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 ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + 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 ''; + echo ''; + echo ''; + echo ''; + } + echo '
' . esc_html__( 'Pages with this image', 'robotstxt-mediaaudit' ) . '
' . esc_html__( 'Page URL', 'robotstxt-mediaaudit' ) . '' . esc_html__( 'Page Title', 'robotstxt-mediaaudit' ) . '
' . esc_html( $page_url ) . '' . esc_html( $page_title ) . '
'; + } + + // Full matching images. + $full_val = $web['fullMatchingImages'] ?? null; + $full = is_array( $full_val ) ? $full_val : array(); + if ( ! empty( $full ) ) { + echo ''; + echo ''; + echo ''; + 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 ''; + } + echo '
' . esc_html__( 'Full image matches', 'robotstxt-mediaaudit' ) . '
' . esc_html__( 'Image URL', 'robotstxt-mediaaudit' ) . '
' . esc_html( $img_url ) . '
'; + } + + // Partial matching images. + $partial_val = $web['partialMatchingImages'] ?? null; + $partial = is_array( $partial_val ) ? $partial_val : array(); + if ( ! empty( $partial ) ) { + echo ''; + echo ''; + echo ''; + 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 ''; + } + echo '
' . esc_html__( 'Partial image matches', 'robotstxt-mediaaudit' ) . '
' . esc_html__( 'Image URL', 'robotstxt-mediaaudit' ) . '
' . esc_html( $img_url ) . '
'; + } + } + + /** + * Renders the TinEye backlinks table, sorted by crawl_date DESC. + * + * @param array $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 ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + + 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 ''; + echo ''; + echo ''; + echo ''; + echo ''; + } + + echo '
' . esc_html__( 'Backlinks found by TinEye (sorted by crawl date, newest first)', 'robotstxt-mediaaudit' ) . '
' . esc_html__( 'Page URL', 'robotstxt-mediaaudit' ) . '' . esc_html__( 'Image URL', 'robotstxt-mediaaudit' ) . '' . esc_html__( 'Crawl date', 'robotstxt-mediaaudit' ) . '
' . esc_html( $page_url ) . '' . esc_html( $img_url ) . '' . esc_html( $bl_date ) . '
'; + } + + /** + * Renders the PicDefense scan result sections. + * + * @param array $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 '

' . esc_html( ucfirst( $picrisk ) ) . '

'; + } + + // 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 '

' . esc_html__( 'Risk flags', 'robotstxt-mediaaudit' ) . '

'; + if ( empty( $flags ) ) { + echo '

' . esc_html__( 'No risk flags detected.', 'robotstxt-mediaaudit' ) . '

'; + } else { + echo '
    '; + foreach ( $flags as $flag ) { + echo '
  • ' . $flag . '
  • '; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- already escaped above. + } + echo '
'; + } + + // 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 ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + + 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 ''; + echo ''; + echo ''; + echo ''; + echo ''; + } + + echo '
' . esc_html__( 'Backlinks found by PicDefense', 'robotstxt-mediaaudit' ) . '
' . esc_html__( 'Page URL', 'robotstxt-mediaaudit' ) . '' . esc_html__( 'Image URL', 'robotstxt-mediaaudit' ) . '' . esc_html__( 'Similarity', 'robotstxt-mediaaudit' ) . '
' . esc_html( $page_url ) . '' . esc_html( $img_url ) . '' . esc_html( $score ) . '
'; + } + + // 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 '

' . esc_html__( 'Labels', 'robotstxt-mediaaudit' ) . '

'; + echo '

' . esc_html( $label_str ) . '

'; + } + } + } + } +} diff --git a/includes/Admin/AuditPage.php b/includes/Admin/AuditPage.php index b4cb720..36070df 100644 --- a/includes/Admin/AuditPage.php +++ b/includes/Admin/AuditPage.php @@ -7,6 +7,7 @@ namespace MediaRightsAudit\Admin; +use MediaRightsAudit\Admin\AttachmentDetailPage; use MediaRightsAudit\External\ExternalScanner; use MediaRightsAudit\External\ResultsConsolidator; use MediaRightsAudit\Internal\AttachmentIndexer; @@ -269,7 +270,7 @@ class AuditPage { // phpcs:ignore WordPress.DB.DirectDatabaseQuery,WordPress.DB.PreparedSQL.InterpolatedNotPrepared $rows = $wpdb->get_results( $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 WHERE attachment_id = %d ORDER BY provider ASC", @@ -285,6 +286,7 @@ class AuditPage { $provider_names = array( 'google_vision' => 'Google Cloud Vision', 'tineye' => 'TinEye', + 'picdefense' => 'PicDefense', ); $html = '

' . esc_html__( 'External Scan Results', 'robotstxt-mediaaudit' ) . '

'; @@ -303,6 +305,12 @@ class AuditPage { $td_val = $row['top_domains'] ?? null; $td_raw = is_string( $td_val ) ? json_decode( $td_val, true ) : null; $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 .= '
'; $html .= '' . esc_html( $name ) . ' — '; @@ -319,6 +327,19 @@ class AuditPage { $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 .= ' ' . esc_html( ucfirst( $picrisk ) ) . ''; + } + } + } + if ( '' !== $scanned_at ) { $html .= ' ' . esc_html( $scanned_at ) . ''; } @@ -364,6 +385,10 @@ class AuditPage { } } + $html .= ''; + return $html; } diff --git a/includes/Admin/MediaListTable.php b/includes/Admin/MediaListTable.php index 58b3a40..ac4261a 100644 --- a/includes/Admin/MediaListTable.php +++ b/includes/Admin/MediaListTable.php @@ -7,6 +7,8 @@ namespace MediaRightsAudit\Admin; +use MediaRightsAudit\Admin\AttachmentDetailPage; + if ( ! class_exists( 'WP_List_Table' ) ) { require_once ABSPATH . 'wp-admin/includes/class-wp-list-table.php'; } @@ -172,6 +174,11 @@ class MediaListTable extends \WP_List_Table { $id, __( 'View Details', 'robotstxt-mediaaudit' ) ), + 'full_report' => sprintf( + '%s', + esc_url( AttachmentDetailPage::url( $id ) ), + __( 'Full Report', 'robotstxt-mediaaudit' ) + ), ); return $title . $this->row_actions( $actions ); diff --git a/includes/Admin/Settings.php b/includes/Admin/Settings.php index f141285..5472515 100644 --- a/includes/Admin/Settings.php +++ b/includes/Admin/Settings.php @@ -80,6 +80,22 @@ class Settings { '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( 'mra_external_scanning', __( 'External Scanning', 'robotstxt-mediaaudit' ), @@ -126,6 +142,12 @@ class Settings { $tineye_val = $input['tineye_api_key'] ?? null; $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 = is_numeric( $batch_val ) ? (int) $batch_val : 10; $output['external_batch_size'] = max( 1, min( 100, $batch ) ); @@ -265,6 +287,58 @@ class Settings { + +

+ +

+ + +

+ + — + + + +

+ is_google_vision_configured(); + $te_ok = $this->is_tineye_configured(); + $pd_ok = $this->is_picdefense_configured(); ?>

+ +
+ +
    +
  • + + + + + + +
  • +
  • + + + + + + +
  • +
  • + + + + + + PicDefense +
  • +
+
+
settings = new Settings(); - $this->audit_page = new AuditPage(); - $this->tools_page = new ToolsPage(); + $this->settings = new Settings(); + $this->audit_page = new AuditPage(); + $this->tools_page = new ToolsPage(); + $this->detail_page = new AttachmentDetailPage(); } /** @@ -138,6 +148,15 @@ class Plugin { 'robotstxt-mediaaudit-settings', 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 TinEyeProvider(); + if ( $this->is_provider_configured( 'picdefense' ) ) { + $list[] = new PicDefenseProvider(); + } 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. * diff --git a/includes/External/AbstractProvider.php b/includes/External/AbstractProvider.php index 0c4916b..2d52d75 100644 --- a/includes/External/AbstractProvider.php +++ b/includes/External/AbstractProvider.php @@ -37,7 +37,10 @@ abstract class AbstractProvider { * @return positive-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; } /** diff --git a/includes/External/ExternalScanner.php b/includes/External/ExternalScanner.php index cce1451..0bd233c 100644 --- a/includes/External/ExternalScanner.php +++ b/includes/External/ExternalScanner.php @@ -138,7 +138,12 @@ class ExternalScanner { * @return 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 ) { Scheduler::schedule_single( self::AS_HOOK ); diff --git a/includes/External/PicDefenseProvider.php b/includes/External/PicDefenseProvider.php new file mode 100644 index 0000000..6e0a808 --- /dev/null +++ b/includes/External/PicDefenseProvider.php @@ -0,0 +1,156 @@ +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 $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 ); + } +} diff --git a/includes/External/TinEyeProvider.php b/includes/External/TinEyeProvider.php index b10050e..1caacc4 100644 --- a/includes/External/TinEyeProvider.php +++ b/includes/External/TinEyeProvider.php @@ -136,9 +136,10 @@ class TinEyeProvider extends AbstractProvider { if ( ! is_array( $bl ) ) { continue; } - $url_val = $bl['url'] ?? null; - if ( is_string( $url_val ) && '' !== $url_val ) { - $backlink_items[] = array( 'url' => $url_val ); + // 'backlink' is the page URL; 'url' is the direct image URL. + $page_url_val = $bl['backlink'] ?? null; + if ( is_string( $page_url_val ) && '' !== $page_url_val ) { + $backlink_items[] = array( 'url' => $page_url_val ); } } } diff --git a/languages/robotstxt-mediaaudit-ca.mo b/languages/robotstxt-mediaaudit-ca.mo index 65f8a2ddb40c336747f66ff206a3b127b481fb2e..40cc51425b4186e9cbdd151ac6c67682cafd57e5 100644 GIT binary patch delta 6256 zcmZYC3v?9K9mnyT2X6u)5;26pfIx_b@CYyEAt3>cfCdDlh{CWtNv3Re!|sHq?TS#U zwkoln1qVBgJHNjW098aJ!drixf!@MV_yKAHMeLWx z8G_onk^Eq{OhX0vzmUdqI#%FKI1E2QcEj`<9IR+W=mcz|zaBNQeW;AQfJ*sE)C5nV z2JAj0m_T1tZ45;XToXDcO+$Nh9V(SBY9;G%J8nj0ro;+9AB^>!SEKsdQCo98YQ;No z1Rg}zU_L@^Md47k150r}PC|{F-bzC=x*auvhp+*kMh$QpRU=(Vi!LY+9f?ZyI8-rR z9NL81s%BJ%;-Q;SnfVTCi}xTINt?YiRMkI072m<&19J>Dz)7sZk5D%#JwLDt_j5iS zmFiQdE&Bqs6{W*@`Cv89#S^IOhm)SgIK?;33x$TN_5)O^{)Sp<;mC0BP%9dW&tes7 zp!bk}<{$jf_s_2kzE^`v{UlWXB2*Esz>9DVTKFiQ%lPIMHSm4ZL{4K@?7@aA1I2g^ zmWAgXs2hJB^}@P2?0+4*asDpq`j1gt_-9ngPh)rdH)_1@RpeiLK7_^s9F0$48p)y= zbAd4v@hY5vHzWVd^Zd|+KE)n5`@&%2S7H~=uR{$S#XGPaHG%xu45-(?^qk z4ctbD2Hb?I+HI&}{2o4-$9o#Jr%haCVH{ONwiV6i;(QFXpd;wu zakOyqcqV~MQRAi`qftPk6F1@kT!d3}u@cbyUE9N!SN()KjQ0#}wSS=1?d^4AZ28^R#5VxW(*d3lfjx#tvgj#Xl z#lhB8p$2S3?d1Zz6j!1$vImucPSk+MP~ZCm`{LiRNY8(_slm!hQC}E@dQLAyeQ^S+ z78+0kMo|-u;c&bG^YH=n2S)9ECsyMNSdM=|U0*aUDCSa3SJF{SgKaU_po@2)Zu|vm z1s0{N8&1X|oP|5F2`lgu)C9YdCSDz8Bx=P?sP{uF_QND<;#*PIeRl@=S7SdN{qZ%_ z3O+>*cp9IHNA+!@Up}(RQGN>;2gQgajbMDlo zgBu*8V+0+)N3HOm*oeJm1ru3}rJSz}&)1>`-i%t=U8ofAK?`3%rTRnEz<&xYtPg%& zm!m$Pl%}CC%s^H3VpI(*LlvQm%EVfniJMUwIE0$eoA?=ij(R%YZU~C>eLSD@UbFeL z49B2W+-FWu?5pt_&eONhXr}QImf`$MgB!P?9Mff?0eU2h0l|pHp}R=R@ZkLy}B8 zs>q(fcKjSQ5t}b*YgVHM-h#^D&G;R>JM15KW$^vUSj_mQjz$qK3|)!SIFF+y_9&`0 z4ul@UuAIM!D!QYn8u|mChj~pw|3K7zC*dS)KxJ|hvU_F=rpM5DnMNP{BDCj%U<=An zHyn+5s6(})<0|5}gl?=ud#YnYfVmrQB-)9uhkfj@xrvB}=Ww0>hsO?DKPFtFFR`D< z61n4nu<kvpgf|33n&Q8~>WTWDxcwc!mxXWBeQ;~e6hpu_)d@+57Q?KnbJ zr)Ow9@er|%&~Y_!KQUD&9IC2c_$~fi#^;Cw#I?lF2_4$U-w`~HewDtU>8K2K>^9$^ae{b?*huhp^M7Og8b=d4h6b4Hm5u`9 z7GfEpXXU%Z4a5+llgJ%&Y0M#3<~l+j$0%{vS^fF6zeyYq`+jrQXK&G7Lj01@%f&xd z@Keu&4n3$>5=Z?C`2nqBqMpbdgK2Chx)Mi-gG7SJ9W69o%bnmO#7~LM#9`vwL~laJ z!^DL|e_|&wiqP>cF;E3Fg2vC(!?Bb&_}^y6ZfozPJ1HhWiBsqmPJ;_-1zF0)tu;vN3B&G ztR^?o;50k&lw)Su|M%%4H$HoVV~t92z2kpBlC;;wtf=id)mGeD=cGKV*-fUrs_bJW z`|`TyKFlubKfR#e(#APfB+9(|b7h5Az*&PGM=Jkp=R@CvF2)E1}I`6Vxjq&D0 zvd#9~L_AYiW_7Vr?nWmwq-@yWIUTWBIDO-{-8Q?$vG{JJ)k$S$mQC;OcPw5wFT1Ji z!@NRGEVH2e(sNfiv6Pu-$D?g_a`l~;#>S(t-(nLPZd)u~K^PQ+`TXh{#*UJp98lN{YyP)cL zLAPM*oJ`-*ThnLi$4e$w*9#q^Dl^}W+H6S7irv3!bbfJzw(hL!vUiX9xS*sySA*7UO42XH z8*A^(>r-zh^=*}+9kny(Ok8$uSc?(&aMr(Vv+J#Ql9|}VQH2^RbJN6&#)ONewA;r>-5As%%xQ2dnoimokjl(L_@9t!j_dWFDCM zjr5|!d%Xltyg&E$!}n5twmGw!Tu+n#3B%cKt1O!-r`U*KssEdwb~_p2IXIIb{~2IY zO?}Mn@T;HE!nzr#H&bH+$?QmVP|~@6y0hb^MG7k#|8L%A_IfAM;n_)xSHXn-*{5cf z74*1DkA&-WL^Chd-`c0q-vcdbl^u^{moz+GSQ@Kz{r8ZQ+gJbTC~W-Qv~X`c)!2bI*;o=RYF;!;whxm~h`V*ZZm+PnrJ!A)MdY delta 4472 zcmY+`32;@_0mkw3UY5raB80GIAqz_gAuKU$A&Ef@fdCeeeW?Z#ffB-EfI?{=1XF>c z5H1AVFf6htpzx>=0}9fHD%6p&saj2R3ewJi<1kd)+JWi+d-tTHclh4#ob%qj=iGD7 zy`*8k=Nrf4Z;lGxWjNj=aim|kF+Ptm*%4|rW=4BsVsIwLV>uS$4(y0c7=?GRD~5J3 zrX9v3mojOXi5b`h7hoIoBVG8-777p0up7CCIgAN-7E|$4Y>QrciNr|M*y2$g_vJ$! z=3r|ahtqK?s-xqmftyLCwGxJb(xAC>C@!hJWS`AM|ZnF%6p0Se#7%W(Wn1a5HKk+c6YtPy>0< zwjV%U=qT#WE?_KPLCxIXkv`48k+GUcMiaat)IbNLW-1>C;bZ96NUA9C&(!jvDXhZ` zyo8$a?@<>D>&ED@i!~87@Kn?i4MYt*8#VBes68?kHPE@}#Wkqwtn0@7_n}ZlgE}~Y zy5I%W)L%w!#(a%wcn=@PR7RbQn~^4S3^mXOYa=e9eghf1nN07Rxy7gfRic)%CYJeE z2M1`7^{A=+7&Q~kxE=q2y2CX*BAUV~9EA?5{S%}wa~*X<_pukIa2L9f34CaY7UKk5 zhwA48KLuT|5g)W5=dQ#u882XjzUy$p3HD^Yu=61CQwZ2KP6RKJS- z@dWDpCaeDrg|}!3OK_+D9BN80p{A?}U&nvqIIK-{FK`>DQa4Gi(@;xr40WgVs3pCM z8hA6F#ha+>zryW8XuP93VF6R4&715Uz^@eS<7LUV1? zh=cJzn27_py}nqC8o)Nxp1Oq^=yw=K|0XoWUGp|rO}#UQ;t>qTLraiZH!JZ|eA#}#YM|Yer~%dD7_39>5$_;o3wxu^`^HZ} zBW>V)%adrDQB!pf=U~i(?wYPftz`vjKpRm5*=+08$QaCS)SX>Mo;34k)b+nd9w-yZ z&Qw2xup{~>QqT#-wqZHuQm;bY;qOsXejC+c1Y5iSqp>~CL3OYMwX3VJ9qvSJ#{IVa zD7K=07ImGA$N>E2eF{8DrU||1W#?%(hat;n+9PjYlY=^OCTi0aVG8<@Q_W#qgiWaH z6%2Kkq7-$#ji`Y=hr975OwjY6In4b-V+wZRiwe}#)Sx$4=Nf z+pQ;{W~ML3V1}(vKwWn#YVVYymS{O@$=9P_7krjN9_~Ti!Dpxr?%*x-@r-IM@1mys z2h?s(W}`IYXq>uf_ z={3}deGj{L+8Mi3PePrShq~kOI29Kl50p8NTDoto?MAsvnudCM9zxAn{wTjOODRmC zK~sGmSKt-Y%;Y}e{%|bn#7WlawtX)4ro99^;8vWEd+`hO=DVBtOMH}i#As#i}*!DQyD*Amg>M`txx`7<)ILxL#6E(0J48Wc+)pNwm&jrA7SS^4C?^|(CHIbOG58e)y+emAoy;4bv$Rfh&!Px<`gH(~{NN=KJCDF36 z9ZW4LB)=eQh?b*H7dS%V$OdwZ=vd$q{M^l_mO?vRd*#Q+CMqXv!_TmotS3oCN4m@Z zKW9=}Ox_^R5I>Ky;e8VPUuqVB{ChqDjkffx;8yS7Zix zhrB@AkY1$aNTKk&t&GGc$tvP`tHPnjNrzsYJ;^vyOIkBb6kR zuBzB0-Z}x7>vD%E_(%*n8fc9Am&a+2o*&rT>4YaRy-QX|;9yLDkMmV*zB42)#+elt z?<*)ND_K!g>YR*=CP{%W;;ws~`q;KkQ}=YIN5bg9>V$nBXL!;K=V($zcu86DqOt`6 zCwWCkpndP9US~_61%Z2g>O!0={oagP)$6ImzpZx?S1wB|Uz%84R9@m7Nzd?P1U^r{ z7UDDvS{^u-(Z(CNoOv@OaA8=r$9W=art?u&isKuR{*!_DN2CVoMhx&e;komir*lgJ zKjdb6orNP;?09&CZz6*zDJu`ejG7-B*j_L+#JN_uHBdV4bf}X(@x$nrK@`m|Us+VT KsC;cvx%nRm=K_@g diff --git a/languages/robotstxt-mediaaudit-ca.po b/languages/robotstxt-mediaaudit-ca.po index 4b416d8..81ee81b 100644 --- a/languages/robotstxt-mediaaudit-ca.po +++ b/languages/robotstxt-mediaaudit-ca.po @@ -2,7 +2,7 @@ # This file is distributed under the GPL-3.0-or-later. msgid "" 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-" "mediaaudit\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." 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/" #~ msgstr "https://robotstxt.es/plugins/media-audit/" diff --git a/languages/robotstxt-mediaaudit-es_ES.mo b/languages/robotstxt-mediaaudit-es_ES.mo index 97518faaeed8273036ff9ac0eed0aa8439cbdb51..8b307ec161b2035152c1eb938542b0c6ccc7682d 100644 GIT binary patch delta 6246 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 L{M)&mvdn)0!r$S6 delta 4474 zcmY+{3wTcV0mt$Gn}nojki;dD$i*c|b0Z5P5*4Z;>M}x7msKX2mEk#OCr%|*i8_!BFW2#S;d8)?qbX%9FTiZQ*c=r9BbF#~M@_Rr3^MBv>{IBP{ z)He8D-sbb)2n*U_IL?r0lG@&wV4pF$om6W~Nf%>!;o}&C)mVtzFdVOA7`9?h4C-pk zgBXKc%A{ZxW?~PViyhF9G~qY36gpF}3%Q1Q4P&txlkqd`hyk<`ilL~!#h@B~h!-`O zhY#R5EXJv*hK`^*(u_0lJ$wwK>Ag++W*&uhRK!FWLtCZ~2IC;qi1Y9y&OkNXqnkVO zzNn68V?K^S%|IB}dX5v}Y zlz)f1P`gNak3Br&P#sT3Eztl}$8%8~&qwW%v8ayD!T?->y3Wc-=0AzTIx5sa6Y7F3 zsHs1X4948X6ugH|U^2Z!1-55-_2xN*UZgFb!ZK0DR)LO|7xI- z3V9MWwU<#daRayDUDOCyaEoXP*WoC%Q1w@lw#+TmfbL^IOk@-q$ar3~MDuYxu0*x- zfuDjdcm*H9Tc`^r^>)`V3-!UVs3|Q%jbJ8fsu!U~vJ|y<)}YpUgI8aNn(9|D9h*?! zzwYU8rEr{zcCqf%pF&OPS=5wW$3ysc9EbIB?gjpgQ#m*BuG3IUa2Pexlc*)Vi0b$a zY{oyKuD_4<>#zI&1O<)oChCJjh_3FF!aW52&L#a>4IIKW*u*S0<)q%HBd*~c$ z#i<_ZqN ze_|G9FuaGb4Ap_ns6F*Hs-yqFcC>GT65TcLfLl55jzM?;+v5@M{kJ?@P#w95TGP)_ zGxz{IPoIzQj6z)}0rgx+MD3M4&qDO8qL_lyQw-*OGY-aGn1LUm8oZBsf^|wZ=9rJi zG_ozs@AOV7m#N^6F`ZN0hEh=-%R%kd z0@N30pc<@1?S&UmyZs#M_WKmI=~_{nEHKTT>KN1vrQ&=Xg8E)#8uPCyvZ+{%%~*^n z%E=Fa zrefHkozb67LA!B`S5b`g$;?HKa5t)B@1h#~97o|T?1G7GdA*;Fx?mA@#WK_yFGF2_ z6>0z*z54AKuKWK*3hL=m48T9&gLoBL4RagSKsY;69mqiKfo$xCCCKcWDx8h`P}eg< z+@OyN!_wgot2kWpWPGLvt z^OabH>oEeap*r+8klMgX+jfsHwk$J@6aU)CcFfOBRZ{PAvNM#S{u7aS*B_PkL^`zjD4GbwMl7d1jkE z$kW-J!K-*1r{U@0?rj|Lu)BE&Vg>b^Fbcm%bufH{Yw8H*Uz=kz6?d@+V=*t^{kPvN z)NXzrb^mV1INayex1eU~L!5wr#6(PIG?6&Xa}jDOYEd(>4|Tm)NBWIfNZ}Y28e#k> zc0MMfW@am@eh=!4jo$g|-urK2AL>tG7~aH6{1!jP*^jsb88F(IM>(I4G-Vo)8_0a+ zr?8YlB=4%h&8W5AjT-q;RL@W24!neVU{w^jcf$hI-Led|Y1evgM^?q`N6qk;sPDCU zeuwQj_y3E6raWMb+h79f3mK?QG8}nkm_iK3RX7gUV>(_!b<{W3eFBD}kMsXMYB?z< zE4|9Ma3^7)e&=z!+d z?+pvthbGWDXZ=S|(9~9tV`K(dK-vzCax>BLXs@gxrDPuA>&`DFm~CVo*+lvi9a`G$ zgl%BzNg??;SwXZMCv<@WB$})yhl!54F3#&NUbV(k$s(ubzb_joHhHHLv5Y)R;)#wl zm;byzPH8@QgFH|C+9W(549n;|d|p8vETePqaC83mnz5FQE6p*^IBk#5&IGK8eK6X-sbI5+Q;Erd<({Q6BnZNm4+V`LABAZ&YsoLiX0*Q7&$+4KgH8T zM>0tuJ$2$8F`nb`7rNZ56oN@Fa?rjK_Px)#5i!T!+pWoG7x%~swDb-j0*)hG5*dwFbw&l(zEVjYZM)xNx{ zY+lt|+e%m*Xm{zqFuu%0~|LaKEuq6AXVLbz^kMky4 zxx**fdxqx*SV8#w>J+6E8-zb)$4n P_0rPHc~6y=Ej0fIG5`h$ diff --git a/languages/robotstxt-mediaaudit-es_ES.po b/languages/robotstxt-mediaaudit-es_ES.po index 77d3d8c..3b58953 100644 --- a/languages/robotstxt-mediaaudit-es_ES.po +++ b/languages/robotstxt-mediaaudit-es_ES.po @@ -2,7 +2,7 @@ # This file is distributed under the GPL-3.0-or-later. msgid "" 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-" "mediaaudit\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." 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/" #~ msgstr "https://robotstxt.es/plugins/media-audit/" diff --git a/readme.txt b/readme.txt index 3668246..7ed9fe6 100644 --- a/readme.txt +++ b/readme.txt @@ -5,7 +5,7 @@ Requires at least: 6.8 Tested up to: 7.0 Requires PHP: 8.2 Requires Plugins: action-scheduler -Stable tag: 1.3.0 +Stable tag: 1.5.0 License: GPL-3.0-or-later 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). += 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 = * Added per-provider scan tracking table (`mra_provider_status`) so each API is tracked independently. diff --git a/robotstxt-mediaaudit.php b/robotstxt-mediaaudit.php index de6e183..9260830 100644 --- a/robotstxt-mediaaudit.php +++ b/robotstxt-mediaaudit.php @@ -3,7 +3,7 @@ * Plugin Name: Media Audit (by ROBOTSTXT) * Plugin URI: https://git.robotstxt.es/ROBOTSTXT/robotstxt-mediaaudit * Description: Internal media library usage auditing and external reverse image search to detect potential copyright issues. - * Version: 1.3.0 + * Version: 1.5.0 * Requires at least: 6.8 * Tested up to: 7.0 * Requires PHP: 8.2 @@ -23,7 +23,7 @@ if ( ! defined( 'ABSPATH' ) ) { 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_PLUGIN_FILE', __FILE__ ); define( 'ROBOTSTXT_MEDIAAUDIT_PLUGIN_DIR', plugin_dir_path( __FILE__ ) ); diff --git a/vendor/composer/autoload_classmap.php b/vendor/composer/autoload_classmap.php index 2e0cdbc..f5cc77e 100644 --- a/vendor/composer/autoload_classmap.php +++ b/vendor/composer/autoload_classmap.php @@ -7,6 +7,7 @@ $baseDir = dirname($vendorDir); return array( 'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php', + 'MediaRightsAudit\\Admin\\AttachmentDetailPage' => $baseDir . '/includes/Admin/AttachmentDetailPage.php', 'MediaRightsAudit\\Admin\\AuditPage' => $baseDir . '/includes/Admin/AuditPage.php', 'MediaRightsAudit\\Admin\\MediaListTable' => $baseDir . '/includes/Admin/MediaListTable.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\\ExternalScanner' => $baseDir . '/includes/External/ExternalScanner.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\\ScanResult' => $baseDir . '/includes/External/ScanResult.php', 'MediaRightsAudit\\External\\TinEyeProvider' => $baseDir . '/includes/External/TinEyeProvider.php', diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php index 5fcb1d6..304049f 100644 --- a/vendor/composer/autoload_static.php +++ b/vendor/composer/autoload_static.php @@ -22,6 +22,7 @@ class ComposerStaticInit957728ab3efa005f456e5f9df13a19d2 public static $classMap = array ( 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', + 'MediaRightsAudit\\Admin\\AttachmentDetailPage' => __DIR__ . '/../..' . '/includes/Admin/AttachmentDetailPage.php', 'MediaRightsAudit\\Admin\\AuditPage' => __DIR__ . '/../..' . '/includes/Admin/AuditPage.php', 'MediaRightsAudit\\Admin\\MediaListTable' => __DIR__ . '/../..' . '/includes/Admin/MediaListTable.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\\ExternalScanner' => __DIR__ . '/../..' . '/includes/External/ExternalScanner.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\\ScanResult' => __DIR__ . '/../..' . '/includes/External/ScanResult.php', 'MediaRightsAudit\\External\\TinEyeProvider' => __DIR__ . '/../..' . '/includes/External/TinEyeProvider.php',