cacheName = 'invitations'; parent::__construct(); // Get invitation configuration $this->inviteConfig = JVB()->invitations()->getInviteConfig(); $this->table = CustomTable::for('invitations'); // Cache connections $this->cache ->connect('user') ->connect('taxonomy'); } public function registerRoutes(): void { Route::for('invitations') ->get([$this, 'getInvitations']) ->args([ 'user' => 'int|required', 'to_term' => 'int', 'taxonomy' => 'string', 'status' => 'string|enum:all,pending,accepted,rejected,expired,revoked|default:all', 'page' => 'int|default:1|min:1' ]) ->auth('user') ->rateLimit(20) ->post([$this, 'createInvitationRequest']) ->args([ 'user' => 'int|required', 'action' => 'string|enum:create,revoke,refresh|default:create', 'invites' => 'array', 'invitation_id' => 'int' ]) ->auth('verified') ->rateLimit(10, 300); } /** * Get invitations for a user or term */ public function getInvitations(WP_REST_Request $request): WP_REST_Response { $userID = $request->get_param('user'); $termID = $request->get_param('to_term'); $taxonomy = $request->get_param('taxonomy'); // Validate user if (get_current_user_id() !== $userID) { return $this->unauthorized('Invalid user'); } $args = [ 'user' => $userID, 'to_term' => $termID, 'taxonomy' => $taxonomy ? jvbNoBase($taxonomy) : null, 'status' => $request->get_param('status'), 'page' => $request->get_param('page') ]; // Check cache $key = $this->cache->generateKey($args); if ($cached = $this->cache->get($key)) { return $this->success($cached); } // Get appropriate invitations $result = ($args['to_term'] && $args['taxonomy']) ? $this->getTermInvitations($args) : $this->getUserInvitations($args); // Cache result $this->cache->set($key, $result); return $this->success($result); } /** * Get invitations for a specific term */ protected function getTermInvitations(array $args): array { // Check permission if (!JVB()->roles()->isManager($args['user'], $args['to_term'])) { return $this->forbidden('You cannot view invitations for this ' . $args['taxonomy'])->get_data(); } $perPage = 20; $offset = ($args['page'] - 1) * $perPage; // Build query $where = ['to_' . $args['taxonomy'] => $args['to_term']]; if ($args['status'] !== 'all') { $where['status'] = $args['status']; } // Fluent CustomTable usage $total = $this->table ->where($where) ->countResults(); $invitations = $this->table ->where($where) ->orderBy('created_at') ->limit($perPage, $offset) ->getResults(); return [ 'invitations' => array_map([$this, 'formatInvitation'], $invitations), 'total' => $total, 'pages' => ceil($total / $perPage), 'page' => $args['page'], 'per_page' => $perPage ]; } /** * Get invitations sent by a user */ protected function getUserInvitations(array $args): array { $perPage = 20; $offset = ($args['page'] - 1) * $perPage; // Use raw query for JSON search $query = "SELECT * FROM {$this->table->getFullTableName()} WHERE JSON_SEARCH(inviters, 'one', %s, NULL, '$[*].user_id') IS NOT NULL"; $params = [$args['user']]; if ($args['status'] !== 'all') { $query .= " AND status = %s"; $params[] = $args['status']; } $query .= " ORDER BY created_at DESC LIMIT %d OFFSET %d"; $params = array_merge($params, [$perPage, $offset]); $invitations = $this->table->queryResults($query, $params); // Get count $countQuery = str_replace('SELECT *', 'SELECT COUNT(*)', substr($query, 0, strrpos($query, 'ORDER BY'))); $countParams = array_slice($params, 0, -2); $total = (int) $this->table->queryVar($countQuery, $countParams); return [ 'invitations' => array_map([$this, 'formatInvitation'], $invitations), 'total' => $total, 'pages' => ceil($total / $perPage), 'page' => $args['page'], 'per_page' => $perPage ]; } /** * Create invitation request - queues invitations for processing */ public function createInvitationRequest(WP_REST_Request $request): WP_REST_Response { $userID = $request->get_param('user'); $action = $request->get_param('action'); // Validate user if (get_current_user_id() !== $userID) { return $this->unauthorized('Invalid user'); } // Handle actions return match($action) { 'revoke' => $this->revokeInvite($userID, $request->get_params()), 'refresh' => $this->resendInvite($userID, $request->get_params()), default => $this->queueInvitations($userID, $request->get_param('invites')) }; } protected function queueInvitations(int $userID, array $invites): WP_REST_Response { if (empty($invites)) { return $this->error('No invitations provided'); } // Validate invitations $validated = $this->validateInvitations($userID, $invites); if (empty($validated)) { return $this->error('No valid invitations to send'); } // Queue using fluent interface $op = JVB()->queue()->add( 'invitation_create', $userID, ['invitations' => $validated], [ 'priority' => 'high', 'chunk_key' => 'invitations', 'chunk_size' => 20 ] ); return $this->queued($op['operation_id']); } /** * Validate and sanitize invitation data */ protected function validateInvitations(int $userID, array $invites): array { return JVB()->invitations()->validateInvitations($userID, $invites); } /** * Revoke an invitation */ protected function revokeInvite(int $userID, array $data): WP_REST_Response { $invitationID = (int) ($data['invitation_id'] ?? 0); if (!$invitationID) { return $this->error('Invitation ID required'); } $op = JVB()->queue()->add( 'invitation_revoke', $userID, ['invitation_id' => $invitationID], ['priority' => 'high'] ); return $this->queued($op['operation_id']); } /** * Resend an invitation */ protected function resendInvite(int $userID, array $data): WP_REST_Response { $invitationID = (int) ($data['invitation_id'] ?? 0); if (!$invitationID) { return $this->error('Invitation ID required'); } $op = JVB()->queue()->add( 'invitation_resend', $userID, ['invitation_id' => $invitationID], ['priority' => 'high'] ); if (is_wp_error($op)) { return $this->error($op->get_error_message()); } return $this->queued($op['operation_id']); } /** * Format invitation for API response */ protected function formatInvitation(object $invitation): array { $inviters = json_decode($invitation->inviters ?? '[]', true) ?: []; $formatted = [ 'id' => (int) $invitation->id, 'name' => $invitation->name, 'email' => $invitation->email, 'invited_role' => $invitation->invited_role, 'status' => $invitation->status, 'expires_at' => $invitation->expires_at, 'accepted_at' => $invitation->accepted_at ?? null, 'created_at' => $invitation->created_at, 'inviters' => array_map(fn($inviter) => [ 'id' => (int) $inviter['user_id'], 'name' => jvbGetUsername($inviter['user_id']), 'invited_at' => $inviter['invited_at'] ], $inviters) ]; // Add term information if present foreach (JVB()->roles()->getInvitableTaxonomies() as $taxonomy) { $column = 'to_' . $taxonomy; if (isset($invitation->$column) && $invitation->$column) { $termID = (int) $invitation->$column; $term = get_term($termID, BASE . $taxonomy); if ($term && !is_wp_error($term)) { $formatted['term'] = [ 'id' => $termID, 'name' => $term->name, 'taxonomy' => $taxonomy ]; break; // Only show first term } } } return $formatted; } }