Skip to content

LearnDash Group Invitation System Implementation Guide #1

Description

@vapvarun

This implementation creates a permanent, group-specific invitation link that:

  • Is the same for all users in a group
  • Never expires
  • Works for any number of users (limited only by available seats)
  • Does not require individual tokens for each user

Files to Modify

You'll need to modify the Freedomology class in your plugin. Based on the code provided, that would be the main plugin file:

freedomology.php

Implementation Steps

1. Add New Functions to the Freedomology Class

Add these new functions to your Freedomology class:

/**
 * Generate a permanent invite link for a LearnDash group
 * 
 * @param int $group_id The LearnDash group ID
 * @return string The permanent invite URL
 */
public function generate_permanent_group_invite_link($group_id) {
    // Create a unique but permanent hash for this group
    $hash = wp_hash($group_id . get_option('site_secret_key', ''));
    $hash = substr($hash, 0, 12); // Shortened for URL friendliness
    
    // Get the first course in the group
    $group_course_ids = learndash_group_enrolled_courses($group_id);
    $course_id = !empty($group_course_ids) ? $group_course_ids[0] : 0;
    
    $invite_url = add_query_arg(
        array(
            'group_id' => $group_id,
            'course_id' => $course_id,
            'invite_key' => $hash,
        ),
        home_url('/sign-up/')
    );
    
    return $invite_url;
}

/**
 * Validate a group invitation key
 * 
 * @param int $group_id The LearnDash group ID
 * @param string $invite_key The invitation key to validate
 * @return bool True if valid, false otherwise
 */
public function validate_group_invite_key($group_id, $invite_key) {
    $expected_key = substr(wp_hash($group_id . get_option('site_secret_key', '')), 0, 12);
    return $invite_key === $expected_key;
}

/**
 * Process a user signup via invite link
 * 
 * @param int $user_id The WordPress user ID
 * @param int $group_id The LearnDash group ID
 * @param string $invite_key The invitation key
 * @return bool True if successful, false otherwise
 */
public function wbcom_process_invite_signup($user_id, $group_id, $invite_key) {
    // Validate the invite key
    if (!$this->validate_group_invite_key($group_id, $invite_key)) {
        return false;
    }
    
    // Check if the group has available seats
    $remaining_seats = ulgm()->group_management->seat->remaining_seats($group_id);
    if ($remaining_seats <= 0) {
        return false;
    }
    
    // Add user to the group directly
    $result = SharedFunctions::set_user_to_group($user_id, $group_id);
    
    // Store record that this user was added via invite link
    if ($result) {
        update_user_meta($user_id, '_joined_via_group_invite', $group_id);
    }
    
    return $result;
}

/**
 * Handle registration from invite links
 * 
 * @param array $form The form data
 * @param array $entry The form entry
 * @param int $user_id The new user ID
 */
public function wbcom_handle_invite_registration($form, $entry, $user_id) {
    // Check if this is an invite registration
    if (isset($_GET['group_id']) && isset($_GET['invite_key'])) {
        $group_id = intval($_GET['group_id']);
        $invite_key = sanitize_text_field($_GET['invite_key']);
        
        // Process the invite signup
        $this->wbcom_process_invite_signup($user_id, $group_id, $invite_key);
    }
}

/**
 * Skip seat validation for invite links
 * 
 * @param bool $validate Whether to validate seat availability
 * @param int $group_id The LearnDash group ID
 * @return bool Whether to validate seat availability
 */
public function skip_seat_validation_for_invite_links($validate, $group_id) {
    if (isset($_GET['invite_key']) && $this->validate_group_invite_key($group_id, $_GET['invite_key'])) {
        return false; // Skip validation
    }
    return $validate;
}

2. Update the wbcom_add_invite_form_fields Function

Replace your existing wbcom_add_invite_form_fields function with this updated version:

/**
 * Add invite form fields to the group management interface
 * 
 * @param int $group_id The LearnDash group ID
 * @param object $object The group management object
 */
public function wbcom_add_invite_form_fields($group_id, $object) {
    // Generate permanent invite link
    $invite_url = $this->generate_permanent_group_invite_link($group_id);
    
    ?>
    <div class="uo-row" id="uo_add_user_invite_url" style="display: none;">
        <label for="wbcom_invite_url">
            <div class="uo-row__title">
                <?php _e('Invite With Link', 'wbcom'); ?>
            </div>
        </label>
        <div class="uo_add_user_invite_url_block">
            <input class="uo-input" type="url" name="wbcom_invite_url" id="wbcom_invite_url" value="<?php echo $invite_url; ?>" readonly />
            <button class="uo-btn" type="button" onclick="copyInviteUrl()">Copy</button>
        </div>
        <span id="copyTooltip" style="visibility: hidden;">URL Copied!</span>
    </div>
    <?php
}

3. Modify the Sign-up Form Validation

Update the wbcom_validate_invitation_code function to support the new invite key method:

/**
 * Validate invitation codes and invite keys
 * 
 * @param array $result Validation result
 * @param string $value The field value
 * @param array $form The form data
 * @param object $field The field object
 * @return array Validation result
 */
