table = CustomTable::for('invitations'); $this->roleManager = new RoleManager(); } public function execute(Operation $operation, Progress $progress): Result { return match($operation->type) { 'invitation_create' => $this->processCreation($operation, $progress), 'invitation_resend' => $this->processResend($operation, $progress), 'invitation_revoke' => $this->processRevoke($operation, $progress), default => Result::fail("Unknown operation type: {$operation->type}") }; } protected function processCreation(Operation $operation, Progress $progress): Result { $invitations = $operation->requestData['invitations'] ?? []; $userID = $operation->userId; $results = [ 'success' => [], 'failed' => [] ]; $this->table->startTransaction(); try { foreach ($invitations as $index => $invite) { $progress->track($index); $result = $this->createSingleInvitation( $invite['name'], $invite['email'], $userID, $invite['invited_role'], $invite['to_term'] ?? null, $invite['taxonomy'] ?? null ); if (is_wp_error($result)) { $results['failed'][] = [ 'email' => $invite['email'], 'name' => $invite['name'], 'reason' => $result->get_error_message() ]; } else { $results['success'][] = $result; } } if (!empty($results['success'])) { $this->table->commit(); // Send emails after successful commit foreach ($results['success'] as $invitation) { $this->sendInvitationEmail($invitation, $userID); } } else { $this->table->rollback(); } return Result::success($results, [ 'sent' => count($results['success']), 'failed' => count($results['failed']) ]); } catch (\Exception $e) { $this->table->rollback(); return Result::fail($e->getMessage()); } } protected function createSingleInvitation( string $name, string $email, int $inviterID, string $invitedRole, ?int $termID = null, ?string $taxonomy = null ): WP_Error|array { // Check for existing invitation $existing = $this->table->get([ 'email' => $email, 'invited_role' => $invitedRole ]); $token = wp_generate_password(32, false); $expiresAt = date('Y-m-d H:i:s', strtotime("+{$this->expiryDays} days")); if ($existing) { // Update existing $inviters = json_decode($existing->inviters, true) ?: []; $inviterExists = false; foreach ($inviters as &$inviter) { if ($inviter['user_id'] == $inviterID) { $inviterExists = true; $inviter['invited_at'] = current_time('mysql'); break; } } if (!$inviterExists) { $inviters[] = [ 'user_id' => $inviterID, 'invited_at' => current_time('mysql') ]; } $updateData = [ 'inviters' => json_encode($inviters), 'status' => 'pending', 'expires_at' => $expiresAt ]; if ($termID && $taxonomy) { $updateData['to_' . $taxonomy] = $termID; } if ($existing->status === 'expired') { $updateData['invitation_token'] = $token; } else { $token = $existing->invitation_token; } $this->table->update($updateData, ['id' => $existing->id]); $invitationID = $existing->id; } else { // Create new $insertData = [ 'name' => sanitize_text_field($name), 'email' => $email, 'invitation_token' => $token, 'invited_role' => $invitedRole, 'status' => 'pending', 'inviters' => json_encode([[ 'user_id' => $inviterID, 'invited_at' => current_time('mysql') ]]), 'expires_at' => $expiresAt ]; if ($termID && $taxonomy) { $insertData['to_' . $taxonomy] = $termID; } $invitationID = $this->table->insert($insertData); } return [ 'id' => $invitationID, 'token' => $token, 'expires_at' => $expiresAt, 'to_term' => $termID, 'taxonomy' => $taxonomy, 'invited_role' => $invitedRole, 'email' => $email, 'name' => $name ]; } protected function sendInvitationEmail(array $invitation, int $inviterID): void { $terms = []; if ($invitation['to_term'] && $invitation['taxonomy']) { $terms[$invitation['taxonomy']] = $invitation['to_term']; } // This would call your email service do_action( BASE . 'send_invitation_email', $invitation['name'], $invitation['email'], $invitation['token'], $inviterID, $terms, $invitation['invited_role'] ); } protected function processResend(Operation $operation, Progress $progress): Result { $invitationID = $operation->requestData['invitation_id'] ?? 0; $userID = $operation->userId; if (!$invitationID) { return Result::fail('Invitation ID required'); } // Get invitation $invitation = $this->table->get(['id' => $invitationID]); if (!$invitation) { return Result::fail('Invitation not found'); } // Verify status if (!in_array($invitation->status, ['pending', 'expired'])) { return Result::fail('Only pending or expired invitations can be resent'); } // Check if user is an inviter $inviters = json_decode($invitation->inviters, true) ?: []; $isInviter = false; foreach ($inviters as &$inviter) { if ($inviter['user_id'] == $userID) { $isInviter = true; $inviter['invited_at'] = current_time('mysql'); break; } } if (!$isInviter) { return Result::fail('You are not authorized to resend this invitation'); } // Generate new token and expiry $token = wp_generate_password(32, false); $expiresAt = date('Y-m-d H:i:s', strtotime("+{$this->expiryDays} days")); // Update invitation $this->table->update( [ 'invitation_token' => $token, 'status' => 'pending', 'expires_at' => $expiresAt, 'inviters' => json_encode($inviters) ], ['id' => $invitation->id] ); // Build term data for email $terms = []; foreach ($this->roleManager->getInvitableTaxonomies() as $taxonomy) { $column = 'to_' . $taxonomy; if (isset($invitation->$column) && $invitation->$column) { $terms[$taxonomy] = (int) $invitation->$column; } } // Send email do_action( BASE . 'send_invitation_email', $invitation->name, $invitation->email, $token, $userID, $terms, $invitation->invited_role ); return Result::success([ 'message' => 'Invitation resent successfully', 'expires_at' => $expiresAt ]); } protected function processRevoke(Operation $operation, Progress $progress): Result { $invitationID = $operation->requestData['invitation_id'] ?? 0; $userID = $operation->userId; if (!$invitationID) { return Result::fail('Invitation ID required'); } // Get invitation $invitation = $this->table->get(['id' => $invitationID]); if (!$invitation) { return Result::fail('Invitation not found'); } // Can only revoke pending/expired if (!in_array($invitation->status, ['pending', 'expired'])) { return Result::fail('Only pending or expired invitations can be revoked'); } // Check if user is an inviter $inviters = json_decode($invitation->inviters, true) ?: []; $isInviter = false; $updatedInviters = []; foreach ($inviters as $inviter) { if ($inviter['user_id'] == $userID) { $isInviter = true; } else { $updatedInviters[] = $inviter; } } if (!$isInviter) { return Result::fail('You are not authorized to revoke this invitation'); } // If other inviters remain, just update list if (!empty($updatedInviters)) { $this->table->update( ['inviters' => json_encode($updatedInviters)], ['id' => $invitation->id] ); return Result::success([ 'message' => 'You have been removed from the inviters list', 'fully_revoked' => false ]); } // No inviters left, revoke completely $this->table->update( ['status' => 'revoked'], ['id' => $invitation->id] ); // Send revocation email do_action( BASE . 'send_revocation_email', $invitation->email, $invitation->name ); return Result::success([ 'message' => 'Invitation revoked successfully', 'fully_revoked' => true ]); } }