151 lines
4.1 KiB
PHP
151 lines
4.1 KiB
PHP
<?php
|
|
/**
|
|
* TinEye reverse image search provider.
|
|
*
|
|
* @package MediaRightsAudit\External
|
|
*/
|
|
|
|
namespace MediaRightsAudit\External;
|
|
|
|
use MediaRightsAudit\Admin\Settings;
|
|
|
|
/**
|
|
* Submits images to the TinEye Commercial API and normalises the response
|
|
* into a ScanResult.
|
|
*
|
|
* Uses URL-based image submission (GET request with image_url parameter).
|
|
* Reads the API key from the plugin settings (tineye_api_key).
|
|
* Match count reflects the number of distinct matching images found;
|
|
* top domains are extracted from the backlink URLs of all matches.
|
|
*/
|
|
class TinEyeProvider extends AbstractProvider {
|
|
|
|
/**
|
|
* TinEye Commercial API search endpoint.
|
|
*/
|
|
const ENDPOINT = 'https://api.tineye.com/rest/search/';
|
|
|
|
/**
|
|
* Returns the machine-readable provider identifier.
|
|
*
|
|
* @return string
|
|
*/
|
|
public function provider_slug(): string {
|
|
return 'tineye';
|
|
}
|
|
|
|
/**
|
|
* Returns the human-readable provider display name.
|
|
*
|
|
* @return string
|
|
*/
|
|
public function provider_name(): string {
|
|
return 'TinEye';
|
|
}
|
|
|
|
/**
|
|
* Submits the image URL to TinEye and returns a ScanResult.
|
|
*
|
|
* @param int $attachment_id WordPress attachment ID.
|
|
* @param string $file_url Public URL of the image.
|
|
*
|
|
* @return ScanResult
|
|
*
|
|
* @throws \RuntimeException On configuration, HTTP, or parse errors.
|
|
*/
|
|
protected function do_scan( int $attachment_id, string $file_url ): ScanResult {
|
|
$api_key = $this->get_api_key();
|
|
if ( '' === $api_key ) {
|
|
throw new \RuntimeException( 'TinEye API key is not configured.' );
|
|
}
|
|
|
|
$url = add_query_arg(
|
|
array(
|
|
'api_key' => $api_key,
|
|
'image_url' => $file_url,
|
|
),
|
|
self::ENDPOINT
|
|
);
|
|
|
|
$response = wp_remote_get( $url, array( 'timeout' => 30 ) );
|
|
|
|
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( 'TinEye 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 TinEye API response.' );
|
|
}
|
|
|
|
return $this->parse_response( $decoded );
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Private helpers
|
|
// -------------------------------------------------------------------------
|
|
|
|
/**
|
|
* Reads the TinEye API key from plugin settings.
|
|
*
|
|
* @return string Empty string if not configured.
|
|
*/
|
|
private function get_api_key(): string {
|
|
$raw = get_option( Settings::OPTION_NAME, array() );
|
|
$opts = is_array( $raw ) ? $raw : array();
|
|
$val = $opts['tineye_api_key'] ?? null;
|
|
return is_string( $val ) ? trim( $val ) : '';
|
|
}
|
|
|
|
/**
|
|
* Parses a decoded TinEye API response into a ScanResult.
|
|
*
|
|
* Navigates results.matches, extracts backlink page URLs to build the
|
|
* domain-frequency map, and counts distinct image matches.
|
|
*
|
|
* @param array<mixed, mixed> $decoded json_decode()'d API response.
|
|
*
|
|
* @return ScanResult
|
|
*/
|
|
private function parse_response( array $decoded ): ScanResult {
|
|
$results_raw = $decoded['results'] ?? null;
|
|
$results = is_array( $results_raw ) ? $results_raw : array();
|
|
|
|
$matches_raw = $results['matches'] ?? null;
|
|
$matches = is_array( $matches_raw ) ? $matches_raw : array();
|
|
|
|
$backlink_items = array();
|
|
foreach ( $matches as $match ) {
|
|
if ( ! is_array( $match ) ) {
|
|
continue;
|
|
}
|
|
$bls_raw = $match['backlinks'] ?? null;
|
|
if ( ! is_array( $bls_raw ) ) {
|
|
continue;
|
|
}
|
|
foreach ( $bls_raw as $bl ) {
|
|
if ( ! is_array( $bl ) ) {
|
|
continue;
|
|
}
|
|
$url_val = $bl['url'] ?? null;
|
|
if ( is_string( $url_val ) && '' !== $url_val ) {
|
|
$backlink_items[] = array( 'url' => $url_val );
|
|
}
|
|
}
|
|
}
|
|
|
|
$match_count = count( $matches );
|
|
$top_domains = $this->extract_top_domains( $backlink_items );
|
|
|
|
return new ScanResult( $match_count, $top_domains, $decoded );
|
|
}
|
|
}
|