<?php
|
namespace JVBase\integrations;
|
|
if (!defined('ABSPATH')) {
|
exit;
|
}
|
|
class RateLimits {
|
protected array $history = [];
|
protected int $maxHistory = 100;
|
protected array $limits = [
|
'per_second' => 2,
|
'per_minute' => 30,
|
'per_hour' => 1000
|
];
|
|
protected string $key;
|
|
public function __construct(string $key, array $limits = []) {
|
$this->key = sanitize_title($key);
|
//Allow custom limits
|
foreach ($limits as $interval => $limit) {
|
$key = match($interval) {
|
'second', 'per_second','s' => 'per_second',
|
'minute', 'per_minute', 'm' => 'per_minute',
|
'hour', 'per_hour', 'h' => 'per_hour',
|
default => false
|
};
|
if (!$key) {
|
error_log('Attempted to add custom limit: '.$interval.' for key '.$key);
|
continue;
|
}
|
$this->limits[$key] = (int)$limit;
|
}
|
|
$this->getHistory();
|
add_action('shutdown', [$this, 'saveHistory']);
|
}
|
protected function getHistory():void
|
{
|
$stored = get_transient(BASE.$this->key.'_limits');
|
if (!$stored) {
|
$stored = [];
|
}
|
$this->history = $stored;
|
}
|
public function recordRequest(string $endpoint = 'base'):void
|
{
|
if (!array_key_exists($endpoint, $this->history)) {
|
$this->history[$endpoint] = [];
|
}
|
|
$this->history[$endpoint][] = time();
|
$this->checkHistorySize();
|
}
|
public function saveHistory():void
|
{
|
set_transient(BASE.$this->key.'_limits', $this->history);
|
}
|
protected function checkHistorySize():void
|
{
|
foreach ($this->history as $endpoint => $history) {
|
if (count($history) > $this->maxHistory) {
|
$this->history[$endpoint] = array_slice($this->history[$endpoint], -$this->maxHistory);
|
}
|
}
|
}
|
|
public function checkRateLimit(string $endpoint = 'base'):bool
|
{
|
if (!array_key_exists($endpoint, $this->history)) {
|
return true;
|
}
|
|
$now = time();
|
|
$this->cleanHistory($now);
|
$counts = [
|
'per_second' => 0,
|
'per_minute' => 0,
|
'per_hour' => count($this->history)
|
];
|
|
foreach ($this->history[$endpoint]??[] as $timestamp) {
|
if ($now - $timestamp <=1) $counts['per_second']++;
|
if ($now - $timestamp <=60) $counts['per_minute']++;
|
}
|
|
foreach ($this->limits as $interval => $limit) {
|
if ($counts[$interval] >= $limit) return false;
|
}
|
return true;
|
}
|
|
protected function cleanHistory($now):void
|
{
|
$this->history = array_map(function ($endpointHistory) use ($now) {
|
return array_filter($endpointHistory, function($timestamp) use ($now) {
|
return ($now - $timestamp) < 3600; //Keep last hour
|
});
|
}, $this->history);
|
}
|
}
|