# 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 ```sql - 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 ```sql - 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 holder - `int $secondary_user_id` - The user to link - `string $relationship` - Relationship type (default: 'spouse') **Returns**: `array` with keys: - `success` (bool) - Whether the link was created - `message` (string) - Status message - `link_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 link - `primary_user_id` (int|null) - ID of primary account holder - `relationship` (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 ID - `user_id` - Secondary user ID - `first_name` - User's first name - `last_name` - User's last name - `email` - User's email - `relationship` - Relationship type - `linked_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 remove - `int $primary_user_id` - The primary user (for verification) **Returns**: `array` with keys: - `success` (bool) - Whether the unlink was successful - `message` (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 link - `relationship` (string, optional) - Relationship type (default: 'spouse') - `csrf_token` (string) - CSRF token **Response**: ```json { "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 remove - `csrf_token` (string) - CSRF token **Response**: ```json { "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: 1. Direct membership (user has membership_application and membership_fees) 2. 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 1. User A (primary) has active membership 2. User B (spouse) links to User A's membership 3. 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 ```php $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 ```php if (getUserMemberStatus($user_id)) { // User has direct or linked membership echo "Welcome member!"; } else { // Redirect to membership page header('Location: membership'); } ``` ### Getting Linked Users ```php $linkedUsers = getLinkedSecondaryUsers($_SESSION['user_id']); foreach ($linkedUsers as $user) { echo "Linked: " . $user['first_name'] . ' (' . $user['relationship'] . ')'; } ``` ### Removing a Link ```php $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 1. **Admin Management**: Allow admins to create/remove links for members 2. **Selective Permissions**: Allow customizing which permissions each secondary user has 3. **Invitations**: Send email invitations to secondary users to accept 4. **Multiple Links**: Allow primary users to link multiple users (families) 5. **UI Dashboard**: Create page for managing linked accounts 6. **Notifications**: Notify secondary users when linked 7. **Payment Tracking**: Track which user made payments for membership 8. **Audit Log**: Log all link/unlink operations for compliance ## Migration Instructions 1. Run migration 004 to create tables and permissions table 2. Update `src/config/functions.php` with new linking functions 3. Update `getUserMemberStatus()` to check links 4. Add routes to `.htaccess` for new endpoints 5. Deploy processors for link/unlink operations 6. Test with married couple accounts 7. Document for users in membership information