From 42fa8304ddb811b0f725f245130f70c0f5e86a6c Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Tue, 04 Nov 2025 06:12:02 +0000
Subject: [PATCH] =Refactored LoginManager to be more extensible and configurable, as well as an AjaxRateLimiter

---
 inc/rest/RestRouteManager.php |  332 ++++++++++++++++++++++++++++++++++++++++++-------------
 1 files changed, 254 insertions(+), 78 deletions(-)

diff --git a/inc/rest/RestRouteManager.php b/inc/rest/RestRouteManager.php
index 91f76f2..8f1b1ba 100644
--- a/inc/rest/RestRouteManager.php
+++ b/inc/rest/RestRouteManager.php
@@ -50,7 +50,7 @@
         $this->base = BASE;
         $this->rate_limiter = new RateLimiter();
         if ($this->cache_name !== '') {
-            $this->cache = new CacheManager($this->cache_name, $this->cache_ttl);
+            $this->cache = CacheManager::for($this->cache_name, $this->cache_ttl);
         }
         add_action('rest_api_init', [$this, 'registerRoutes']);
     }
@@ -94,6 +94,7 @@
     {
         // Check rate limits first
         if (!$this->rate_limiter->checkLimit($request)) {
+			error_log('Rate Limit Reached');
             return new WP_Error(
                 'rate_limit_reached',
                 'Rate limit reached',
@@ -102,6 +103,7 @@
         }
         $user_id = $request->get_param('user');
         if (!empty($user_id) && !$this->userCheck($user_id)) {
+			error_log('Usercheck failed');
             return false;
         }
         // Verify nonces
@@ -180,82 +182,64 @@
         }
     }
 
-    /**
-     * @param int $userID The user ID to check
-     *
-     * @return bool whether user exists
-     */
-    protected function checkUser(int $userID):bool
-    {
-        $checked = $this->cache->get($userID, 'checked_users');
-        if ($checked) {
-            return $checked;
-        }
-        $test = (bool)get_userdata($userID);
+	/**
+	 * Check if user exists (cached)
+	 */
+	protected function checkUser(int $userID): bool
+	{
+		$cache = CacheManager::for('users');
 
-        $this->cache->set($userID, $test, null, 'checked_users');
-        return $test;
-    }
+		return $cache->remember("user_exists_{$userID}", function() use ($userID) {
+			return (bool)get_userdata($userID);
+		}, DAY_IN_SECONDS);
+	}
 
