Developer Guide For BuddyPress Private Community

Architecture Overview

Plugin Structure

bp-lock/
├── admin/                    # Admin functionality
│   ├── class-bp-lock-admin.php
│   ├── css/                 # Admin styles
│   ├── js/                  # Admin scripts
│   └── templates/           # Admin templates
├── public/                   # Frontend functionality
│   ├── class-bp-lock-public.php
│   ├── css/                 # Public styles
│   ├── js/                  # Public scripts
│   └── templates/           # Public templates
├── includes/                 # Core functionality
│   ├── class-bp-lock.php
│   ├── class-bp-lock-activator.php
│   └── class-bp-lock-deactivator.php
├── docs/                     # Documentation
└── bp-lock.php              # Main plugin file
Design Patterns
  • MVC Pattern: Separation of logic, templates, and data
  • Singleton Pattern: Main plugin class instance
  • Hook-Driven: WordPress action/filter system
  • OOP Structure: Class-based architecture

Core Classes

Bp_Lock

Main plugin class that coordinates between admin and public functionality.

class Bp_Lock {
    protected $loader;
    protected $plugin_name;
    protected $version;
    public $locked_content_template;
    
    public function __construct() {
        $this->version = BPLOCK_PLUGIN_VERSION;
        $this->plugin_name = 'bp-lock';
        $this->locked_content_template = BPLOCK_PLUGIN_PATH . 'public/templates/bplock-locked-content-template.php';
    }
}

Bp_Lock_Admin

Handles all admin-side functionality.

class Bp_Lock_Admin {
    private $plugin_name;
    private $version;
    public $plugin_settings_tabs;
    
    public function bplock_general_settings() {
        // Registers settings tabs
    }
    
    public function bplock_register_settings_function() {
        register_setting('bplock_general_settings', 'bplock_general_settings');
    }
}

Bp_Lock_Public

Manages frontend protection logic.

class Bp_Lock_Public {
    public function bplock_lock_pages($template) {
        // Page protection logic
    }
    
    public function bplock_lock_complete_site($template) {
        // Full site protection
    }
    
    private function bplock_apply_protection($template, $general_settings) {
        // Apply protection method
    }
}

Hooks & Filters

Action Hooks

bplock_before_apply_protection

Fires before applying protection to a page.

do_action('bplock_before_apply_protection', $lr_form, $general_settings);

// Usage example:
add_action('bplock_before_apply_protection', function($method, $settings) {
    // Log protection events
    error_log("Protecting page with method: " . $method);
}, 10, 2);
bplock_protecting_page

Fires when protecting a specific page.

do_action('bplock_protecting_page', $current_url, $general_settings);

// Usage example:
add_action('bplock_protecting_page', function($url, $settings) {
    // Track protected page visits
    do_action('my_analytics_track', 'protected_page_visit', $url);
}, 10, 2);

Filter Hooks

bplock_protection_method

Modify the protection method dynamically.

apply_filters('bplock_protection_method', $lr_form, $general_settings);

// Usage example:
add_filter('bplock_protection_method', function($method, $settings) {
    // Force redirect for specific conditions
    if (is_mobile()) {
        return 'page_redirect';
    }
    return $method;
}, 10, 2);

bplock_is_page_protected

Override protection status for specific pages.

apply_filters('bplock_is_page_protected', $protected, $current_url, $general_settings);

// Usage example:
add_filter('bplock_is_page_protected', function($protected, $url, $settings) {
    // Always protect admin-ajax requests
    if (strpos($url, 'admin-ajax.php') !== false) {
        return false; // Don't protect AJAX
    }
    return $protected;
}, 10, 3);
bplock_locked_urls

Modify the list of protected URLs.

apply_filters('bplock_locked_urls', $locked_urls, $current_url);

// Usage example:
add_filter('bplock_locked_urls', function($urls, $current) {
    // Add dynamic URLs
    $urls[] = '/user/' . get_current_user_id() . '/private';
    return $urls;
}, 10, 2);

bplock_whitelist_urls

Modify whitelist URLs.

apply_filters('bplock_whitelist_urls', $urls, $current_url);

