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 65f8a2d..40cc514 100644 Binary files a/languages/robotstxt-mediaaudit-ca.mo and b/languages/robotstxt-mediaaudit-ca.mo differ diff --git a/languages/robotstxt-mediaaudit-ca.po b/languages/robotstxt-mediaaudit-ca.po index 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 97518fa..8b307ec 100644 Binary files a/languages/robotstxt-mediaaudit-es_ES.mo and b/languages/robotstxt-mediaaudit-es_ES.mo differ diff --git a/languages/robotstxt-mediaaudit-es_ES.po b/languages/robotstxt-mediaaudit-es_ES.po index 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',