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