- Created membership_links table to associate secondary users with primary memberships - Created membership_permissions table for granular permission control - Added linkSecondaryUserToMembership() function to create links with validation - Added getUserMembershipLink() to check access via secondary links - Added getLinkedSecondaryUsers() to list all secondary users for a primary member - Added unlinkSecondaryUser() to remove links - Updated getUserMemberStatus() to check both direct and linked memberships - Created link_membership_user processor to handle linking via API - Created unlink_membership_user processor to handle unlinking via API - Added .htaccess routes for linking endpoints - Grants default permissions: access_member_areas, member_pricing, book_campsites, book_courses, book_trips - Includes transaction safety with rollback on errors - Includes comprehensive documentation with usage examples - Validates primary user has active membership before allowing links - Prevents duplicate links and self-linking
9.0 KiB
Membership Linking Feature
Overview
The Membership Linking feature allows users to link secondary accounts (spouses, family members, etc.) to a primary membership account. This enables multiple users to access member-only areas and receive member pricing under a single membership.
Database Schema
membership_links Table
- link_id (INT, PK, AUTO_INCREMENT)
- primary_user_id (INT, FK to users) - Main membership holder
- secondary_user_id (INT, FK to users) - Secondary user sharing the membership
- relationship (VARCHAR 50) - Type of relationship (spouse, family_member, etc)
- linked_at (TIMESTAMP)
- created_at (TIMESTAMP)
Constraints:
- UNIQUE(primary_user_id, secondary_user_id) - Prevent duplicate links
- Foreign keys on both user IDs with CASCADE DELETE
- Indexes on both user IDs for performance
membership_permissions Table
- permission_id (INT, PK, AUTO_INCREMENT)
- link_id (INT, FK to membership_links) - Reference to the link
- permission_name (VARCHAR 100) - Permission type (access_member_areas, member_pricing, etc)
- granted_at (TIMESTAMP)
Constraints:
- UNIQUE(link_id, permission_name) - Prevent duplicate permissions
- Foreign key to membership_links with CASCADE DELETE
- Index on link_id for performance
Default Permissions Granted:
- access_member_areas
- member_pricing
- book_campsites
- book_courses
- book_trips
Functions
linkSecondaryUserToMembership()
Purpose: Link a secondary user to a primary user's active membership
Parameters:
int $primary_user_id- The main membership holderint $secondary_user_id- The user to linkstring $relationship- Relationship type (default: 'spouse')
Returns: array with keys:
success(bool) - Whether the link was createdmessage(string) - Status messagelink_id(int) - ID of created link (on success)
Validation:
- Primary and secondary user IDs must be different
- Primary user must have active membership
- Secondary user must exist
- Link must not already exist
Side Effects:
- Creates membership_links record
- Creates default permission records
- Uses transaction (rolls back on failure)
getUserMembershipLink()
Purpose: Check if a user has access through a secondary membership link
Parameters:
int $user_id- User to check
Returns: array with keys:
has_access(bool) - Whether user has access via linkprimary_user_id(int|null) - ID of primary account holderrelationship(string|null) - Relationship type
Validation:
- Verifies the link exists
- Checks primary user has active membership
- Validates payment status and expiration date
- Confirms indemnity waiver accepted
getLinkedSecondaryUsers()
Purpose: Get all secondary users linked to a primary user's membership
Parameters:
int $primary_user_id- The primary membership holder
Returns: array of linked users with:
link_id- Link IDuser_id- Secondary user IDfirst_name- User's first namelast_name- User's last nameemail- User's emailrelationship- Relationship typelinked_at- When the link was created
unlinkSecondaryUser()
Purpose: Remove a secondary user from a primary user's membership
Parameters:
int $link_id- The membership link ID to removeint $primary_user_id- The primary user (for verification)
Returns: array with keys:
success(bool) - Whether the unlink was successfulmessage(string) - Status message
Validation:
- Verifies link exists
- Confirms primary user owns the link
- Uses transaction (rolls back on failure)
API Endpoints
POST /link_membership_user
Purpose: Link a new secondary user to the requester's membership
Required Parameters:
secondary_email(string) - Email of user to linkrelationship(string, optional) - Relationship type (default: 'spouse')csrf_token(string) - CSRF token
Response:
{
"success": true,
"message": "User successfully linked to membership",
"link_id": 123
}
Error Responses:
- 403: Forbidden (not authenticated or POST required)
- 400: Bad Request (invalid CSRF, missing email, user not found, or linking failed)
Access Control:
- Authenticated users only
- Can only link to own membership
POST /unlink_membership_user
Purpose: Remove a secondary user from the requester's membership
Required Parameters:
link_id(int) - ID of the link to removecsrf_token(string) - CSRF token
Response:
{
"success": true,
"message": "User successfully unlinked from membership"
}
Error Responses:
- 403: Forbidden (not authenticated or POST required)
- 400: Bad Request (invalid CSRF, link not found, or unauthorized)
Access Control:
- Authenticated users only
- Can only remove links from own membership
Integration Points
Updated getUserMemberStatus()
The getUserMemberStatus() function now checks both:
- Direct membership (user has membership_application and membership_fees)
- Secondary membership (user is linked to another user's active membership)
When user doesn't have direct membership, it automatically checks if they're linked to someone else's active membership.
Member Access Checks
All member-only pages should use getUserMemberStatus() which now automatically handles:
- Direct members
- Secondary members via links
- Expired memberships
- Indemnity waiver validation
Use Cases
Spouse/Partner Access
- User A (primary) has active membership
- User B (spouse) links to User A's membership
- User B can now:
- Access member areas
- Receive member pricing
- Book campsites
- Book courses
- Book trips
Renewal
- When primary membership renews, secondary users automatically maintain access
- No need to re-create links on renewal
Membership Termination
- If primary membership expires, secondary users lose access
- Primary user can manually unlink secondary users anytime
Usage Examples
Linking a User
$result = linkSecondaryUserToMembership(
$_SESSION['user_id'],
'spouse@example.com',
'spouse'
);
if ($result['success']) {
$_SESSION['message'] = 'Successfully linked ' . $partner_email . ' to your membership';
} else {
$_SESSION['error'] = $result['message'];
}
Checking User Access
if (getUserMemberStatus($user_id)) {
// User has direct or linked membership
echo "Welcome member!";
} else {
// Redirect to membership page
header('Location: membership');
}
Getting Linked Users
$linkedUsers = getLinkedSecondaryUsers($_SESSION['user_id']);
foreach ($linkedUsers as $user) {
echo "Linked: " . $user['first_name'] . ' (' . $user['relationship'] . ')';
}
Removing a Link
$result = unlinkSecondaryUser($link_id, $_SESSION['user_id']);
if ($result['success']) {
echo "User unlinked successfully";
} else {
echo "Error: " . $result['message'];
}
Security Considerations
Authorization
- Users can only link to their own membership
- Users can only manage their own links
- Secondary users cannot create or modify links (primary user only)
Data Validation
- Email validation before linking
- User existence verification
- Duplicate link prevention
- CSRF token validation on all operations
Relationships
- Foreign keys prevent orphaned links
- CASCADE DELETE ensures cleanup when users are deleted
- Transactions ensure consistency
Testing Checklist
- Link new user to own membership
- Attempt to link non-existent user (error)
- Attempt to link same user twice (error)
- Secondary user can access member areas
- Secondary user receives member pricing
- Unlink secondary user
- Unlinked user cannot access member areas
- Primary user can see list of linked users
- Linked user appears in notifications (if applicable)
- Membership renewal maintains links
- Expired membership removes secondary access
- Deleting user removes their links
- Permission records created on link
- Cannot link without active primary membership
- Cannot link if different user attempts
- CSRF token validation works
Future Enhancements
- Admin Management: Allow admins to create/remove links for members
- Selective Permissions: Allow customizing which permissions each secondary user has
- Invitations: Send email invitations to secondary users to accept
- Multiple Links: Allow primary users to link multiple users (families)
- UI Dashboard: Create page for managing linked accounts
- Notifications: Notify secondary users when linked
- Payment Tracking: Track which user made payments for membership
- Audit Log: Log all link/unlink operations for compliance
Migration Instructions
- Run migration 004 to create tables and permissions table
- Update
src/config/functions.phpwith new linking functions - Update
getUserMemberStatus()to check links - Add routes to
.htaccessfor new endpoints - Deploy processors for link/unlink operations
- Test with married couple accounts
- Document for users in membership information