$userData]); * return Response::error('Not found', 'not_found', 404); * return Response::paginated($items, $total, $page, $perPage); */ class Response { private array $data = []; private int $status = 200; private array $headers = []; private ?WP_REST_Response $response = null; /** * Create a success response */ public static function success(array $data = [], int $status = 200): WP_REST_Response { $response = new self(); $response->data = array_merge(['success' => true], $data); $response->status = $status; return $response->build(); } /** * Create an error response */ public static function error( string $message, string $code = 'error', int $status = 400, ?string $field = null, array $extra = [] ): WP_REST_Response { $data = [ 'success' => false, 'code' => $code, 'message' => $message, ]; if ($field !== null) { $data['field'] = $field; } if (!empty($extra)) { $data = array_merge($data, $extra); } return new WP_REST_Response($data, $status); } /** * Create a validation error response (422) */ public static function validationError(array $errors, string $message = 'Validation failed'): WP_REST_Response { return new WP_REST_Response([ 'success' => false, 'code' => 'validation_error', 'message' => $message, 'errors' => $errors, ], 422); } /** * Create an unauthorized response (401) */ public static function unauthorized(string $message = 'Authentication required'): WP_REST_Response { return self::error($message, 'unauthorized', 401); } /** * Create a forbidden response (403) */ public static function forbidden(string $message = 'Access denied'): WP_REST_Response { return self::error($message, 'forbidden', 403); } /** * Create a not found response (404) */ public static function notFound(string $message = 'Resource not found'): WP_REST_Response { return self::error($message, 'not_found', 404); } /** * Create a rate limit response (429) */ public static function rateLimited(int $retryAfter = 60, string $message = 'Too many requests'): WP_REST_Response { $response = self::error($message, 'rate_limit', 429); $response->header('Retry-After', (string) $retryAfter); return $response; } /** * Create a server error response (500) */ public static function serverError(string $message = 'An unexpected error occurred'): WP_REST_Response { return self::error($message, 'server_error', 500); } /** * Create a paginated response */ public static function paginated( array $items, int $total, int $page = 1, int $perPage = 20, array $extra = [] ): WP_REST_Response { $totalPages = (int) ceil($total / $perPage); $data = array_merge([ 'success' => true, 'items' => $items, 'pagination' => [ 'total' => $total, 'per_page' => $perPage, 'current_page' => $page, 'total_pages' => $totalPages, 'has_more' => $page < $totalPages, ], ], $extra); $response = new WP_REST_Response($data, 200); // Add pagination headers $response->header('X-Total-Count', (string) $total); $response->header('X-Total-Pages', (string) $totalPages); $response->header('X-Current-Page', (string) $page); $response->header('X-Per-Page', (string) $perPage); return $response; } /** * Create a collection response (list without pagination) */ public static function collection(array $items, array $extra = []): WP_REST_Response { $data = array_merge([ 'success' => true, 'items' => $items, 'total' => count($items), ], $extra); return new WP_REST_Response($data, 200); } /** * Create a single item response */ public static function item(array $item, string $key = 'item'): WP_REST_Response { return new WP_REST_Response([ 'success' => true, $key => $item, ], 200); } /** * Create a created response (201) */ public static function created(array $data = [], ?string $location = null): WP_REST_Response { $response = new WP_REST_Response( array_merge(['success' => true], $data), 201 ); if ($location) { $response->header('Location', $location); } return $response; } /** * Create a no content response (204) */ public static function noContent(): WP_REST_Response { return new WP_REST_Response(null, 204); } /** * Create a response from WP_Error */ public static function fromError(WP_Error $error): WP_REST_Response { $data = $error->get_error_data(); $status = is_array($data) && isset($data['status']) ? $data['status'] : 400; return self::error( $error->get_error_message(), $error->get_error_code(), $status ); } /** * Build a response with fluent interface */ public static function make(array $data = []): self { $instance = new self(); $instance->data = $data; return $instance; } /** * Set response data */ public function data(array $data): self { $this->data = array_merge($this->data, $data); return $this; } /** * Set HTTP status code */ public function status(int $status): self { $this->status = $status; return $this; } /** * Add a response header */ public function header(string $name, string $value): self { $this->headers[$name] = $value; return $this; } /** * Add multiple headers */ public function headers(array $headers): self { $this->headers = array_merge($this->headers, $headers); return $this; } /** * Add cache control headers */ public function cache(int $maxAge = 300, bool $private = true): self { $directive = $private ? 'private' : 'public'; $this->headers['Cache-Control'] = "{$directive}, max-age={$maxAge}"; $this->headers['Vary'] = 'Cookie'; return $this; } /** * Add no-cache headers */ public function noCache(): self { $this->headers['Cache-Control'] = 'no-store, no-cache, must-revalidate, max-age=0'; $this->headers['Pragma'] = 'no-cache'; return $this; } /** * Add ETag header for conditional requests */ public function etag(string $data): self { $this->headers['ETag'] = '"' . md5($data) . '"'; return $this; } /** * Add Last-Modified header */ public function lastModified(string|int $timestamp): self { if (is_string($timestamp)) { $timestamp = strtotime($timestamp); } $this->headers['Last-Modified'] = gmdate('D, d M Y H:i:s', $timestamp) . ' GMT'; return $this; } /** * Build and return the response */ public function build(): WP_REST_Response { $response = new WP_REST_Response($this->data, $this->status); foreach ($this->headers as $name => $value) { $response->header($name, $value); } return $response; } /** * Convert to WP_REST_Response (alias for build) */ public function toResponse(): WP_REST_Response { return $this->build(); } // ========================================================================= // UTILITY METHODS // ========================================================================= /** * Format MySQL datetime to ISO 8601 timestamp */ public static function formatTimestamp(?string $mysqlDatetime): ?string { if (empty($mysqlDatetime)) { return null; } try { $wpTimezone = wp_timezone(); $date = new DateTime($mysqlDatetime, $wpTimezone); $date->setTimezone(new DateTimeZone('UTC')); return $date->format('c'); } catch (Exception $e) { return null; } } /** * Format an array of items with a callback */ public static function formatItems(array $items, callable $formatter): array { return array_map($formatter, $items); } /** * Pluck specific fields from items */ public static function pluck(array $items, array $fields): array { return array_map(function($item) use ($fields) { $result = []; foreach ($fields as $field) { if (is_array($item) && array_key_exists($field, $item)) { $result[$field] = $item[$field]; } elseif (is_object($item) && property_exists($item, $field)) { $result[$field] = $item->$field; } } return $result; }, $items); } /** * Add server timestamp to response data */ public static function withTimestamp(array $data): array { $data['timestamp'] = date('c'); $data['server_time'] = date('c'); return $data; } /** * Create response for queued operation */ public static function queued(string $operationId, string $message = 'Queued for processing', array $extra = []): WP_REST_Response { return self::success(array_merge([ 'message' => $message, 'operation_id' => $operationId, 'status' => 'queued', ], $extra), 202); } /** * Create response for operation status */ public static function operationStatus( string $id, string $status, int $progress = 0, int $total = 0, ?string $message = null ): WP_REST_Response { $data = [ 'operation_id' => $id, 'status' => $status, 'progress' => $progress, 'total' => $total, ]; if ($total > 0) { $data['progress_percentage'] = round(($progress / $total) * 100); } if ($message) { $data['message'] = $message; } return self::success($data); } } /** * Alias for backward compatibility */ class ResponseBuilder extends Response {}