From 3b83905603d44b1a08f8b2b36a605808ce686ad6 Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Tue, 02 Jun 2026 00:46:48 +0000
Subject: [PATCH] =double checking schema outputs for legacytattooremoval
---
inc/rest/Route.php | 266 ++++++++++++++++++++++------------------------------
1 files changed, 112 insertions(+), 154 deletions(-)
diff --git a/inc/rest/Route.php b/inc/rest/Route.php
index 2350eea..d769c6e 100644
--- a/inc/rest/Route.php
+++ b/inc/rest/Route.php
@@ -12,8 +12,15 @@
* Fluent REST Route Builder
*
* Usage:
- * Route::get('queue', [$this, 'getQueue'])->auth('user')->args(['status' => 'string']);
- * Route::resource('content')->get(...)->post(...)->delete(false);
+ * // Single-method routes
+ * Route::for('queue')->get([$this, 'getQueue'])->auth('user');
+ * Route::for('content')->post([$this, 'create'])->auth('verified');
+ *
+ * // Multi-method resources
+ * Route::for('uploads')
+ * ->get([$this, 'list'])->auth('user')
+ * ->post([$this, 'upload'])->auth('user')
+ * ->delete(false);
*/
class Route
{
@@ -23,57 +30,21 @@
private bool $registered = false;
private static string $namespace = 'jvb/v1';
- private static ?RateLimiter $rateLimiter = null;
+ private static ?RateLimits $rateLimiter = null;
+
+ // =========================================================================
+ // ENTRY POINTS
+ // =========================================================================
/**
- * Create a resource route (supports multiple methods)
+ * Create a new route builder for the given path
*/
- public static function resource(string $path): self
+ public static function for(string $path): self
{
return new self($path);
}
/**
- * Create a GET route
- */
- public static function get(string $path, callable|array $callback): self
- {
- return (new self($path))->addMethod('GET', $callback);
- }
-
- /**
- * Create a POST route
- */
- public static function post(string $path, callable|array $callback): self
- {
- return (new self($path))->addMethod('POST', $callback);
- }
-
- /**
- * Create a PUT route
- */
- public static function put(string $path, callable|array $callback): self
- {
- return (new self($path))->addMethod('PUT', $callback);
- }
-
- /**
- * Create a PATCH route
- */
- public static function patch(string $path, callable|array $callback): self
- {
- return (new self($path))->addMethod('PATCH', $callback);
- }
-
- /**
- * Create a DELETE route
- */
- public static function delete(string $path, callable|array $callback): self
- {
- return (new self($path))->addMethod('DELETE', $callback);
- }
-
- /**
* Set custom namespace (defaults to 'jvb/v1')
*/
public static function setNamespace(string $namespace): void
@@ -89,13 +60,40 @@
return self::$namespace;
}
+ /**
+ * Convert readable pattern to WordPress regex
+ * Example: 'queue/{id}' becomes 'queue/(?P<id>[a-zA-Z0-9_-]+)'
+ */
+ public static function pattern(string $path, array $patterns = []): string
+ {
+ $defaults = [
+ 'id' => '[a-zA-Z0-9_-]+',
+ 'slug' => '[a-zA-Z0-9_-]+',
+ 'type' => '[a-zA-Z_]+',
+ 'int' => '[0-9]+',
+ ];
+
+ $patterns = array_merge($defaults, $patterns);
+
+ return preg_replace_callback('/\{(\w+)(?::(\w+))?\}/', function($matches) use ($patterns) {
+ $name = $matches[1];
+ $type = $matches[2] ?? $name;
+ $pattern = $patterns[$type] ?? $patterns['id'];
+ return "(?P<{$name}>{$pattern})";
+ }, $path);
+ }
+
private function __construct(string $path)
{
$this->path = '/' . ltrim($path, '/');
}
+ // =========================================================================
+ // HTTP METHODS
+ // =========================================================================
+
/**
- * Add GET method to resource
+ * Add GET handler
*/
public function get(callable|array $callback): self
{
@@ -103,7 +101,7 @@
}
/**
- * Add POST method to resource
+ * Add POST handler
*/
public function post(callable|array $callback): self
{
@@ -111,7 +109,7 @@
}
/**
- * Add PUT method to resource
+ * Add PUT handler
*/
public function put(callable|array $callback): self
{
@@ -119,7 +117,7 @@
}
/**
- * Add PATCH method to resource
+ * Add PATCH handler
*/
public function patch(callable|array $callback): self
{
@@ -127,19 +125,16 @@
}
/**
- * Add DELETE method to resource (pass false to explicitly disable)
+ * Add DELETE handler (pass false to explicitly disable)
*/
public function delete(callable|array|false $callback): self
{
if ($callback === false) {
- return $this; // Explicitly disabled
+ return $this;
}
return $this->addMethod('DELETE', $callback);
}
- /**
- * Internal method to add HTTP method
- */
private function addMethod(string $method, callable|array $callback): self
{
// Finalize previous method if exists
@@ -157,17 +152,21 @@
return $this;
}
+ // =========================================================================
+ // AUTHENTICATION
+ // =========================================================================
+
/**
- * Set authentication/permission requirement
+ * Set authentication requirement
*
- * @param string|array|false $auth
+ * @param string|array|callable|false $auth
* - 'public' or false: Anyone can access
* - 'user': Logged-in user must match 'user' param in request
* - 'logged_in': Any logged-in user
* - 'admin': Users with manage_options capability
* - 'verified': Users with skip_moderation capability
- * - ['capability' => 'edit_posts']: Specific capability check
- * - ['role' => 'artist']: Specific role check
+ * - ['capability' => 'edit_posts']: Specific capability
+ * - ['role' => 'artist']: Specific role
* - ['roles' => ['artist', 'admin']]: Multiple roles (OR)
* - callable: Custom permission callback
*/
@@ -180,31 +179,23 @@
$this->currentMethod['permission_callback'] = match (true) {
$auth === false || $auth === 'public'
=> '__return_true',
-
$auth === 'logged_in'
=> 'is_user_logged_in',
-
$auth === 'user'
=> [PermissionHandler::class, 'userMatch'],
-
$auth === 'admin'
=> [PermissionHandler::class, 'isAdmin'],
-
$auth === 'verified'
=> [PermissionHandler::class, 'isVerified'],
-
+ $auth === 'nonce' => [PermissionHandler::class, 'nonce'],
is_callable($auth)
=> $auth,
-
is_array($auth) && isset($auth['capability'])
=> fn(WP_REST_Request $req) => current_user_can($auth['capability']),
-
is_array($auth) && isset($auth['role'])
=> fn(WP_REST_Request $req) => PermissionHandler::hasRole($req, $auth['role']),
-
is_array($auth) && isset($auth['roles'])
=> fn(WP_REST_Request $req) => PermissionHandler::hasAnyRole($req, $auth['roles']),
-
default
=> '__return_true',
};
@@ -213,10 +204,7 @@
}
/**
- * Add rate limiting to the route
- *
- * @param int $limit Maximum requests
- * @param int $window Time window in seconds
+ * Add rate limiting
*/
public function rateLimit(int $limit = 60, int $window = 60): self
{
@@ -227,12 +215,10 @@
$originalCallback = $this->currentMethod['permission_callback'];
$this->currentMethod['permission_callback'] = function(WP_REST_Request $request) use ($originalCallback, $limit, $window) {
- // Initialize rate limiter if needed
if (self::$rateLimiter === null) {
- self::$rateLimiter = new RateLimiter();
+ self::$rateLimiter = new RateLimits();
}
- // Check rate limit first
if (!self::$rateLimiter->checkLimit($request, $limit, $window)) {
return new WP_Error(
'rate_limit',
@@ -241,16 +227,13 @@
);
}
- // Then check original permission
if ($originalCallback === '__return_true') {
return true;
}
- if (is_callable($originalCallback)) {
- return call_user_func($originalCallback, $request);
- }
-
- return true;
+ return is_callable($originalCallback)
+ ? call_user_func($originalCallback, $request)
+ : true;
};
return $this;
@@ -258,9 +241,6 @@
/**
* Require nonce verification
- *
- * @param string $action Nonce action name (default: 'wp_rest')
- * @param string $header Header name containing nonce (default: 'X-WP-Nonce')
*/
public function nonce(string $action = 'wp_rest', string $header = 'X-WP-Nonce'): self
{
@@ -272,7 +252,9 @@
$this->currentMethod['permission_callback'] = function(WP_REST_Request $request) use ($originalCallback, $action, $header) {
$nonce = $request->get_header($header);
-
+ error_log('[Route] Validating nonce....');
+ error_log('Nonce: '.print_r($nonce, true));
+ error_log('Action: '.print_r($action, true));
if (!wp_verify_nonce($nonce, $action)) {
return new WP_Error(
'invalid_nonce',
@@ -281,35 +263,31 @@
);
}
- // Then check original permission
if ($originalCallback === '__return_true') {
return true;
}
- if (is_callable($originalCallback)) {
- return call_user_func($originalCallback, $request);
- }
-
- return true;
+ return is_callable($originalCallback)
+ ? call_user_func($originalCallback, $request)
+ : true;
};
return $this;
}
+ // =========================================================================
+ // ARGUMENTS
+ // =========================================================================
+
/**
* Define route arguments with shorthand syntax
*
- * @param array $args Argument definitions
- * Shorthand: ['name' => 'type|required|default:value|enum:a,b,c']
- * Full: ['name' => ['type' => 'string', 'required' => true, ...]]
- *
* Examples:
* 'status' => 'string'
* 'status' => 'string|required'
* 'status' => 'string|default:all'
* 'status' => 'string|enum:pending,completed,failed'
* 'limit' => 'integer|default:50|min:1|max:100'
- * 'ids' => 'array|required'
*/
public function args(array $args): self
{
@@ -337,12 +315,8 @@
return $this;
}
- /**
- * Parse shorthand argument definition into WP REST format
- */
private function parseArgDefinition(string|array $definition): array
{
- // Already full format
if (is_array($definition)) {
return $definition;
}
@@ -351,50 +325,60 @@
$type = trim($parts[0]);
$arg = [
- 'type' => $type,
+ 'type' => $type === 'int' ? 'integer' : ($type === 'bool' ? 'boolean' : $type),
'required' => false,
];
- // Add sanitize callback based on type
+ // Sanitize callback based on type
$arg['sanitize_callback'] = match ($type) {
'integer', 'int' => 'absint',
'string' => 'sanitize_text_field',
'email' => 'sanitize_email',
'url' => 'esc_url_raw',
'boolean', 'bool' => 'rest_sanitize_boolean',
+ 'array' => function($value) {
+ if (is_array($value)) {
+ return $value;
+ }
+ return [];
+ },
+ 'object' => function($value) {
+ if (is_array($value) || is_object($value)) {
+ return (array) $value;
+ }
+ return [];
+ },
default => null,
};
- // Normalize type for WP
- if ($type === 'int') {
- $arg['type'] = 'integer';
- } elseif ($type === 'bool') {
- $arg['type'] = 'boolean';
+ // Add validate callback for array/object types
+ if (in_array($type, ['array', 'object'])) {
+ $arg['validate_callback'] = function($value, $request, $param) {
+ // Allow empty arrays/objects
+ if (empty($value)) {
+ return true;
+ }
+ // Ensure it's an array or object
+ return is_array($value) || is_object($value);
+ };
}
// Parse modifiers
foreach (array_slice($parts, 1) as $part) {
$part = trim($part);
- if ($part === 'required') {
- $arg['required'] = true;
- } elseif (str_starts_with($part, 'default:')) {
- $value = substr($part, 8);
- $arg['default'] = $this->castValue($value, $type);
- } elseif (str_starts_with($part, 'enum:')) {
- $arg['enum'] = array_map('trim', explode(',', substr($part, 5)));
- } elseif (str_starts_with($part, 'min:')) {
- $arg['minimum'] = (int) substr($part, 4);
- } elseif (str_starts_with($part, 'max:')) {
- $arg['maximum'] = (int) substr($part, 4);
- } elseif (str_starts_with($part, 'desc:')) {
- $arg['description'] = substr($part, 5);
- } elseif (str_starts_with($part, 'pattern:')) {
- $arg['pattern'] = substr($part, 8);
- }
+ match (true) {
+ $part === 'required' => $arg['required'] = true,
+ str_starts_with($part, 'default:') => $arg['default'] = $this->castValue(substr($part, 8), $type),
+ str_starts_with($part, 'enum:') => $arg['enum'] = array_map('trim', explode(',', substr($part, 5))),
+ str_starts_with($part, 'min:') => $arg['minimum'] = (int) substr($part, 4),
+ str_starts_with($part, 'max:') => $arg['maximum'] = (int) substr($part, 4),
+ str_starts_with($part, 'desc:') => $arg['description'] = substr($part, 5),
+ str_starts_with($part, 'pattern:') => $arg['pattern'] = substr($part, 8),
+ default => null,
+ };
}
- // Remove null sanitize callback
if ($arg['sanitize_callback'] === null) {
unset($arg['sanitize_callback']);
}
@@ -402,9 +386,6 @@
return $arg;
}
- /**
- * Cast value to appropriate type
- */
private function castValue(string $value, string $type): mixed
{
return match ($type) {
@@ -416,6 +397,10 @@
};
}
+ // =========================================================================
+ // REGISTRATION
+ // =========================================================================
+
/**
* Register the route with WordPress
*/
@@ -425,7 +410,6 @@
return $this;
}
- // Add current method if not empty
if (!empty($this->currentMethod)) {
$this->methods[] = $this->currentMethod;
$this->currentMethod = [];
@@ -435,13 +419,10 @@
return $this;
}
- // Register single method or array of methods
$config = count($this->methods) === 1 ? $this->methods[0] : $this->methods;
-
register_rest_route(self::$namespace, $this->path, $config);
$this->registered = true;
-
return $this;
}
@@ -450,31 +431,8 @@
*/
public function __destruct()
{
- if (!$this->registered && !empty($this->methods) || !empty($this->currentMethod)) {
+ if (!$this->registered && (!empty($this->methods) || !empty($this->currentMethod))) {
$this->register();
}
}
-
- /**
- * Convert WordPress route pattern to more readable format
- * Converts: queue/{id} to queue/(?P<id>[a-zA-Z0-9_-]+)
- */
- public static function pattern(string $path, array $patterns = []): string
- {
- $defaults = [
- 'id' => '[a-zA-Z0-9_-]+',
- 'slug' => '[a-zA-Z0-9_-]+',
- 'type' => '[a-zA-Z_]+',
- 'int' => '[0-9]+',
- ];
-
- $patterns = array_merge($defaults, $patterns);
-
- return preg_replace_callback('/\{(\w+)(?::(\w+))?\}/', function($matches) use ($patterns) {
- $name = $matches[1];
- $type = $matches[2] ?? $name;
- $pattern = $patterns[$type] ?? $patterns['id'];
- return "(?P<{$name}>{$pattern})";
- }, $path);
- }
}
--
Gitblit v1.10.0