136 lines
3.7 KiB
PHP
136 lines
3.7 KiB
PHP
<?php
|
|
/**
|
|
* Base class for all external reverse-image-search providers.
|
|
*
|
|
* @package MediaRightsAudit\External
|
|
*/
|
|
|
|
namespace MediaRightsAudit\External;
|
|
|
|
if ( ! defined( 'ABSPATH' ) ) {
|
|
exit;
|
|
}
|
|
|
|
use MediaRightsAudit\Admin\Settings;
|
|
|
|
/**
|
|
* Provides rate-limited scanning via a transient-based per-minute counter.
|
|
*
|
|
* Subclasses implement do_scan() with the provider-specific HTTP call.
|
|
* The public scan() wrapper enforces the rate limit before delegating.
|
|
*/
|
|
abstract class AbstractProvider {
|
|
|
|
/**
|
|
* Machine-readable provider identifier stored in mra_external_results.provider.
|
|
*
|
|
* @return string
|
|
*/
|
|
abstract public function provider_slug(): string;
|
|
|
|
/**
|
|
* Human-readable provider display name.
|
|
*
|
|
* @return string
|
|
*/
|
|
abstract public function provider_name(): string;
|
|
|
|
/**
|
|
* Maximum API requests allowed per minute for this provider.
|
|
*
|
|
* Subclasses may override to read a per-provider setting.
|
|
*
|
|
* @return positive-int
|
|
*/
|
|
public function rate_limit(): int {
|
|
$opts = Settings::get_effective_settings();
|
|
$val = $opts['rate_limit_per_minute'] ?? null;
|
|
return is_numeric( $val ) ? max( 1, (int) $val ) : 10;
|
|
}
|
|
|
|
/**
|
|
* Performs the provider-specific scan and returns a result.
|
|
*
|
|
* @param int $attachment_id WordPress attachment ID.
|
|
* @param string $file_url Public URL of the image to analyse.
|
|
*
|
|
* @return ScanResult
|
|
*
|
|
* @throws \RuntimeException On HTTP or parse errors.
|
|
*/
|
|
abstract protected function do_scan( int $attachment_id, string $file_url ): ScanResult;
|
|
|
|
/**
|
|
* Scans an image URL after checking the per-minute rate limit.
|
|
*
|
|
* @param int $attachment_id WordPress attachment ID.
|
|
* @param string $file_url Public URL of the image to analyse.
|
|
*
|
|
* @return ScanResult
|
|
*
|
|
* @throws \RuntimeException When rate limit is exceeded or the scan fails.
|
|
*/
|
|
final public function scan( int $attachment_id, string $file_url ): ScanResult {
|
|
$this->enforce_rate_limit();
|
|
return $this->do_scan( $attachment_id, $file_url );
|
|
}
|
|
|
|
/**
|
|
* Builds a domain → count map from a list of URL-bearing items.
|
|
*
|
|
* Each item must be an array with a string 'url' key. Items missing the key
|
|
* or whose host cannot be parsed are silently skipped.
|
|
*
|
|
* @param array<mixed> $items URL-bearing items (e.g. pages or backlink objects).
|
|
*
|
|
* @return array<string, int> Domain → occurrence count, sorted descending, max 10.
|
|
*/
|
|
protected function extract_top_domains( array $items ): array {
|
|
$counts = array();
|
|
|
|
foreach ( $items as $item ) {
|
|
if ( ! is_array( $item ) ) {
|
|
continue;
|
|
}
|
|
$url_val = $item['url'] ?? null;
|
|
if ( ! is_string( $url_val ) || '' === $url_val ) {
|
|
continue;
|
|
}
|
|
$host = wp_parse_url( $url_val, PHP_URL_HOST );
|
|
if ( ! is_string( $host ) || '' === $host ) {
|
|
continue;
|
|
}
|
|
$counts[ $host ] = ( $counts[ $host ] ?? 0 ) + 1;
|
|
}
|
|
|
|
arsort( $counts );
|
|
return array_slice( $counts, 0, 50, true );
|
|
}
|
|
|
|
/**
|
|
* Checks and increments the per-minute request counter via transients.
|
|
*
|
|
* Throws if the counter has reached the configured limit.
|
|
*
|
|
* @throws \RuntimeException When the rate limit for the current minute is exhausted.
|
|
*/
|
|
private function enforce_rate_limit(): void {
|
|
$key = 'mra_rl_' . $this->provider_slug() . '_' . gmdate( 'YmdHi' );
|
|
$raw = get_transient( $key );
|
|
$current = is_numeric( $raw ) ? (int) $raw : 0;
|
|
|
|
if ( $current >= $this->rate_limit() ) {
|
|
// phpcs:disable WordPress.Security.EscapeOutput.ExceptionNotEscaped
|
|
throw new \RuntimeException(
|
|
sprintf(
|
|
'Rate limit of %d req/min exceeded for provider "%s".',
|
|
$this->rate_limit(),
|
|
$this->provider_slug()
|
|
)
|
|
);
|
|
// phpcs:enable WordPress.Security.EscapeOutput.ExceptionNotEscaped
|
|
}
|
|
|
|
set_transient( $key, $current + 1, 90 );
|
|
}
|
|
}
|