// Usage example:
add_filter('bplock_whitelist_urls', function($urls, $current) {
    // Add WooCommerce checkout to whitelist
    if (class_exists('WooCommerce')) {
        $urls[] = '/checkout';
        $urls[] = '/cart';
    }
    return $urls;
}, 10, 2);
bplock_redirect_url

Change the redirect destination.

apply_filters('bplock_redirect_url', $redirepage_url, $redirepage);

// Usage example:
add_filter('bplock_redirect_url', function($url, $page_id) {
    // Redirect to custom login with return URL
    return add_query_arg('redirect_to', $_SERVER['REQUEST_URI'], $url);
}, 10, 2);
bplock_protected_template

Override the template used for protected content.

apply_filters('bplock_protected_template', $template, $lr_form, $general_settings);

// Usage example:
add_filter('bplock_protected_template', function($template, $method, $settings) {
    // Use custom template for specific pages
    if (is_page('premium')) {
        return get_template_directory() . '/custom-locked.php';
    }
    return $template;
}, 10, 3);
bplock_page_template

Final template filter for any page.

apply_filters('bplock_page_template', $template, $protected, $general_settings);

// Usage example:
add_filter('bplock_page_template', function($template, $protected, $settings) {
    // Log all template decisions
    do_action('log_template_decision', $template, $protected);
    return $template;
}, 10, 3);

Database Structure

Options Table

bplock_general_settings

Main settings array stored as serialized option.

$settings = array(
    // Partial Protection
    'bp-components' => array('members', 'groups', 'activity'),
    'locked-urls' => "/members\n/private/*\n/premium",
    
    // Full Protection
    'bplock-complete-site' => 'on',
    'bplock-whitelist-urls' => "/\nwp-login.php\n/api/*",
    
    // Protection Rules
    'lr-form' => 'plugin_form', // or 'custom_form', 'page_redirect'
    'locked_content' => '<p>This content is protected.</p>',
    'custom_form_content' => '[custom_login_form]',
    'logout_redirect_page' => 123, // Page ID
);

Legacy Options (Backward Compatibility)

// Old options still checked for compatibility
get_option('bp-components');      // Array of component slugs
get_option('bplock-pages');       // Array of page IDs
get_option('bplock-complete-lock'); // 'on' or false

API Reference

Public Functions

Check Protection Status

/**
 * Check if current page should be protected
 * 
 * @return bool
 */
function bplock_is_current_page_protected() {
    global $bplock;
    $public = new Bp_Lock_Public($bplock->plugin_name, $bplock->version);
    
    // Check protection logic
    $settings = get_option('bplock_general_settings', array());
    
    if (is_user_logged_in()) {
        return false;
    }
    
    // Full protection check
    if (isset($settings['bplock-complete-site']) && $settings['bplock-complete-site'] === 'on') {
        return !$public->bplock_is_whitelisted_page();
    }
    
    // Partial protection check
    // ... implement URL checking logic
    
    return false;
}

Get Protected URLs

/**
 * Get all protected URLs
 * 
 * @return array
 */
function bplock_get_protected_urls() {
    $settings = get_option('bplock_general_settings', array());
    $urls = array();
    
    if (isset($settings['locked-urls'])) {
        $urls = explode("\n", $settings['locked-urls']);
        $urls = array_map('trim', $urls);
        $urls = array_filter($urls, function($url) {
            return !empty($url) && strpos($url, '#') !== 0;
        });
    }
    
    return apply_filters('bplock_protected_urls_list', $urls);
}

Check URL Pattern Match

/**
 * Check if URL matches pattern
 * 
 * @param string $url URL to check
 * @param string $pattern Pattern with wildcards
 * @return bool
 */
function bplock_url_matches_pattern($url, $pattern) {
    // Handle wildcards
    if (strpos($pattern, '*') !== false) {
        $regex_pattern = str_replace('*', '.*', preg_quote($pattern, '/'));
        return preg_match('/^' . $regex_pattern . '$/i', $url);
    }
    
    // Exact match (ignore trailing slashes)
    return trim($url, '/') === trim($pattern, '/');
}

Extending the Plugin

Custom Protection Method

// Add custom protection method
add_filter('bplock_protection_method', function($method, $settings) {
    // Check for custom condition
    if (isset($_GET['special_access'])) {
        return 'my_custom_method';
    }
    return $method;
}, 10, 2);