-    /**
-     * @param int $shopID the shop ID to check
-     *
-     * @return bool whether the shop exists
-     */
-    protected function checkShop(int $shopID):bool
-    {
-        $checked = $this->cache->get($shopID, 'checked_shops');
-        if ($checked) {
-            return (bool)$checked;
-        }
-        $test =  term_exists($shopID, BASE . 'shop');
-        $this->cache->set($shopID, (int)$test, null, 'checked_shops');
-        return $test;
-    }
+	/**
+	 * Check if shop exists (cached)
+	 */
+	protected function checkShop(int $shopID): bool
+	{
+		$cache = CacheManager::for('shop');
 
-	protected function checkTerm(array $args) {
-		$termID = $args['to_term']??$args['term_id']??false;
+		return $cache->remember("shop_exists_{$shopID}", function() use ($shopID) {
+			return (bool)term_exists($shopID, BASE . 'shop');
+		}, DAY_IN_SECONDS);
+	}
+
+	/**
+	 * Check if term exists (cached)
+	 */
+	protected function checkTerm(array $args): bool
+	{
+		$termID = $args['to_term'] ?? $args['term_id'] ?? false;
 		if (!$termID) {
 			return false;
 		}
-		$taxonomy = $args['taxonomy']??false;
+
+		$taxonomy = $args['taxonomy'] ?? false;
 		if (!$taxonomy) {
 			return false;
 		}
-		$checked = $this->cache->get($termID, 'checked_'.$taxonomy);
-		if ($checked) {
-			return (bool) $checked;
-		}
-		$test = term_exists($termID, jvbCheckBase($taxonomy));
-		$this->cache->set($termID, (int)$test, null, 'checked_'.$taxonomy);
-		return (bool)$test;
+
+		$taxonomy = jvbCheckBase($taxonomy);
+		$cache = CacheManager::for($taxonomy);
+
+		return $cache->remember("term_exists_{$termID}", function() use ($termID, $taxonomy) {
+			return (bool)term_exists($termID, $taxonomy);
+		}, DAY_IN_SECONDS);
 	}
 
-    /**
-     * Check if an artist is verified
-     *
-     * @param int $user_id User ID
-     * @return bool True if verified
-     */
-    public function isVerifiedUser(int $user_id):bool
-    {
-        // Cache result to avoid repeated checks
-        $cache_key = "verified_users";
-        $verified = $this->cache->get($cache_key, 'users');
-        $verified = ($verified) ?: [];
-        if (array_key_exists($user_id, $verified)) {
-            return (bool) $verified[$user_id];
-        }
+	/**
+	 * Check if an artist is verified
+	 */
+	public function isVerifiedUser(int $user_id): bool
+	{
+		$cache = CacheManager::forUser($user_id);
 
-        // Check if user has the skip_moderation capability
-        $is_verified = user_can($user_id, 'skip_moderation');
-
-        $verified[$user_id] = $is_verified;
-        // Cache for a day
-        $this->cache->set($cache_key, $verified, DAY_IN_SECONDS, 'users');
-
-        return $is_verified;
-    }
+		return $cache->remember('is_verified', function() use ($user_id) {
+			return user_can($user_id, 'skip_moderation');
+		}, DAY_IN_SECONDS);
+	}
 
     protected function applyTaxonomyFilters(array $args, array $data):array
     {
@@ -405,27 +389,219 @@
 		return $wpdb->get_var("SHOW TABLES LIKE '{$tableName}'") !== $tableName;
 	}
 
-	protected function ifModifiedSince($lastModified, $params, $request):WP_REST_Response|null {
-		$etag = '"' . md5(serialize($params)) . '"';
-		// Check ETag
+	// ========== HTTP CACHING METHODS ==========
+
+	/**
+	 * Check HTTP caching headers (ETag and If-Modified-Since)
+	 * Returns 304 Not Modified if content hasn't changed
+	 *
+	 * @param WP_REST_Request $request The REST request
+	 * @param string|array $content_types Content type(s) to check timestamps for
+	 * @param array $additional_params Additional params for ETag uniqueness (e.g., user_id, filters)
+	 * @return WP_REST_Response|null Returns 304 response if not modified, null to continue processing
+	 */
+	protected function checkHeaders(
+		WP_REST_Request $request,
+		string|array $content_types,
+		array $additional_params = []
+	): WP_REST_Response|null {
+
+		// Get latest timestamp for the content type(s)
+		$last_modified = CacheManager::getTimestamp($content_types);
+
+		// Generate ETag from request params + timestamp
+		$etag = $this->generateETag($request->get_params(), $additional_params, $last_modified);
+
+		// Check If-None-Match (ETag) header
 		$if_none_match = $request->get_header('If-None-Match');
-		if ($if_none_match && $if_none_match === $etag) {
-			return new WP_REST_Response(null, 304);
+		if ($if_none_match === $etag) {
+			return $this->createNotModifiedResponse($etag, $last_modified);
 		}
 
+		// Check If-Modified-Since header
 		$if_modified_since = $request->get_header('If-Modified-Since');
-		if ($if_modified_since && $lastModified) {
+		if ($if_modified_since) {
 			$if_modified_timestamp = strtotime($if_modified_since);
-			if ($lastModified <= $if_modified_timestamp) {
-				return new WP_REST_Response(null, 304);
+			if ($last_modified <= $if_modified_timestamp) {
+				return $this->createNotModifiedResponse($etag, $last_modified);
 			}
 		}
 
-		header('ETag: ' . $etag);  // Add this line
-		if ($lastModified) {
-			header('Last-Modified: ' . gmdate('D, d M Y H:i:s', $lastModified) . ' GMT');
+		// Content has changed - store headers to add to successful response
+		$this->response_headers = $this->buildCacheHeaders($etag, $last_modified);
+
+		return null; // Continue processing
+	}
+
+	/**
+	 * Generate ETag from request parameters and timestamp
+	 *
+	 * @param array $params Request parameters
+	 * @param array $additional Additional parameters for uniqueness
+	 * @param int $timestamp Last modified timestamp
+	 * @return string ETag value with quotes
+	 */
+	private function generateETag(array $params, array $additional, int $timestamp): string
+	{
+		// Combine all data that makes this response unique
+		$etag_data = array_merge(
+			$params,
+			$additional,
+			['t' => $timestamp]
+		);
+
+		return '"' . md5(serialize($etag_data)) . '"';
+	}
+
+	/**
+	 * Create 304 Not Modified response with proper headers
+	 *
+	 * @param string $etag ETag value
+	 * @param int $last_modified Last modified timestamp
+	 * @return WP_REST_Response 304 response
+	 */
+	private function createNotModifiedResponse(string $etag, int $last_modified): WP_REST_Response
+	{
+		$response = new WP_REST_Response(null, 304);
+		$response->set_headers($this->buildCacheHeaders($etag, $last_modified));
+		return $response;
+	}
+
+	/**
+	 * Build cache headers array
+	 *
+	 * @param string $etag ETag value
+	 * @param int $last_modified Last modified timestamp
+	 * @return array Headers array
+	 */
+	private function buildCacheHeaders(string $etag, int $last_modified): array
+	{
+		return [
+			'ETag' => $etag,
+			'Last-Modified' => gmdate('D, d M Y H:i:s', $last_modified) . ' GMT',
+			'Cache-Control' => 'private, max-age=60, must-revalidate'
+		];
+	}
+
+	/**
+	 * Add stored cache headers to a response
+	 * Call this on your final WP_REST_Response before returning
+	 *
+	 * @param WP_REST_Response $response The response to add headers to
+	 * @return WP_REST_Response The response with headers added
+	 */
+	protected function addCacheHeaders(WP_REST_Response $response): WP_REST_Response
+	{
+		if (!empty($this->response_headers)) {
+			$response->set_headers($this->response_headers);
+			$this->response_headers = []; // Clear after use
 		}
-		header('Cache-Control: private, max-age=30');
-		return null;
+		return $response;
+	}
+
+	/**
+	 * Helper: Check headers for user-specific endpoints
+	 * Automatically includes user_id in ETag
+	 *
+	 * @param WP_REST_Request $request The REST request
+	 * @param int $user_id User ID
+	 * @param string|array $content_types Content type(s)
+	 * @return WP_REST_Response|null
+	 */
+	protected function checkUserHeaders(
+		WP_REST_Request $request,
+		int $user_id,
+		string|array $content_types = 'user'
+	): WP_REST_Response|null {
+
+		// Include user-specific timestamp
+		$types = is_array($content_types) ? $content_types : [$content_types];
+		$types[] = "user_{$user_id}";
+
+		return $this->checkHeaders($request, $types, ['user_id' => $user_id]);
 	}
 }
+//
+//Simple example:
+//public function getTattoos(WP_REST_Request $request): WP_REST_Response
+//{
+//	// Check HTTP cache headers first
+//	$cache_check = $this->checkHeaders($request, 'tattoo');
+//	if ($cache_check) {
+//		return $cache_check; // Returns 304 Not Modified
+//	}
+//
+//	// Get data (use CacheManager for data caching too!)
+//	$filters = $request->get_params();
+//	$cache = CacheManager::for('tattoo');
+//
+//	$tattoos = $cache->remember($filters, function() use ($filters) {
+//		return $this->queryTattoos($filters);
+//	}, 300);
+//
+//	$response = new WP_REST_Response(['items' => $tattoos]);
+//	return $this->addCacheHeaders($response); // Add ETag and Last-Modified
+//}
+//
+//Multiple Content Types:
+//public function getTermsWithContent(WP_REST_Request $request): WP_REST_Response
+//{
+//	$taxonomy = $request->get_param('taxonomy');
+//
+//	// Check both taxonomy and its content types
+//	$cache_check = $this->checkHeaders($request, [$taxonomy, 'tattoo', 'artwork']);
+//	if ($cache_check) {
+//		return $cache_check;
+//	}
+//
+//	// ... fetch data ...
+//
+//	$response = new WP_REST_Response($data);
+//	return $this->addCacheHeaders($response);
+//}
+//
+//User-specific:
+//public function getUserFavorites(WP_REST_Request $request): WP_REST_Response
+//{
+//	$user_id = $request->get_param('user');
+//
+//	// Automatically checks user_{$user_id} timestamp + includes user_id in ETag
+//	$cache_check = $this->checkUserHeaders($request, $user_id);
+//	if ($cache_check) {
+//		return $cache_check;
+//	}
+//
+//	// Get user's favorites (cached per user)
+//	$favorites = CacheManager::forUser($user_id)->remember('favorites', function() use ($user_id) {
+//		return $this->getUserFavorites($user_id);
+//	}, 1800);
+//
+//	$response = new WP_REST_Response(['items' => $favorites]);
+//	return $this->addCacheHeaders($response);
+//}
+//
+//Complex with additional params:
+//public function getFilteredContent(WP_REST_Request $request): WP_REST_Response
+//{
+//	$user_id = get_current_user_id();
+//	$filters = $request->get_params();
+//
+//	// Include custom params in ETag for uniqueness
+//	$cache_check = $this->checkHeaders(
+//		$request,
+//		'tattoo',
+//		[
+//			'user_id' => $user_id,
+//			'is_verified' => $this->isVerifiedUser($user_id)
+//		]
+//	);
+//
+//	if ($cache_check) {
+//		return $cache_check;
+//	}
+//
+//	// ... fetch filtered data ...
+//
+//	$response = new WP_REST_Response($data);
+//	return $this->addCacheHeaders($response);
+//}

--
Gitblit v1.10.0