From 3baf3d2545ba6ece6b74a64c0def59bd0774cf54 Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Wed, 10 Jun 2026 16:34:12 +0000
Subject: [PATCH] =Laid the groundwork for an improved DashboardManager.php setup. Have to put it aside so I can get the dang Northeh done though.

---
 inc/rest/RateLimits.php |   98 +++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 98 insertions(+), 0 deletions(-)

diff --git a/inc/rest/RateLimits.php b/inc/rest/RateLimits.php
index e69de29..41d75b8 100644
--- a/inc/rest/RateLimits.php
+++ b/inc/rest/RateLimits.php
@@ -0,0 +1,98 @@
+<?php
+namespace JVBase\rest;
+
+use WP_REST_Request;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+/**
+ * Handles rate limiting for REST requests
+ */
+class RateLimits
+{
+	protected string $cacheGroup = 'jvb_rate_limits';
+
+	protected array $defaults = [
+		'GET' => ['count' => 1000, 'window' => 3600],
+		'POST' => ['count' => 100, 'window' => 3600],
+		'PUT' => ['count' => 100, 'window' => 3600],
+		'PATCH' => ['count' => 100, 'window' => 3600],
+		'DELETE' => ['count' => 50, 'window' => 3600],
+	];
+
+	/**
+	 * Check if request is within rate limits
+	 *
+	 * @param WP_REST_Request $request
+	 * @param int|null $limit Optional custom limit (overrides defaults)
+	 * @param int|null $window Optional custom window in seconds (overrides defaults)
+	 * @return bool True if within limits, false if exceeded
+	 */
+	public function checkLimit(WP_REST_Request $request, ?int $limit = null, ?int $window = null): bool
+	{
+		$method = $request->get_method();
+		$default = $this->defaults[$method] ?? $this->defaults['GET'];
+
+		$limit = $limit ?? $default['count'];
+		$window = $window ?? $default['window'];
+
+		$key = $this->getCacheKey($request, $window);
+		$current = (int) wp_cache_get($key, $this->cacheGroup);
+
+		if ($current >= $limit) {
+			return false;
+		}
+
+		// Increment or initialize
+		if ($current === 0) {
+			wp_cache_set($key, 1, $this->cacheGroup, $window);
+		} else {
+			wp_cache_incr($key, 1, $this->cacheGroup);
+		}
+
+		return true;
+	}
+
+	/**
+	 * Get remaining requests for current window
+	 */
+	public function getRemaining(WP_REST_Request $request, ?int $limit = null, ?int $window = null): int
+	{
+		$method = $request->get_method();
+		$default = $this->defaults[$method] ?? $this->defaults['GET'];
+
+		$limit = $limit ?? $default['count'];
+		$window = $window ?? $default['window'];
+
+		$key = $this->getCacheKey($request, $window);
+		$current = (int) wp_cache_get($key, $this->cacheGroup);
+
+		return max(0, $limit - $current);
+	}
+
+	/**
+	 * Reset rate limit for a request pattern
+	 */
+	public function reset(WP_REST_Request $request, ?int $window = null): void
+	{
+		$method = $request->get_method();
+		$default = $this->defaults[$method] ?? $this->defaults['GET'];
+		$window = $window ?? $default['window'];
+
+		$key = $this->getCacheKey($request, $window);
+		wp_cache_delete($key, $this->cacheGroup);
+	}
+
+	protected function getCacheKey(WP_REST_Request $request, int $window): string
+	{
+		$ip = $request->get_header('X-Forwarded-For') ?: ($_SERVER['REMOTE_ADDR'] ?? 'unknown');
+		$userId = get_current_user_id();
+		$method = $request->get_method();
+		$route = $request->get_route();
+
+		// Include window in key so different windows don't collide
+		return "rate:{$ip}:{$userId}:{$method}:{$route}:{$window}";
+	}
+}

--
Gitblit v1.10.0