// Handle custom method
add_filter('bplock_protected_template', function($template, $method, $settings) {
    if ($method === 'my_custom_method') {
        // Return custom template
        return plugin_dir_path(__FILE__) . 'templates/special-access.php';
    }
    return $template;
}, 10, 3);

Role-Based Protection

// Add role-based protection
add_filter('bplock_is_page_protected', function($protected, $url, $settings) {
    // Allow specific roles even when logged in
    if (is_user_logged_in()) {
        $user = wp_get_current_user();
        
        // Protect from subscribers
        if (in_array('subscriber', $user->roles) && strpos($url, '/premium') !== false) {
            return true;
        }
    }
    
    return $protected;
}, 10, 3);

Custom Whitelist Rules

// Dynamic whitelist based on conditions
add_filter('bplock_whitelist_urls', function($urls) {
    // Add time-based whitelist
    $current_hour = date('H');
    if ($current_hour >= 9 && $current_hour <= 17) {
        $urls[] = '/business-hours-content';
    }
    
    // Add user-specific whitelist
    if (isset($_COOKIE['special_token'])) {
        $urls[] = '/token-protected-content';
    }
    
    return $urls;
});

Integration with Membership Plugins

// Integrate with membership plugin
add_filter('bplock_is_page_protected', function($protected, $url, $settings) {
    // Check membership level
    if (function_exists('pmpro_hasMembershipLevel')) {
        if (pmpro_hasMembershipLevel()) {
            return false; // Has membership, allow access
        }
    }
    
    return $protected;
}, 10, 3);

Code Examples

Example 1: Protect WooCommerce Products

// Protect all WooCommerce products for non-customers
add_filter('bplock_is_page_protected', function($protected, $url) {
    if (is_product()) {
        // Check if user has purchased before
        if (is_user_logged_in()) {
            $customer = new WC_Customer(get_current_user_id());
            if ($customer->get_order_count() == 0) {
                return true; // No orders, protect
            }
        } else {
            return true; // Not logged in, protect
        }
    }
    return $protected;
}, 10, 2);

Example 2: Custom Login Form

// Create custom login form template
add_filter('bplock_protected_template', function($template, $method) {
    if ($method === 'custom_form') {
        // Check if on specific page type
        if (is_singular('course')) {
            return plugin_dir_path(__FILE__) . 'templates/course-login.php';
        }
    }
    return $template;
}, 10, 2);

Example 3: Logging Protection Events

// Log all protection events
add_action('bplock_protecting_page', function($url, $settings) {
    $log_entry = array(
        'timestamp' => current_time('mysql'),
        'url' => $url,
        'ip' => $_SERVER['REMOTE_ADDR'],
        'user_agent' => $_SERVER['HTTP_USER_AGENT'],
        'method' => $settings['lr-form'] ?? 'unknown'
    );
    
    // Save to custom table or file
    error_log(json_encode($log_entry), 3, WP_CONTENT_DIR . '/protection.log');
}, 10, 2);

Example 4: API Endpoint Protection

// Protect REST API endpoints
add_filter('rest_authentication_errors', function($result) {
    // Check if Private Community protection should apply
    $settings = get_option('bplock_general_settings', array());
    
    if (isset($settings['bplock-complete-site']) && $settings['bplock-complete-site'] === 'on') {
        if (!is_user_logged_in()) {
            return new WP_Error(
                'rest_forbidden',
                __('REST API requires authentication.', 'bp-lock'),
                array('status' => 401)
            );
        }
    }
    
    return $result;
});

Development Workflow

Setting Up Development Environment

# Clone repository
git clone https://github.com/wbcomdesigns/bp-lock.git

# Install dependencies
composer install
npm install

# Set up local WordPress
wp core download
wp config create --dbname=bplock_dev --dbuser=root
wp core install --url=bplock.test --title="BP Lock Dev"

Code Standards

PHP Standards

  • Follow WordPress Coding Standards
  • Use PHP 7.4+ features
  • Implement proper escaping and sanitization
// Good
$url = esc_url($_POST['url']);
$text = sanitize_text_field($_POST['text']);
echo esc_html($output);

// Bad
$url = $_POST['url'];
echo $output;

JavaScript Standards

  • Use ES6+ syntax
  • Follow WordPress JavaScript standards
  • Implement proper error handling