public function wbcom_validate_invitation_code($result, $value, $form, $field) {
    global $bp;
    
    // Check if this is an invite link registration
    if (isset($_GET['invite_key']) && isset($_GET['group_id'])) {
        $group_id = intval($_GET['group_id']);
        $invite_key = sanitize_text_field($_GET['invite_key']);
        
        // Validate the invite key
        if ($this->validate_group_invite_key($group_id, $invite_key)) {
            // Check seat availability
            $remaining_seats = ulgm()->group_management->seat->remaining_seats($group_id);
            if ($remaining_seats <= 0) {
                $result['is_valid'] = false;
                $result['message'] = esc_html__('No seats available in this group', 'uncanny-learndash-groups');
            } else {
                $result['is_valid'] = true;
            }
            return $result;
        }
    }
    
    // Standard code validation for non-invite link registrations
    $code = $value;
    if ('' === $code && 'no' === $code) {
        $result['is_valid'] = false;
        $result['message'] = esc_html__('Registration code is empty', 'uncanny-learndash-groups');
    }

    $code_details = SharedFunctions::is_key_available($code);

    if ('failed' === $code_details['result']) {
        if ('invalid' === $code_details['error']) {
            $result['is_valid'] = false;
            $result['message'] = esc_html__('Invalid registration code', 'uncanny-learndash-groups');
        } elseif ('existing' === $code_details['error']) {
            $result['is_valid'] = false;
            $result['message'] = esc_html__('Code already redeemed', 'uncanny-learndash-groups');
        } elseif ('seat_not_available' === $code_details['error']) {
            $result['is_valid'] = false;
            $result['message'] = esc_html__('Seat not available', 'uncanny-learndash-groups');
        }
    }

    return $result;
}

4. Update the User Signup Process

Modify the wbcom_cleanup_user_signup function to handle invite links:

/**
 * Process user registration and add to group
 * 
 * @param int $user_id The new user ID
 * @param array $feed The form feed
 * @param array $entry The form entry
 * @param string $user_pass The user password
 */
public function wbcom_cleanup_user_signup($user_id, $feed, $entry, $user_pass) {
    $form = GFFormsModel::get_form_meta($entry['form_id']);
    $meta = $feed['meta'];

    if (!$user_pass) {
        $user_pass = gf_user_registration()->get_meta_value('password', $meta, $form, $entry);
    }

    // Check if this is an invite link registration
    if (isset($_GET['group_id']) && isset($_GET['invite_key'])) {
        $group_id = intval($_GET['group_id']);
        $invite_key = sanitize_text_field($_GET['invite_key']);
        
        // Process the invite signup
        $this->wbcom_process_invite_signup($user_id, $group_id, $invite_key);
    } else {
        // Standard code registration
        $code = isset($entry[8]) ? $entry[8] : '';
        $group_id = isset($entry[6]) ? $entry[6] : 0;

        if (empty($code)) {
            return;
        }

        // Update user meta with the used code
        update_user_meta($user_id, '_ulgm_code_used', $code);

        // Assign user to group
        $result = ulgm()->group_management->set_user_to_code($user_id, $code, SharedFunctions::$not_started_status, $group_id);

        if ($result) {
            SharedFunctions::set_user_to_group($user_id, $group_id);
        }
    }

    // Get user data and auto-login
    $user = get_userdata($user_id);

    if ($user) {
        $creds = array(
            'user_login'    => $user->user_login,
            'user_password' => $user_pass,
            'remember'      => true,
        );

        add_filter('check_password', '__return_true');

        $login_user = wp_signon($creds, false);

        remove_filter('check_password', '__return_true');
    }
}

5. Add Hook to Initialize the New Filters

Add these hooks in your init_hooks function:

private function init_hooks() {
    // Existing hooks
    add_action('init', [$this, 'initialize_plugin_features']);
    add_action('wp_enqueue_scripts', [$this, 'wbcom_enqueue_assets']);
    // ...
    
    // New hooks for the invite system
    add_filter('ulgm_validate_seat_availability', [$this, 'skip_seat_validation_for_invite_links'], 10, 2);
    add_action('gform_user_registered', [$this, 'wbcom_handle_invite_registration'], 9, 3); // Before wbcom_cleanup_user_signup
    
    // Existing hooks continued
    // ...
}

Testing Your Implementation

Once you've implemented these changes, you should test the system to ensure it works correctly:

  1. Log in as a group leader
  2. Navigate to the group management page
  3. Click on "Add user" and then "Send enrollment key"
  4. You should see the "Invite With Link" field with your permanent group invite URL
  5. Copy this URL and try to access it in an incognito browser
  6. Fill out the registration form and submit
  7. You should be automatically added to the group

Troubleshooting

If you encounter issues with the implementation, check these common problems:

  • URL not working: Verify that your site's permalink structure is working correctly
  • Users not being added to group: Check that the group has available seats
  • Validation issues: Ensure your Gravity Forms hooks are firing in the correct order
  • Debug logging: Add some error logging to trace the execution path
// Example debug logging
error_log('Processing invite signup: Group ID=' . $group_id . ', Key=' . $invite_key);

Security Considerations

This implementation uses WordPress's wp_hash function with the site secret key, which provides good security. However, you should be aware that:

  1. Anyone with the link can join the group (if seats are available)
  2. The link is permanent and doesn't expire
  3. If you want to revoke access, you would need to add a custom revocation system

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions