# Membership Linking Feature - Test & Verification Checklist ## Feature Overview This document outlines the membership linking feature that allows multiple users (e.g., married couples, family members) to share a single membership account. ## Database Schema ### Tables Created 1. **membership_links** - Tracks relationships between primary and secondary users - link_id (auto-increment) - primary_user_id - User who owns/manages the membership - secondary_user_id - User gaining access to membership - status (ACTIVE/INACTIVE) - created_date - expires_date (optional) 2. **membership_permissions** - Granular permission control - permission_id (auto-increment) - link_id - Foreign key to membership_links - permission_name (e.g., access_member_areas, member_pricing, etc.) - granted_date ## Core Functions (in src/config/functions.php) ### New Functions Added 1. **linkSecondaryUserToMembership($primary_user_id, $secondary_user_id, $permissions = [])** - Creates link and assigns default permissions - Validates primary user has active membership - Validates secondary user exists and doesn't already link - Returns success/error response 2. **getUserMembershipLink($user_id)** - Checks if user is linked as secondary to another membership - Returns link details if active - Returns false if no active link 3. **getLinkedSecondaryUsers($primary_user_id)** - Returns array of all secondary users linked to primary - Includes link creation date and status - Used for UI display on membership_details page 4. **unlinkSecondaryUser($primary_user_id, $secondary_user_id)** - Removes link and associated permissions - Returns success/error response ### Modified Functions 1. **getUserMemberStatus($user_id)** - NOW checks linked memberships at ALL failure points: * If user has no application → check if linked to active membership * If user hasn't accepted indemnity → check if linked * If user has no payment record → check if linked * If user's direct membership expired → check if linked - Returns true for linked members even if direct membership check fails ## API Endpoints ### POST /src/processors/link_membership_user.php - **Purpose**: AJAX endpoint for creating membership links - **Parameters**: - csrf_token (validated) - secondary_user_email (validated) - **Returns**: JSON response with success/error - **Security**: CSRF token validation, database injection prevention ### POST /src/processors/unlink_membership_user.php - **Purpose**: AJAX endpoint for removing membership links - **Parameters**: - csrf_token (validated) - secondary_user_id (validated) - **Returns**: JSON response with success/error - **Security**: CSRF token validation, only primary user can unlink ## UI Implementation ### Membership Details Page (src/pages/membership_details.php) - Added "Linked Accounts" section OUTSIDE main info form - Displays list of currently linked secondary users - Form to add new linked user by email - Unlink buttons for each linked user - IMPORTANT FIX: Form moved outside infoForm to prevent form submission conflicts ### Header Navigation (src/pages/header.php) - "Members Area" dropdown shown for users with direct OR linked membership - Uses getUserMemberStatus() to determine visibility - Shows: Campsites & Gallery links ## Booking Pages & Pricing ### Protected Member Pages - `src/pages/bookings/campsites.php` - Redirects non-members - `src/pages/gallery/gallery.php` - Redirects non-members - `src/pages/gallery/view_album.php` - Redirects non-members - `src/pages/gallery/create_album.php` - Redirects non-members ### Open Booking Pages (All Users Welcome) 1. **Trip Details** (`src/pages/bookings/trip-details.php`) - Shows member & non-member rates - Linked members get member pricing - Correct calculateTotal() logic with adults/children/pensioners 2. **Driver Training** (`src/pages/bookings/driver_training.php`) - Pricing: Members vs Non-members - Form fields adjusted for non-members - FIXED: calculateTotal() now correctly: * Members: (self + additional_members at member rate) + additional_nonmembers * Non-members: (self + additional participants at non-member rate) 3. **Bush Mechanics** (`src/pages/other/bush_mechanics.php`) - FIXED: calculateTotal() pricing logic corrected - Members: (self at member rate) + additional members + additional non-members - Non-members: (self + additional participants at non-member rate) 4. **Rescue & Recovery** (`src/pages/other/rescue_recovery.php`) - FIXED: calculateTotal() pricing logic corrected - Members: (self at member rate) + additional members + additional non-members - Non-members: (self + additional participants at non-member rate) 5. **Course Details** (`src/pages/bookings/course_details.php`) - Shows member & non-member rates - Open to all users (members and non-members) 6. **Campsite Booking** (`src/pages/bookings/campsite_booking.php`) - Pricing: Members stay FREE, Non-members R200/night - Calculates based on getUserMemberStatus() ### Booking Processors 1. **process_trip_booking.php** - ✅ Allows non-members, applies pricing correctly 2. **process_course_booking.php** - ✅ Allows non-members, applies pricing correctly 3. **process_camp_booking.php** - ✅ Allows non-members, applies pricing correctly ## Testing Checklist ### Unit Tests - [ ] Link secondary user to primary user membership - [ ] Verify linked user appears in getLinkedSecondaryUsers() - [ ] Verify linked user gets member pricing on bookings - [ ] Verify linked user can access member-only areas - [ ] Unlink secondary user from primary membership - [ ] Verify unlinked user loses member benefits - [ ] Test with invalid secondary user email - [ ] Test with secondary user who already has direct membership ### Integration Tests - [ ] Member books trip - should use member pricing - [ ] Member books course - should use member pricing - [ ] Member books campsite - should stay FREE - [ ] Linked member books trip - should use member pricing - [ ] Linked member books course - should use member pricing - [ ] Linked member books campsite - should stay FREE - [ ] Non-member books trip - should use non-member pricing - [ ] Non-member books course - should use non-member pricing - [ ] Non-member books campsite - should pay R200/night - [ ] Linked member can view members gallery - [ ] Non-member cannot access members gallery - [ ] Linked member dropdown link shows in header - [ ] Payment processing for non-member bookings ### UI/UX Tests - [ ] Linking form displays properly on membership details - [ ] Unlink buttons work correctly - [ ] "You will be added at non-member rate" message shows for non-members - [ ] Pricing calculations update correctly as form fields change - [ ] Member/Non-member rate display is clear ## Known Issues & Fixes Applied ### Issue 1: Form Submission Conflicts - **Problem**: linkUserForm nested inside infoForm - submit triggered parent - **Fix**: Moved linkUserForm outside infoForm closes - **Commit**: c5112e1c ### Issue 2: Linked Members Not Recognized - **Problem**: getUserMemberStatus() only checked linked if no application existed - **Fix**: Added linked checks at all failure points in function - **Commit**: e63bd806 ### Issue 3: JavaScript Pricing Calculations Wrong - **Problem**: calculateTotal() in driver_training, bush_mechanics, rescue_recovery incorrectly calculated non-member totals - **Fix**: Corrected variable names and logic to properly handle: - Members: count themselves + additional members/non-members - Non-members: count themselves only + additional participants - **Commits**: - driver_training: inline with member label UI improvement - bush_mechanics & rescue_recovery: 646a3ecb ## Performance Considerations ### Database Queries - getUserMembershipLink() - Single query with index on secondary_user_id - getLinkedSecondaryUsers() - Single join query with index on primary_user_id - getUserMemberStatus() - Multiple queries but cached in session after first call ### Recommended Indexes - membership_links(secondary_user_id) - membership_links(primary_user_id) - membership_links(status) ## Security Considerations ### Access Control - Only primary user can link/unlink accounts - Secondary user cannot manage their own link (primary must unlink) - CSRF tokens validated on all membership operations ### Input Validation - User emails validated before linking - User IDs validated as integers - Links can only be created between valid users ### Audit Trail - All linking operations logged via auditLog() - Timestamps recorded for all changes ## Future Enhancements 1. **Secondary user control** - Allow secondary users to decline/accept links - Option for secondary user to self-unlink 2. **Permissions system** - Granular control over which permissions secondary users receive - Ability to revoke specific permissions without unlinking 3. **Multiple primary accounts** - Allow one user to be secondary to multiple primaries - Flexible family/group structure support 4. **Member linking UI** - Search for existing members to link - Batch link multiple users - Link management dashboard 5. **Expiration dates** - Time-limited links (e.g., seasonal guests) - Auto-renewal options - Expiration notifications ## Rollback Plan If issues arise, revert to previous commit: ```bash git revert ``` Key commits to know: - 646a3ecb - Latest fixes (pricing calculations) - e63bd806 - Improved getUserMemberStatus - c5112e1c - Fixed form nesting issue - bd20fc0f - Initial feature implementation