// Good
const settings = {
    protected: true,
    method: 'plugin_form'
};

// Use promises or async/await
async function checkProtection() {
    try {
        const response = await fetch('/wp-json/bplock/v1/check');
        return response.json();
    } catch (error) {
        console.error('Protection check failed:', error);
    }
}

Testing

Unit Testing

class BPLockTest extends WP_UnitTestCase {
    public function test_url_pattern_matching() {
        $this->assertTrue(
            bplock_url_matches_pattern('/blog/post-1', '/blog/*')
        );
        
        $this->assertFalse(
            bplock_url_matches_pattern('/page', '/blog/*')
        );
    }
}

Integration Testing

public function test_protection_with_full_lock() {
    // Set up full protection
    update_option('bplock_general_settings', array(
        'bplock-complete-site' => 'on',
        'bplock-whitelist-urls' => '/'
    ));
    
    // Test as guest
    wp_set_current_user(0);
    $this->assertTrue(bplock_is_current_page_protected());
    
    // Test as logged in
    wp_set_current_user(1);
    $this->assertFalse(bplock_is_current_page_protected());
}

Debugging

Enable Debug Mode

// wp-config.php
define('WP_DEBUG', true);
define('WP_DEBUG_LOG', true);
define('WP_DEBUG_DISPLAY', false);
define('SCRIPT_DEBUG', true);

Debug Functions

// Debug protection decisions
add_action('bplock_protecting_page', function($url) {
    if (defined('WP_DEBUG') && WP_DEBUG) {
        error_log('BP Lock: Protecting URL: ' . $url);
        error_log('BP Lock: Stack trace: ' . print_r(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS), true));
    }
});

Performance Optimization

Caching Protection Checks

// Cache protection status
function bplock_is_protected_cached($url) {
    $cache_key = 'bplock_protected_' . md5($url);
    $cached = wp_cache_get($cache_key, 'bplock');
    
    if ($cached !== false) {
        return $cached;
    }
    
    $protected = bplock_is_page_protected($url);
    wp_cache_set($cache_key, $protected, 'bplock', 300); // 5 minutes
    
    return $protected;
}

Database Query Optimization

// Optimize settings retrieval
function bplock_get_settings() {
    static $settings = null;
    
    if ($settings === null) {
        $settings = get_option('bplock_general_settings', array());
    }
    
    return $settings;
}

Security Best Practices

Input Validation

// Always validate and sanitize input
$url = isset($_POST['url']) ? esc_url_raw($_POST['url']) : '';
$text = isset($_POST['text']) ? sanitize_text_field($_POST['text']) : '';
$html = isset($_POST['html']) ? wp_kses_post($_POST['html']) : '';

Nonce Verification

// Always verify nonces
if (!wp_verify_nonce($_POST['_wpnonce'], 'bplock_action')) {
    wp_die(__('Security check failed', 'bp-lock'));
}

Capability Checks

// Check user capabilities
if (!current_user_can('manage_options')) {
    wp_die(__('Insufficient permissions', 'bp-lock'));
}

SQL Injection Prevention

// Use prepared statements
global $wpdb;
$results = $wpdb->get_results(
    $wpdb->prepare(
        "SELECT * FROM {$wpdb->posts} WHERE post_type = %s AND post_status = %s",
        'page',
        'publish'
    )
);

Troubleshooting

Common Issues

Protection Not Working

  1. Check if settings are saved correctly
  2. Verify hooks are properly registered
  3. Clear all caches
  4. Check for conflicting plugins

Redirect Loops

  1. Ensure login page is whitelisted
  2. Check redirect URL is not protected
  3. Verify cookies are working

Performance Issues

  1. Implement caching for protection checks
  2. Optimize regex patterns
  3. Reduce number of protected URLs
  4. Use static variables for repeated checks

Contributing

Pull Request Process

  1. Fork the repository
  2. Create feature branch
  3. Commit changes with clear messages
  4. Write/update tests
  5. Update documentation
  6. Submit pull request

Code Review Checklist

  • Follows WordPress coding standards
  • Includes proper documentation
  • Has unit tests
  • Security measures implemented
  • Backward compatible
  • Performance optimized
Update on September 3, 2025