9.5 KiB
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
-
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)
-
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
-
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
-
getUserMembershipLink($user_id)
- Checks if user is linked as secondary to another membership
- Returns link details if active
- Returns false if no active link
-
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
-
unlinkSecondaryUser($primary_user_id, $secondary_user_id)
- Removes link and associated permissions
- Returns success/error response
Modified Functions
- 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
- NOW checks linked memberships at ALL failure points:
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-memberssrc/pages/gallery/gallery.php- Redirects non-memberssrc/pages/gallery/view_album.php- Redirects non-memberssrc/pages/gallery/create_album.php- Redirects non-members
Open Booking Pages (All Users Welcome)
-
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
-
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)
-
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)
-
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)
-
Course Details (
src/pages/bookings/course_details.php)- Shows member & non-member rates
- Open to all users (members and non-members)
-
Campsite Booking (
src/pages/bookings/campsite_booking.php)- Pricing: Members stay FREE, Non-members R200/night
- Calculates based on getUserMemberStatus()
Booking Processors
- process_trip_booking.php - ✅ Allows non-members, applies pricing correctly
- process_course_booking.php - ✅ Allows non-members, applies pricing correctly
- 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
-
Secondary user control
- Allow secondary users to decline/accept links
- Option for secondary user to self-unlink
-
Permissions system
- Granular control over which permissions secondary users receive
- Ability to revoke specific permissions without unlinking
-
Multiple primary accounts
- Allow one user to be secondary to multiple primaries
- Flexible family/group structure support
-
Member linking UI
- Search for existing members to link
- Batch link multiple users
- Link management dashboard
-
Expiration dates
- Time-limited links (e.g., seasonal guests)
- Auto-renewal options
- Expiration notifications
Rollback Plan
If issues arise, revert to previous commit:
git revert <commit-hash>
Key commits to know: