v1.0.0
This commit is contained in:
commit
6708bbb67f
43 changed files with 8342 additions and 0 deletions
128
includes/External/AbstractProvider.php
vendored
Normal file
128
includes/External/AbstractProvider.php
vendored
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
<?php
|
||||
/**
|
||||
* Base class for all external reverse-image-search providers.
|
||||
*
|
||||
* @package MediaRightsAudit\External
|
||||
*/
|
||||
|
||||
namespace MediaRightsAudit\External;
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
return 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, 10, 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 );
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue