MainWP 开发者指南

MainWP 开发者指南,介绍多站管理扩展开发。

Prerequisites {#prerequisites}

Before you begin developing for MainWP, ensure you have:

Environment Setup {#environment-setup}

  1. Set up a local WordPress development environment:
# Using Local by Flywheel or similar tool
local-by-flywheel create-site mainwp-dev
  1. Install required development tools:
# Install Composer globally
curl -sS https://getcomposer.org/installer | php
mv composer.phar /usr/local/bin/composer

# Install Node.js and npm (optional, for build tools)
brew install node

MainWP Installation {#mainwp-installation}

  1. Install the MainWP Dashboard plugin:

    • Download from MainWP.com
    • Upload and activate in WordPress
    • Complete the setup wizard
  2. Install MainWP Child plugin on test sites:

    • Download the Child plugin
    • Install on your test WordPress sites
    • Connect sites to your dashboard

Creating Your First Extension {#create-extension}

Start with this basic extension structure:

<?php
/*
Plugin Name: My MainWP Extension
Plugin URI: https://yourwebsite.com/mainwp-extension
Description: Description of your extension
Version: 1.0
Author: Your Name
Author URI: https://yourwebsite.com
Documentation URI: https://yourwebsite.com/docs
*/

class My_MainWP_Extension {
    public function __construct() {
        add_action('init', array($this, 'init'));
    }

    public function init() {
        // Your initialization code here
    }
}

new My_MainWP_Extension();

Extension Structure {#basic-structure}

A typical MainWP extension includes:

Example directory structure:

my-mainwp-extension/
├── includes/
│   ├── class-admin.php
│   ├── class-api.php
│   └── class-helpers.php
├── assets/
│   ├── css/
│   └── js/
└── my-mainwp-extension.php

Testing Your Extension {#testing}

  1. Local Testing

    • Activate your extension on your development site
    • Test all features thoroughly
    • Check for conflicts with other extensions
  2. Debug Mode

    define('MAINWP_DEBUG', true);
    
  3. Common Test Cases

    • Extension activation/deactivation
    • Settings saving/loading
    • API interactions
    • User interface elements
    • Error handling
  4. Testing with Multiple Child Sites

    • Test with various WordPress versions
    • Test with different server configurations
    • Verify performance with many connected sites

Remember to always follow MainWP's coding standards and best practices when developing your extension. For more detailed information, check out our Extension Development Guide.

MainWP Developer Guides

Welcome to the MainWP Developer Guides! This documentation is designed to help you create add-ons for the MainWP platform, whether you're building a standalone extension or integrating with a third-party service.

Understanding MainWP Add-on Types

MainWP uses specific terminology to distinguish between different types of add-ons:

Which Type Should You Build?

Use this decision tree to determine whether you should build an Extension or an Integration:

flowchart TD A[Do you need to connect with a third-party service or plugin?] -->|Yes| B[Integration] A -->|No| C[Extension] B --> D[Examples: WooCommerce Integration, Google Analytics Integration] C --> E[Examples: Advanced Uptime Monitor, Advanced Reports Extension]

Who These Guides Are For

These guides are intended for developers who want to:

Whether you're new to MainWP development or an experienced developer looking for specific information, you'll find resources tailored to your needs. We've optimized these guides for part-time developers who want to create MainWP add-ons efficiently.

Guide Categories

Getting Started

Essential information for beginning your MainWP development journey:

Core Development Topics

Detailed guides for key development areas:

Best Practices

Recommendations for creating high-quality MainWP add-ons:

Reference

Technical reference material:

Additional Resources

How to Use These Guides

Each guide is structured to provide information at different levels of detail:

We've designed these guides with part-time developers in mind. If you're creating a MainWP add-on as a side project, you'll find:

Use the navigation to find the specific information you need, or follow the guides sequentially for a comprehensive learning path.

API Integration Best Practices for MainWP Add-ons

This guide covers best practices for integrating third-party APIs with MainWP add-ons. Following these practices will help you create reliable, secure, and maintainable integrations.

Real-World MainWP Example: Google Analytics Integration

Here's a concrete example of integrating Google Analytics with MainWP to track website performance across all managed sites:

class MainWP_Google_Analytics_Integration {
    private $api_client;
    private $view_id;
    
    public function __construct() {
        // Initialize Google Analytics API client
        $this->api_client = new Google_Analytics_API_Client(
            get_option('mainwp_ga_client_id'),
            get_option('mainwp_ga_client_secret')
        );
        
        // Set default view ID
        $this->view_id = get_option('mainwp_ga_view_id');
        
        // Hook into MainWP actions
        add_action('mainwp_site_synced', [$this, 'sync_analytics_data'], 10, 2);
        add_filter('mainwp_sync_others_data', [$this, 'add_analytics_data_to_sync'], 10, 2);
        
        // Add dashboard widget
        add_action('mainwp_dashboard_widgets', [$this, 'add_analytics_widget']);
    }
    
    /**
     * Sync analytics data when a site is synced
     */
    public function sync_analytics_data($website, $data) {
        // Skip if API client isn't configured
        if (!$this->api_client->is_configured()) {
            return;
        }
        
        try {
            // Get analytics data for this site
            $analytics_data = $this->api_client->get_site_analytics(
                $website->url,
                date('Y-m-d', strtotime('-30 days')),
                date('Y-m-d'),
                $this->view_id
            );
            
            // Store analytics data for this site
            update_option('mainwp_ga_data_' . $website->id, $analytics_data);
            update_option('mainwp_ga_last_sync_' . $website->id, time());
            
        } catch (Exception $e) {
            // Log error but continue
            error_log('Google Analytics API Error: ' . $e->getMessage());
        }
    }
    
    /**
     * Add analytics data to sync data
     */
    public function add_analytics_data_to_sync($sync_data, $website) {
        $sync_data['google_analytics'] = [
            'last_sync' => get_option('mainwp_ga_last_sync_' . $website->id, 0),
            'has_data' => !empty(get_option('mainwp_ga_data_' . $website->id, []))
        ];
        
        return $sync_data;
    }
    
    /**
     * Add analytics widget to dashboard
     */
    public function add_analytics_widget() {
        wp_add_dashboard_widget(
            'mainwp-google-analytics-widget',
            'Google Analytics Overview',
            [$this, 'render_analytics_widget']
        );
    }
    
    /**
     * Render analytics widget
     */
    public function render_analytics_widget() {
        // Get all websites
        $websites = MainWP\Dashboard\MainWP_DB::instance()->get_websites();
        
        // Collect analytics data
        $total_sessions = 0;
        $total_users = 0;
        $total_pageviews = 0;
        
        foreach ($websites as $website) {
            $analytics_data = get_option('mainwp_ga_data_' . $website->id, []);
            
            if (!empty($analytics_data)) {
                $total_sessions += $analytics_data['sessions'] ?? 0;
                $total_users += $analytics_data['users'] ?? 0;
                $total_pageviews += $analytics_data['pageviews'] ?? 0;
            }
        }
        
        // Display analytics data
        ?>
        <div class="ui grid">
            <div class="four wide column">
                <div class="ui statistic">
                    <div class="value"><?php echo number_format($total_sessions); ?></div>
                    <div class="label">Sessions</div>
                </div>
            </div>
            <div class="four wide column">
                <div class="ui statistic">
                    <div class="value"><?php echo number_format($total_users); ?></div>
                    <div class="label">Users</div>
                </div>
            </div>
            <div class="four wide column">
                <div class="ui statistic">
                    <div class="value"><?php echo number_format($total_pageviews); ?></div>
                    <div class="label">Pageviews</div>
                </div>
            </div>
        </div>
        <?php
    }
}

This example demonstrates:

  1. Hooking into MainWP events - Using mainwp_site_synced to trigger data collection
  2. Extending sync data - Adding custom data to the sync process
  3. Dashboard integration - Displaying aggregated analytics data in the MainWP dashboard
  4. Error handling - Gracefully handling API errors without disrupting MainWP
  5. Data storage - Storing API data per website using WordPress options

Common Pitfalls When Integrating APIs with MainWP

Avoid these common mistakes when building your MainWP API integration:

1. Blocking the Main Thread with Synchronous API Calls

Problem: Making synchronous API calls during critical MainWP operations can cause timeouts and a poor user experience.

Solution: Use asynchronous processing for non-critical operations:

// Instead of this:
function on_site_sync($website_id) {
    $this->api_client->sync_data($website_id); // Blocks until complete
}

// Do this:
function on_site_sync($website_id) {
    wp_schedule_single_event(
        time(),
        'my_integration_async_sync',
        [$website_id]
    );
}

// Then handle it asynchronously:
add_action('my_integration_async_sync', [$this, 'process_async_sync']);

2. Not Respecting API Rate Limits

Problem: MainWP manages multiple sites, and your integration might trigger many API calls at once, quickly hitting rate limits.

Solution: Implement rate limiting and queuing:

// Track API calls with timestamps
private function track_api_call() {
    $calls = get_option('my_integration_api_calls', []);
    $calls[] = time();
    
    // Keep only the last 100 calls
    $calls = array_slice($calls, -100);
    update_option('my_integration_api_calls', $calls);
}

// Check if we're within rate limits
private function can_make_api_call() {
    $calls = get_option('my_integration_api_calls', []);
    $recent_calls = array_filter($calls, function($time) {
        return $time > time() - 60; // Last minute
    });
    
    return count($recent_calls) < 60; // 60 calls per minute limit
}

3. Storing API Credentials Insecurely

Problem: Storing API keys and secrets in plain text makes them vulnerable to exposure.

Solution: Always encrypt sensitive credentials:

// Encrypt API secret before storing
private function save_api_secret($secret) {
    $encrypted = $this->encrypt($secret);
    update_option('my_integration_api_secret', $encrypted);
}

// Decrypt when needed
private function get_api_secret() {
    $encrypted = get_option('my_integration_api_secret', '');
    return $this->decrypt($encrypted);
}

4. Not Handling MainWP Child Site Disconnections

Problem: If a site is removed from MainWP, your integration might continue trying to sync data for it.

Solution: Hook into the site deletion event:

// Clean up when a site is removed from MainWP
add_action('mainwp_site_deleted', function($website_id) {
    // Delete site-specific data
    delete_option('my_integration_data_' . $website_id);
    delete_option('my_integration_last_sync_' . $website_id);
    
    // If needed, notify the third-party service
    try {
        $this->api_client->remove_site($website_id);
    } catch (Exception $e) {
        error_log('Failed to remove site from service: ' . $e->getMessage());
    }
});

5. Ignoring Error States in the UI

Problem: When API errors occur, users are left in the dark about what went wrong.

Solution: Provide clear error states and recovery options in your UI:

// Store API connection state
public function check_api_connection() {
    try {
        $result = $this->api_client->test_connection();
        update_option('my_integration_connection_status', 'connected');
        return true;
    } catch (Exception $e) {
        update_option('my_integration_connection_status', 'error');
        update_option('my_integration_connection_error', $e->getMessage());
        return false;
    }
}

// Display connection status in admin UI
public function render_connection_status() {
    $status = get_option('my_integration_connection_status', '');
    
    if ($status === 'connected') {
        echo '<div class="ui green message">Connected to API</div>';
    } else if ($status === 'error') {
        $error = get_option('my_integration_connection_error', 'Unknown error');
        echo '<div class="ui red message">API Connection Error: ' . esc_html($error) . '</div>';
        echo '<button class="ui button" id="reconnect-api">Reconnect</button>';
    } else {
        echo '<div class="ui yellow message">Not connected to API</div>';
    }
}

Quick Start for Experienced Developers

// 1. Secure credential storage
class ApiCredentialManager {
    public static function save_credentials($api_key, $api_secret) {
        // Never store credentials in plain text
        update_option('my_integration_api_key', $api_key);
        update_option('my_integration_api_secret', self::encrypt($api_secret));
    }
    
    private static function encrypt($data) {
        if (!defined('AUTH_KEY')) return $data; // Fallback if AUTH_KEY not defined
        $iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length('aes-256-cbc'));
        $encrypted = openssl_encrypt($data, 'aes-256-cbc', AUTH_KEY, 0, $iv);
        return base64_encode($iv . $encrypted);
    }
}

// 2. Implement caching for performance
function get_api_data($endpoint, $params = []) {
    $cache_key = 'my_integration_' . md5($endpoint . serialize($params));
    $cached = get_transient($cache_key);
    
    if (false !== $cached) {
        return $cached; // Return cached data if available
    }
    
    // Get fresh data from API
    $data = $api_client->get($endpoint, $params);
    
    // Cache for 5 minutes (adjust based on data volatility)
    set_transient($cache_key, $data, 5 * MINUTE_IN_SECONDS);
    
    return $data;
}

// 3. Implement graceful error handling
try {
    $data = $api_client->get('endpoint');
    // Process data
} catch (ApiRateLimitException $e) {
    // Log rate limit error and schedule retry
    error_log('Rate limit exceeded: ' . $e->getMessage());
    wp_schedule_single_event(time() + $e->getRetryAfter(), 'my_integration_retry_api_call', [$endpoint, $params]);
    // Show user-friendly message
    return ['error' => 'Service temporarily unavailable. Retry scheduled.'];
} catch (ApiException $e) {
    // Log error and show user-friendly message
    error_log('API Error: ' . $e->getMessage());
    return ['error' => 'Unable to connect to service. Please try again later.'];
}

// 4. Implement proper logging
function log_api_activity($action, $status, $message = '') {
    $logs = get_option('my_integration_logs', []);
    $logs[] = [
        'time' => current_time('mysql'),
        'action' => $action,
        'status' => $status,
        'message' => $message
    ];
    // Keep only the last 100 logs
    $logs = array_slice($logs, -100);
    update_option('my_integration_logs', $logs);
}

Security Considerations

Security should be your top priority when integrating with third-party APIs. Here are key practices to follow:

Secure Storage of API Credentials

Never store API keys, tokens, or secrets in plain text:

/**
 * Save API credentials securely
 *
 * @param string $api_key API key
 * @param string $api_secret API secret
 * @return bool Whether the credentials were saved
 */
public function save_credentials($api_key, $api_secret) {
    // Validate credentials
    if (empty($api_key) || empty($api_secret)) {
        return false;
    }
    
    // Save API key
    update_option('my_integration_api_key', $api_key);
    
    // Encrypt and save API secret
    $encrypted_secret = $this->encrypt_secret($api_secret);
    update_option('my_integration_api_secret', $encrypted_secret);
    
    return true;
}

/**
 * Encrypt sensitive data
 *
 * @param string $data Data to encrypt
 * @return string Encrypted data
 */
private function encrypt_secret($data) {
    // Use WordPress AUTH_KEY as encryption key
    $key = defined('AUTH_KEY') ? AUTH_KEY : 'default-key';
    
    // Generate random initialization vector
    $iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length('aes-256-cbc'));
    
    // Encrypt data
    $encrypted = openssl_encrypt($data, 'aes-256-cbc', $key, 0, $iv);
    
    // Combine IV and encrypted data
    return base64_encode($iv . $encrypted);
}

/**
 * Decrypt sensitive data
 *
 * @param string $encrypted_data Encrypted data
 * @return string Decrypted data
 */
private function decrypt_secret($encrypted_data) {
    // Use WordPress AUTH_KEY as encryption key
    $key = defined('AUTH_KEY') ? AUTH_KEY : 'default-key';
    
    // Decode combined data
    $combined = base64_decode($encrypted_data);
    
    // Extract IV and encrypted data
    $iv_length = openssl_cipher_iv_length('aes-256-cbc');
    $iv = substr($combined, 0, $iv_length);
    $encrypted = substr($combined, $iv_length);
    
    // Decrypt data
    return openssl_decrypt($encrypted, 'aes-256-cbc', $key, 0, $iv);
}

Use HTTPS for All API Communication

Always use HTTPS for API communication to prevent man-in-the-middle attacks:

/**
 * Ensure API URL uses HTTPS
 *
 * @param string $url API URL
 * @return string Secure API URL
 */
private function ensure_https($url) {
    if (strpos($url, 'http://') === 0) {
        $url = 'https://' . substr($url, 7);
    }
    
    return $url;
}

Implement Proper Authentication

Use the most secure authentication method available:

  1. OAuth 2.0: Preferred for most APIs
  2. API Keys with HMAC: Good for server-to-server communication
  3. JWT: Good for stateless authentication
  4. Basic Auth: Only use with HTTPS and when no better option is available

Validate and Sanitize All Input

Always validate and sanitize input before sending it to the API:

/**
 * Validate API parameters
 *
 * @param array $params API parameters
 * @param array $allowed_params Allowed parameters and their types
 * @return array Validated parameters
 */
private function validate_params($params, $allowed_params) {
    $validated = [];
    
    foreach ($params as $key => $value) {
        // Check if parameter is allowed
        if (!isset($allowed_params[$key])) {
            continue;
        }
        
        // Validate parameter type
        $type = $allowed_params[$key];
        switch ($type) {
            case 'string':
                $validated[$key] = sanitize_text_field($value);
                break;
            
            case 'int':
                $validated[$key] = (int)$value;
                break;
            
            case 'float':
                $validated[$key] = (float)$value;
                break;
            
            case 'bool':
                $validated[$key] = (bool)$value;
                break;
            
            case 'array':
                $validated[$key] = is_array($value) ? $value : [];
                break;
            
            default:
                $validated[$key] = $value;
                break;
        }
    }
    
    return $validated;
}

Implement Rate Limit Protection

Protect your integration from abuse with rate limiting:

/**
 * Check if rate limit is exceeded
 *
 * @param string $action Action name
 * @param int $limit Rate limit
 * @param int $period Period in seconds
 * @return bool Whether rate limit is exceeded
 */
public function is_rate_limited($action, $limit = 10, $period = 60) {
    // Get current user ID
    $user_id = get_current_user_id();
    if (!$user_id) {
        return false;
    }
    
    // Get rate limit data
    $rate_limits = get_user_meta($user_id, 'my_integration_rate_limits', true);
    if (!is_array($rate_limits)) {
        $rate_limits = [];
    }
    
    // Get action data
    $action_data = $rate_limits[$action] ?? [
        'count' => 0,
        'reset' => time() + $period
    ];
    
    // Check if period has expired
    if (time() > $action_data['reset']) {
        // Reset count and period
        $action_data = [
            'count' => 1,
            'reset' => time() + $period
        ];
        
        // Save rate limit data
        $rate_limits[$action] = $action_data;
        update_user_meta($user_id, 'my_integration_rate_limits', $rate_limits);
        
        return false;
    }
    
    // Check if limit is exceeded
    if ($action_data['count'] >= $limit) {
        return true;
    }
    
    // Increment count
    $action_data['count']++;
    
    // Save rate limit data
    $rate_limits[$action] = $action_data;
    update_user_meta($user_id, 'my_integration_rate_limits', $rate_limits);
    
    return false;
}

Secure Webhook Handling

If your integration uses webhooks, implement proper security:

/**
 * Verify webhook signature
 *
 * @param string $payload Webhook payload
 * @param string $signature Webhook signature
 * @return bool Whether the signature is valid
 */
public function verify_webhook_signature($payload, $signature) {
    // Get webhook secret
    $secret = get_option('my_integration_webhook_secret', '');
    if (empty($secret)) {
        return false;
    }
    
    // Calculate expected signature
    $expected_signature = hash_hmac('sha256', $payload, $secret);
    
    // Compare signatures
    return hash_equals($expected_signature, $signature);
}

Performance Optimization

Optimize your integration for performance to provide a better user experience:

Implement Caching

Cache API responses to reduce API calls and improve performance:

/**
 * Get data with caching
 *
 * @param string $endpoint API endpoint
 * @param array $params Query parameters
 * @param int $cache_time Cache time in seconds
 * @return array Response data
 */
public function get_cached($endpoint, $params = [], $cache_time = 300) {
    $cache_key = 'my_integration_' . md5($endpoint . serialize($params));
    
    // Check cache
    $cached = get_transient($cache_key);
    if (false !== $cached) {
        return $cached;
    }
    
    // Get fresh data
    $data = $this->api_client->get($endpoint, $params);
    
    // Cache the data
    set_transient($cache_key, $data, $cache_time);
    
    return $data;
}

Implement Batch Processing

Combine multiple API calls into a single batch request when possible:

/**
 * Process batch of API requests
 *
 * @param array $requests Array of request data
 * @return array Response data
 */
public function batch_process($requests) {
    // Check if API supports batch processing
    if (!$this->api_client->supports_batch()) {
        // Process requests individually
        $responses = [];
        foreach ($requests as $key => $request) {
            $responses[$key] = $this->api_client->request(
                $request['method'],
                $request['endpoint'],
                $request['params'] ?? [],
                $request['data'] ?? null
            );
        }
        return $responses;
    }
    
    // Process requests in a batch
    return $this->api_client->batch($requests);
}

Implement Asynchronous Processing

Use WordPress cron for asynchronous processing of non-critical tasks:

/**
 * Schedule asynchronous API call
 *
 * @param string $method HTTP method
 * @param string $endpoint API endpoint
 * @param array $params Query parameters
 * @param array $data Request data
 */
public function schedule_api_call($method, $endpoint, $params = [], $data = null) {
    wp_schedule_single_event(
        time(),
        'my_integration_api_call',
        [$method, $endpoint, $params, $data]
    );
}

/**
 * Process scheduled API call
 *
 * @param string $method HTTP method
 * @param string $endpoint API endpoint
 * @param array $params Query parameters
 * @param array $data Request data
 */
public function process_scheduled_api_call($method, $endpoint, $params = [], $data = null) {
    try {
        $response = $this->api_client->request($method, $endpoint, $params, $data);
        $this->log_api_activity($endpoint, 'success');
    } catch (Exception $e) {
        $this->log_api_activity($endpoint, 'error', $e->getMessage());
    }
}

Optimize Data Transfer

Minimize the amount of data transferred:

  1. Request only the data you need: Use API parameters to filter results
  2. Compress data: Use gzip compression when available
  3. Use pagination: Request data in smaller chunks
  4. Use conditional requests: Use ETag or If-Modified-Since headers
/**
 * Get paginated data
 *
 * @param string $endpoint API endpoint
 * @param array $params Query parameters
 * @param int $page_size Page size
 * @return array All data
 */
public function get_all_paginated_data($endpoint, $params = [], $page_size = 100) {
    $all_data = [];
    $page = 1;
    
    do {
        // Get page of data
        $page_params = array_merge($params, [
            'page' => $page,
            'per_page' => $page_size
        ]);
        
        $data = $this->api_client->get($endpoint, $page_params);
        
        // Add data to result
        if (!empty($data)) {
            $all_data = array_merge($all_data, $data);
        }
        
        // Increment page
        $page++;
        
        // Continue until no more data
    } while (!empty($data) && count($data) === $page_size);
    
    return $all_data;
}

Error Handling and Graceful Degradation

Implement robust error handling to provide a better user experience:

Implement Comprehensive Error Handling

Handle different types of errors appropriately:

try {
    $data = $this->api_client->get('endpoint');
    // Process data
} catch (ApiAuthException $e) {
    // Handle authentication errors
    $this->log_error('Authentication error: ' . $e->getMessage());
    $this->notify_admin('API authentication failed. Please check your credentials.');
    return $this->get_fallback_data();
} catch (ApiRateLimitException $e) {
    // Handle rate limiting
    $this->log_error('Rate limit exceeded: ' . $e->getMessage());
    $this->schedule_retry($e->getRetryAfter());
    return $this->get_cached_data() ?: $this->get_fallback_data();
} catch (ApiServerException $e) {
    // Handle server errors
    $this->log_error('Server error: ' . $e->getMessage());
    return $this->get_cached_data() ?: $this->get_fallback_data();
} catch (ApiClientException $e) {
    // Handle client errors
    $this->log_error('Client error: ' . $e->getMessage());
    return $this->get_fallback_data();
} catch (Exception $e) {
    // Handle unexpected errors
    $this->log_error('Unexpected error: ' . $e->getMessage());
    return $this->get_fallback_data();
}

Implement Retry Logic

Retry failed API calls with exponential backoff:

/**
 * Retry API call with exponential backoff
 *
 * @param string $method HTTP method
 * @param string $endpoint API endpoint
 * @param array $params Query parameters
 * @param array $data Request data
 * @param int $retry_count Retry count
 * @param int $max_retries Maximum number of retries
 * @return array Response data
 */
public function retry_api_call($method, $endpoint, $params = [], $data = null, $retry_count = 0, $max_retries = 3) {
    try {
        return $this->api_client->request($method, $endpoint, $params, $data);
    } catch (ApiRateLimitException $e) {
        // Handle rate limiting
        $retry_after = $e->getRetryAfter();
        if ($retry_count < $max_retries) {
            sleep($retry_after);
            return $this->retry_api_call($method, $endpoint, $params, $data, $retry_count + 1, $max_retries);
        }
        throw $e;
    } catch (ApiServerException $e) {
        // Handle server errors
        if ($retry_count < $max_retries) {
            $backoff = pow(2, $retry_count);
            sleep($backoff);
            return $this->retry_api_call($method, $endpoint, $params, $data, $retry_count + 1, $max_retries);
        }
        throw $e;
    } catch (ApiConnectionException $e) {
        // Handle connection errors
        if ($retry_count < $max_retries) {
            $backoff = pow(2, $retry_count);
            sleep($backoff);
            return $this->retry_api_call($method, $endpoint, $params, $data, $retry_count + 1, $max_retries);
        }
        throw $e;
    }
}

Implement Fallback Mechanisms

Provide fallback mechanisms for when the API is unavailable:

/**
 * Get fallback data
 *
 * @param string $endpoint API endpoint
 * @return array Fallback data
 */
public function get_fallback_data($endpoint = '') {
    // Check for cached data
    $cached = $this->get_cached_data($endpoint);
    if ($cached) {
        return $cached;
    }
    
    // Return default data
    switch ($endpoint) {
        case 'users':
            return ['status' => 'offline', 'data' => []];
        case 'stats':
            return ['status' => 'offline', 'data' => ['visits' => 0, 'conversions' => 0]];
        default:
            return ['status' => 'offline', 'data' => []];
    }
}

Implement Circuit Breaker Pattern

Implement the circuit breaker pattern to prevent cascading failures:

class CircuitBreaker {
    private $failures = 0;
    private $threshold = 5;
    private $reset_timeout = 60;
    private $last_failure_time = 0;
    private $state = 'closed'; // closed, open, half-open
    
    /**
     * Execute function with circuit breaker
     *
     * @param callable $func Function to execute
     * @return mixed Function result
     */
    public function execute($func) {
        // Check if circuit is open
        if ($this->is_open()) {
            // Check if reset timeout has passed
            if (time() - $this->last_failure_time > $this->reset_timeout) {
                // Set circuit to half-open
                $this->state = 'half-open';
            } else {
                throw new Exception('Circuit is open');
            }
        }
        
        try {
            // Execute function
            $result = $func();
            
            // Reset failures on success
            if ($this->state === 'half-open') {
                $this->reset();
            }
            
            return $result;
        } catch (Exception $e) {
            // Increment failures
            $this->failures++;
            $this->last_failure_time = time();
            
            // Open circuit if threshold is reached
            if ($this->failures >= $this->threshold) {
                $this->state = 'open';
            }
            
            throw $e;
        }
    }
    
    /**
     * Check if circuit is open
     *
     * @return bool Whether circuit is open
     */
    public function is_open() {
        return $this->state === 'open';
    }
    
    /**
     * Reset circuit
     */
    public function reset() {
        $this->failures = 0;
        $this->state = 'closed';
    }
}

User Experience Design for API-Dependent Features

Design your integration with the user experience in mind:

Provide Clear Feedback

Keep users informed about the status of API operations:

/**
 * Display API status
 */
public function display_api_status() {
    // Check API status
    $status = $this->get_api_status();
    
    // Display status
    switch ($status) {
        case 'connected':
            ?>
            <div class="ui green message">
                <i class="check icon"></i>
                <?php esc_html_e('Connected to API', 'my-integration'); ?>
            </div>
            <?php
            break;
        
        case 'limited':
            ?>
            <div class="ui yellow message">
                <i class="exclamation triangle icon"></i>
                <?php esc_html_e('Connected to API with limited functionality', 'my-integration'); ?>
            </div>
            <?php
            break;
        
        case 'disconnected':
            ?>
            <div class="ui red message">
                <i class="times icon"></i>
                <?php esc_html_e('Disconnected from API', 'my-integration'); ?>
            </div>
            <?php
            break;
    }
}

Implement Loading States

Show loading states during API operations:

/**
 * Display loading state
 *
 * @param string $message Loading message
 */
public function display_loading_state($message = '') {
    ?>
    <div class="ui segment loading-state">
        <div class="ui active dimmer">
            <div class="ui text loader"><?php echo esc_html($message ?: __('Loading...', 'my-integration')); ?></div>
        </div>
        <p></p>
    </div>
    
    <script type="text/javascript">
    jQuery(document).ready(function($) {
        // Show loading state
        $('.loading-state').show();
        
        // Make API request
        $.ajax({
            url: ajaxurl,
            type: 'POST',
            data: {
                action: 'my_integration_api_request',
                nonce: '<?php echo wp_create_nonce('my_integration_api_request'); ?>'
            },
            success: function(response) {
                // Hide loading state
                $('.loading-state').hide();
                
                // Display response
                if (response.success) {
                    // Display success message
                } else {
                    // Display error message
                }
            },
            error: function() {
                // Hide loading state
                $('.loading-state').hide();
                
                // Display error message
            }
        });
    });
    </script>
    <?php
}

Implement Offline Mode

Allow users to continue working when the API is unavailable:

/**
 * Check if offline mode is enabled
 *
 * @return bool Whether offline mode is enabled
 */
public function is_offline_mode() {
    return get_option('my_integration_offline_mode', false);
}

/**
 * Enable offline mode
 */
public function enable_offline_mode() {
    update_option('my_integration_offline_mode', true);
}

/**
 * Disable offline mode
 */
public function disable_offline_mode() {
    update_option('my_integration_offline_mode', false);
}

/**
 * Get data with offline mode support
 *
 * @param string $endpoint API endpoint
 * @param array $params Query parameters
 * @return array Data
 */
public function get_data_with_offline_support($endpoint, $params = []) {
    // Check if offline mode is enabled
    if ($this->is_offline_mode()) {
        return $this->get_fallback_data($endpoint);
    }
    
    try {
        // Get data from API
        return $this->api_client->get($endpoint, $params);
    } catch (Exception $e) {
        // Enable offline mode
        $this->enable_offline_mode();
        
        // Return fallback data
        return $this->get_fallback_data($endpoint);
    }
}

Data Synchronization Strategies

Implement effective data synchronization between MainWP and the third-party service:

Implement Pull-Based Sync

Pull data from the third-party service on a schedule:

/**
 * Schedule regular syncs
 */
public function schedule_syncs() {
    // Schedule daily sync if not already scheduled
    if (!wp_next_scheduled('my_integration_daily_sync')) {
        wp_schedule_event(time(), 'daily', 'my_integration_daily_sync');
    }
    
    // Add action hook for the event
    add_action('my_integration_daily_sync', [$this, 'sync_all_websites']);
}

/**
 * Sync all websites
 */
public function sync_all_websites() {
    // Get all MainWP websites
    $websites = MainWP\Dashboard\MainWP_DB::instance()->get_websites();
    
    foreach ($websites as $website) {
        // Check if integration is enabled for this website
        $enabled = get_option('my_integration_enabled_' . $website->id, false);
        if (!$enabled) {
            continue;
        }
        
        // Sync data
        $this->sync_from_service($website->id);
    }
}

Implement Push-Based Sync

Push data to the third-party service when it changes:

/**
 * Hook into MainWP events to push data
 */
public function register_push_hooks() {
    // Hook into site added event
    add_action('mainwp_site_added', [$this, 'push_site_added'], 10, 1);
    
    // Hook into site updated event
    add_action('mainwp_site_updated', [$this, 'push_site_updated'], 10, 1);
    
    // Hook into site deleted event
    add_action('mainwp_site_deleted', [$this, 'push_site_deleted'], 10, 1);
}

/**
 * Push site added event
 *
 * @param int $website_id Website ID
 */
public function push_site_added($website_id) {
    // Get website details
    $website = MainWP\Dashboard\MainWP_DB::instance()->get_website_by_id($website_id);
    if (!$website) {
        return;
    }
    
    // Push data to third-party service
    $this->api_client->post('sites', [
        'url' => $website->url,
        'name' => $website->name
    ]);
}

Implement Bidirectional Sync

Synchronize data in both directions:

/**
 * Perform bidirectional sync
 *
 * @param int $website_id Website ID
 */
public function bidirectional_sync($website_id) {
    // Get website details
    $website = MainWP\Dashboard\MainWP_DB::instance()->get_website_by_id($website_id);
    if (!$website) {
        return;
    }
    
    // Get last sync timestamp
    $last_sync = get_option('my_integration_last_sync_' . $website_id, 0);
    
    // Get changes from third-party service
    $service_changes = $this->api_client->get('sites/' . urlencode($website->url) . '/changes', [
        'since' => $last_sync
    ]);
    
    // Get local changes
    $local_changes = $this->get_local_changes($website_id, $last_sync);
    
    // Resolve conflicts
    $resolved_changes = $this->resolve_conflicts($service_changes, $local_changes);
    
    // Apply service changes locally
    $this->apply_service_changes($website_id, $resolved_changes['local_updates']);
    
    // Push local changes to service
    $this->push_local_changes($website_id, $resolved_changes['service_updates']);
    
    // Update last sync timestamp
    update_option('my_integration_last_sync_' . $website_id, time());
}

Implement Conflict Resolution

Resolve conflicts when data changes in both systems:

/**
 * Resolve conflicts between local and service changes
 *
 * @param array $service_changes Changes from third-party service
 * @param array $local_changes Local changes
 * @return array Resolved changes
 */
public function resolve_conflicts($service_changes, $local_changes) {
    $local_updates = [];
    $service_updates = [];
    
    // Process service changes
    foreach ($service_changes as $key => $service_item) {
        if (isset($local_changes[$key])) {
            // Conflict: both sides have changes
            $local_item = $local_changes[$key];
            
            // Resolve based on timestamp (newer wins)
            if ($service_item['updated_at'] > $local_item['updated_at']) {
                $local_updates[$key] = $service_item;
            } else {
                $service_updates[$key] = $local_item;
            }
            
            // Remove from local changes to mark as processed
            unset($local_changes[$key]);
        } else {
            // No conflict: only service has changes
            $local_updates[$key] = $service_item;
        }
    }
    
    // Remaining local changes need to be sent to service
    foreach ($local_changes as $key => $local_item) {
        $service_updates[$key] = $local_item;
    }
    
    return [
        'local_updates' => $local_updates,
        'service_updates' => $service_updates
    ];
}

Handling API Rate Limits and Quotas

Implement strategies to handle API rate limits and quotas:

Implement Rate Limit Tracking

Track API rate limits to avoid exceeding them:

/**
 * Track API rate limits
 *
 * @param array $response API response
 */
public function track_rate_limits($response) {
    // Get rate limit headers
    $rate_limit = wp_remote_retrieve_header($response, 'X-RateLimit-Limit');
    $rate_limit_remaining = wp_remote_retrieve_header($response, 'X-RateLimit-Remaining');
    $rate_limit_reset = wp_remote_retrieve_header($response, 'X-RateLimit-Reset');
    
    // Save rate limit data
    update_option('my_integration_rate_limit', [
        'limit' => $rate_limit,
        'remaining' => $rate_limit_remaining,
        'reset' => $rate_limit_reset
    ]);
}

/**

Writing Clean & Maintainable MainWP Code: Best Practices

This guide is a placeholder and will be expanded in the future.

This guide will cover best practices for writing clean, maintainable code for MainWP extensions, including:

Check back soon for the complete guide.

Add-on Development Lifecycle

This guide explains the lifecycle of a MainWP add-on (extension or integration), from initial concept to ongoing maintenance. Understanding this lifecycle will help you plan, develop, and maintain successful add-ons.

Understanding MainWP Add-on Types

Before diving into the lifecycle, it's important to understand the terminology:

This guide applies to both extensions and integrations, with specific considerations noted where relevant.

Overview

The MainWP add-on development lifecycle consists of several distinct phases:

flowchart TD A[Planning] --> B[Development] B --> C[Testing] C --> D[Distribution] D --> E[Maintenance] E --> F[Updates] F --> C

Each phase has specific goals, activities, and deliverables that contribute to creating a successful extension.

1. Planning Phase

The planning phase is where you define what your add-on will do and how it will work. This phase is critical for ensuring your add-on meets real user needs and integrates well with MainWP.

Key Activities

  1. Market Research: Identify user needs and potential competitors

  2. Feature Definition: Determine the core functionality of your add-on

  3. Technical Assessment: Evaluate technical feasibility and requirements

  4. Integration Planning: Determine how your add-on will integrate with MainWP

  5. Resource Planning: Estimate time, effort, and resources needed

  6. Add-on Type Decision: Determine whether you need to build an extension or integration

    For integrations, additional planning is required:

    • Identify the third-party service or plugin you'll integrate with
    • Research API documentation and requirements
    • Evaluate authentication methods and security considerations
    • Plan for handling API rate limits and service disruptions

Deliverables

Best Practices

2. Development Phase

The development phase is where you build your add-on, transforming your plans into working code.

Key Activities

  1. Environment Setup: Create a development environment with MainWP Dashboard and Child sites

  2. Architecture Design: Design the structure and components of your add-on

  3. Coding: Implement the add-on's functionality

  4. Documentation: Create inline code documentation and user documentation

  5. Internal Testing: Perform ongoing testing during development

    For integrations, additional activities include:

    • Implementing API client and authentication
    • Creating fallback mechanisms for API failures
    • Developing data synchronization between MainWP and the third-party service
    • Implementing caching to reduce API calls

Deliverables

Best Practices

3. Testing Phase

The testing phase ensures your add-on works correctly, integrates properly with MainWP, and provides a good user experience.

Key Activities

  1. Functional Testing: Verify all features work as expected

  2. Integration Testing: Ensure proper integration with MainWP Dashboard and Child sites

  3. Compatibility Testing: Test with different WordPress versions, themes, and plugins

  4. Performance Testing: Evaluate the add-on's impact on site performance

  5. Security Testing: Identify and address potential security vulnerabilities

  6. User Testing: Get feedback from real users

    For integrations, additional testing includes:

    • API failure testing: Verify graceful handling of API timeouts and errors
    • Rate limit testing: Ensure your add-on respects API rate limits
    • Data synchronization testing: Verify data consistency between MainWP and the third-party service
    • Authentication testing: Ensure secure handling of API credentials

Deliverables

Best Practices

4. Distribution Phase

The distribution phase is where you package your add-on and make it available to users.

Key Activities

  1. Packaging: Prepare your add-on for distribution

  2. Documentation Finalization: Complete user documentation

  3. Marketing Materials: Create screenshots, videos, and promotional content

  4. Distribution Channel Setup: Determine how you'll distribute your add-on

  5. Launch Planning: Plan the launch of your add-on

    For integrations, additional activities include:

    • Documenting third-party API requirements and limitations
    • Creating setup guides for API credentials
    • Preparing support documentation for common API issues

Deliverables

Distribution Options

  1. MainWP Add-ons Directory: Official marketplace for MainWP add-ons
  2. Your Own Website: Sell or distribute through your own site
  3. GitHub or Other Repositories: Open-source distribution
  4. WordPress.org: If your add-on has standalone functionality

Best Practices

5. Maintenance Phase

The maintenance phase involves supporting your add-on, fixing bugs, and making minor improvements.

Key Activities

  1. User Support: Respond to user questions and issues

  2. Bug Fixing: Address reported bugs and issues

  3. Minor Enhancements: Implement small improvements

  4. Compatibility Updates: Ensure compatibility with new MainWP and WordPress versions

  5. Performance Optimization: Improve performance based on real-world usage

    For integrations, additional activities include:

    • Monitoring third-party API changes and updates
    • Adapting to API deprecations or new features
    • Addressing API-related issues reported by users

Deliverables

Best Practices

6. Update Phase

The update phase involves developing significant new features or major changes to your add-on.

Key Activities

  1. Feature Planning: Plan new features based on user feedback and market needs

  2. Major Enhancements: Implement significant new functionality

  3. Architecture Improvements: Make major structural improvements

  4. Testing New Features: Thoroughly test new functionality

  5. Update Documentation: Update documentation to reflect new features

  6. Release Planning: Plan and execute the release of updates

    For integrations, additional activities include:

    • Implementing support for new API features or versions
    • Redesigning integration architecture for better performance or reliability
    • Adding support for additional third-party services

Deliverables

Best Practices

Lifecycle Management Considerations

Version Control

Using a version control system like Git is essential for managing your add-on's lifecycle:

Documentation Management

Documentation should evolve alongside your add-on:

Communication with Users

Maintaining clear communication with users throughout the lifecycle:

Add-on Lifecycle Tools

Several tools can help manage your add-on's lifecycle:

  1. Version Control: GitHub, GitLab, Bitbucket
  2. Issue Tracking: GitHub Issues, JIRA, Trello
  3. Documentation: GitHub Wiki, ReadTheDocs, WordPress Codex
  4. Continuous Integration: GitHub Actions, Travis CI, Jenkins
  5. Testing: PHPUnit, WP-CLI, Browser Testing Tools

Case Studies: Add-on Lifecycle in Action

Let's look at hypothetical case studies of MainWP add-ons through their lifecycle:

Extension Example: Client Reports Pro

Planning

A developer identifies that MainWP users need better reporting capabilities for client sites. They research existing solutions, define key features, and create a development plan for a "Client Reports Pro" extension.

Development

The developer uses the MainWP Development Extension as a starting point, implements the reporting features, creates a database structure for storing report templates, and builds a user interface for creating and scheduling reports.

Testing

The extension is tested with multiple child sites, various WordPress configurations, and different report types. Beta testers provide feedback on usability and feature requests.

Distribution

The developer creates comprehensive documentation, screenshots, and a demo video. They set up a website to sell the extension and establish a support system.

Maintenance

After launch, the developer responds to support requests, fixes bugs, and makes minor improvements based on user feedback.

Updates

Based on user requests, the developer plans and implements a major update that adds PDF export capabilities and more report templates.

Integration Example: Analytics Connect

Planning

A developer notices that MainWP users want to see Google Analytics data directly in their MainWP Dashboard. They research the Google Analytics API, authentication requirements, and plan an integration that will connect MainWP sites with Google Analytics accounts.

Development

The developer creates an API client for Google Analytics, implements OAuth authentication, develops data synchronization between MainWP and Google Analytics, and builds dashboard widgets to display analytics data.

Testing

The integration is tested with various Google Analytics account configurations, different permission levels, and edge cases like API rate limiting. Beta testers provide feedback on the authentication process and data visualization.

Distribution

The developer creates documentation explaining how to connect Google Analytics accounts, obtain API credentials, and interpret the analytics data. They create marketing materials highlighting the time-saving benefits of viewing analytics directly in MainWP.

Maintenance

After launch, the developer monitors Google Analytics API changes, responds to authentication issues reported by users, and optimizes API calls to stay within rate limits.

Updates

When Google releases a new Analytics API version, the developer plans and implements support for the new API while maintaining backward compatibility with the old version.

Conclusion

Understanding the MainWP add-on development lifecycle helps you create, distribute, and maintain successful extensions and integrations. By following best practices at each phase, you can create add-ons that provide real value to users and integrate seamlessly with the MainWP ecosystem.

Whether you're building a standalone extension or integrating with a third-party service, the same lifecycle principles apply, with additional considerations for integrations to ensure reliable connections with external services.

Next Steps

Now that you understand the add-on development lifecycle, you can:

0006-how-to-actions-filters


title: Using MainWP Actions & Filters layout: documentation-single # Explicitly define layout navigation_title: Actions & Filters # Optional shorter title for sidebar header navigation: # Define sidebar structure based on H2/H3 headings



title: Using MainWP Actions & Filters layout: documentation-single # Explicitly define layout navigation_title: Actions & Filters # Optional shorter title for sidebar header navigation: # Define sidebar structure based on H2/H3 headings


Using MainWP Actions & Filters

This guide explains how to use MainWP's actions and filters (hooks) to integrate your add-on (extension or integration) with the MainWP Dashboard and Child plugins. Understanding these hooks is essential for creating powerful, well-integrated add-ons, whether you're building a standalone extension or a third-party integration.

Quick Start for Experienced Developers

namespace MyCompany\MyExtension;

// Hook into MainWP Dashboard actions
add_action('mainwp_pageheader_extensions', function() {
    // Add content to extension page header
});

// Filter MainWP Dashboard data
add_filter('mainwp_getextensions', function($extensions) {
    // Register your extension
    $extensions[] = array(
        'plugin' => __FILE__,
        'api' => 'MyExtension',
        'mainwp' => true,
        'callback' => array('MyCompany\\MyExtension\\MyExtension', 'get_instance'),
        'name' => 'My Extension'
    );
    return $extensions;
});

// Hook into child site actions
add_action('mainwp_child_call', function($action) {
    // Handle requests from the dashboard
    if ($action == 'my_custom_action') {
        // Process the request
    }
});

Key hook categories:

Understanding WordPress Hooks

Before diving into MainWP-specific hooks, it's important to understand the WordPress hooks system:

Actions

Actions are triggered at specific points during execution and allow you to add custom functionality:

namespace MyCompany\MyExtension;

add_action('hook_name', __NAMESPACE__ . '\\callback_function', 10, 2);

function callback_function($arg1, $arg2) {
    // Do something when the hook is triggered
}

Filters

Filters allow you to modify data before it's used:

namespace MyCompany\MyExtension;

add_filter('hook_name', __NAMESPACE__ . '\\filter_function', 10, 1);

function filter_function($value) {
    // Modify $value
    return $modified_value;
}

MainWP Dashboard Hooks

MainWP Dashboard provides numerous hooks for extending its functionality. Here are the most important ones for extension developers:

Extension Registration

The most important hook for any MainWP extension is mainwp_getextensions, which registers your extension with the MainWP Dashboard:

namespace MyCompany\MyExtension;

add_filter('mainwp_getextensions', function($extensions) {
    $extensions[] = array(
        'plugin' => __FILE__,
        'api' => 'MyExtension',
        'mainwp' => true,
        'callback' => array(__NAMESPACE__ . '\\MyExtension', 'get_instance'),
        'name' => 'My Extension'
    );
    return $extensions;
});

This tells MainWP:

Admin Menu Integration

To add menu items to the MainWP Dashboard:

namespace MyCompany\MyExtension;

class Admin {
    public function __construct() {
        add_action('mainwp_admin_menu', [$this, 'add_menu_item']);
    }

    public function add_menu_item() {
        add_submenu_page(
            'mainwp_tab',
            __('My Extension', 'my-extension'),
            __('My Extension', 'my-extension'),
            'read',
            'MyExtension',
            [$this, 'render_admin_page']
        );
    }

    public function render_admin_page() {
        // Page rendering code
    }
}

Page Header and Footer

To add content to the header or footer of your extension's pages:

namespace MyCompany\MyExtension;

class UI {
    public function __construct() {
        add_action('mainwp_pageheader_extensions', [$this, 'render_header']);
        add_action('mainwp_pagefooter_extensions', [$this, 'render_footer']);
    }

    public function render_header() {
        // Add content to the page header
        ?>
        <div class="ui segment">
            <h2 class="ui header"><?php esc_html_e('My Extension Header', 'my-extension'); ?></h2>
        </div>
        <?php
    }

    public function render_footer() {
        // Add content to the page footer
        ?>
        <div class="ui segment">
            <p><?php esc_html_e('My Extension Footer', 'my-extension'); ?></p>
        </div>
        <?php
    }
}

Dashboard Widgets

To add widgets to the MainWP Dashboard:

namespace MyCompany\MyExtension;

class Dashboard {
    public function __construct() {
        add_action('mainwp_dashboard_widgets', [$this, 'register_widget']);
    }

    public function register_widget() {
        add_meta_box(
            'my_extension_widget',
            __('My Extension Widget', 'my-extension'),
            [$this, 'render_dashboard_widget'],
            'mainwp_dashboard',
            'normal',
            'default'
        );
    }

    public function render_dashboard_widget() {
        // Widget content
        ?>
        <div class="ui segment">
            <p><?php esc_html_e('Widget content here', 'my-extension'); ?></p>
        </div>
        <?php
    }
}

Site Actions

To add actions to the site actions dropdown menu:

namespace MyCompany\MyExtension\Sites;

class Actions {
    public function __construct() {
        add_filter('mainwp_site_actions', [$this, 'add_site_action'], 10, 2);
    }

    public function add_site_action($actions, $website) {
        $actions['my_action'] = array(
            'label' => __('My Custom Action', 'my-extension'),
            'url' => admin_url('admin.php?page=MyExtension&site_id=' . $website->id),
            'icon' => 'cog',
        );
        return $actions;
    }
}

Site Sync Data

To add custom data to the site sync process:

namespace MyCompany\MyExtension\Sites;

class Sync {
    public function __construct() {
        add_filter('mainwp_site_sync_data', [$this, 'add_sync_data'], 10, 2);
    }

    public function add_sync_data($data, $website) {
        // Add custom data to the sync process
        $data['my_extension'] = array(
            'custom_field' => 'custom_value',
        );
        return $data;
    }
}

After Site Sync

To perform actions after a site is synced:

namespace MyCompany\MyExtension\Sites;

class Sync {
    public function __construct() {
        add_action('mainwp_site_synced', [$this, 'process_synced_data'], 10, 2);
    }

    public function process_synced_data($website, $data) {
        // Process data after site sync
        if (isset($data['my_extension'])) {
            // Process my_extension data
        }
    }
}

MainWP Child Hooks

If your extension needs to interact with child sites, you'll need to use MainWP Child hooks:

Handling Dashboard Requests

To handle requests from the MainWP Dashboard:

namespace MyCompany\MyExtension\Child;

class Handler {
    public function __construct() {
        add_action('mainwp_child_call', [$this, 'handle_calls']);
    }

    public function handle_calls($action) {
        // Check if this is a request for your extension
        if ($action == 'my_extension') {
            // Get the specific action
            $specific_action = $_POST['specific_action'] ?? '';
            
            // Handle the specific action
            switch ($specific_action) {
                case 'get_data':
                    // Get and return data
                    $data = array('result' => 'success', 'data' => 'your_data');
                    wp_send_json($data);
                    break;
                case 'update_data':
                    // Update data
                    $data = array('result' => 'success', 'message' => 'Data updated');
                    wp_send_json($data);
                    break;
                default:
                    // Handle unknown action
                    $data = array('result' => 'error', 'message' => 'Unknown action');
                    wp_send_json($data);
                    break;
            }
        }
    }
}

Child Plugin Initialization

To initialize your child plugin:

namespace MyCompany\MyExtension\Child;

class Plugin {
    public function __construct() {
        add_action('mainwp_child_init', [$this, 'initialize']);
    }

    public function initialize() {
        // Initialize your child plugin
    }
}

Communication Between Dashboard and Child Sites

One of the most powerful features of MainWP is the ability to communicate between the dashboard and child sites. Here's how to implement this in your add-on:

Sending Requests from Dashboard to Child

namespace MyCompany\MyExtension\Dashboard;

use MainWP\Dashboard\MainWP_DB;
use MainWP\Dashboard\MainWP_Connect;
use Exception;

class Communication {
    public function send_request_to_child($website_id, $action, $data = array()) {
        // Get the website
        $website = MainWP_DB::instance()->get_website_by_id($website_id);
        if (!$website) {
            return array('error' => 'Website not found');
        }
        
        // Prepare the data
        $data['action'] = $action;
        
        // Send the request
        try {
            $information = MainWP_Connect::fetch_url_authed(
                $website,
                'my_extension',
                $data
            );
            
            // Process the response
            if (is_array($information) && isset($information['result'])) {
                return $information;
            } else {
                return array('error' => 'Invalid response from child site');
            }
        } catch (Exception $e) {
            return array('error' => $e->getMessage());
        }
    }
}

Handling Requests on the Child Site

// In your child plugin
add_action('mainwp_child_call', function($action) {
    if ($action == 'my_extension') {
        // Get the specific action
        $specific_action = $_POST['action'] ?? '';
        
        // Handle the specific action
        switch ($specific_action) {
            case 'get_plugin_data':
                // Get plugin data
                $plugin_data = get_plugin_data(WP_PLUGIN_DIR . '/my-plugin/my-plugin.php');
                wp_send_json(array(
                    'result' => 'success',
                    'data' => $plugin_data
                ));
                break;
            // Handle other actions
            default:
                wp_send_json(array(
                    'result' => 'error',
                    'message' => 'Unknown action'
                ));
                break;
        }
    }
});

Common Hook Use Cases

Adding a Settings Page

// Add a settings page to the MainWP Dashboard
add_action('mainwp_admin_menu', function() {
    add_submenu_page(
        'mainwp_tab',
        __('My Extension Settings', 'my-extension'),
        __('My Extension Settings', 'my-extension'),
        'read',
        'MyExtensionSettings',
        array($this, 'render_settings_page')
    );
});

// Render the settings page
public function render_settings_page() {
    // Check if user has permissions
    if (!mainwp_current_user_can('read')) {
        mainwp_do_not_have_permissions(__('My Extension Settings', 'my-extension'));
        return;
    }
    
    // Save settings if form is submitted
    if (isset($_POST['submit'])) {
        // Verify nonce
        check_admin_referer('my_extension_settings_nonce', 'my_extension_settings_nonce');
        
        // Save settings
        $settings = array(
            'setting1' => sanitize_text_field($_POST['setting1'] ?? ''),
            'setting2' => sanitize_text_field($_POST['setting2'] ?? ''),
        );
        update_option('my_extension_settings', $settings);
        
        // Show success message
        ?>
        <div class="ui green message"><?php esc_html_e('Settings saved successfully.', 'my-extension'); ?></div>
        <?php
    }
    
    // Get current settings
    $settings = get_option('my_extension_settings', array(
        'setting1' => '',
        'setting2' => '',
    ));
    
    // Render settings form
    ?>
    <div class="ui segment">
        <h2 class="ui header"><?php esc_html_e('My Extension Settings', 'my-extension'); ?></h2>
        <form class="ui form" method="post" action="">
            <?php wp_nonce_field('my_extension_settings_nonce', 'my_extension_settings_nonce'); ?>
            
            <div class="field">
                <label><?php esc_html_e('Setting 1', 'my-extension'); ?></label>
                <input type="text" name="setting1" value="<?php echo esc_attr($settings['setting1']); ?>">
            </div>
            
            <div class="field">
                <label><?php esc_html_e('Setting 2', 'my-extension'); ?></label>
                <input type="text" name="setting2" value="<?php echo esc_attr($settings['setting2']); ?>">
            </div>
            
            <button class="ui green button" type="submit" name="submit"><?php esc_html_e('Save Settings', 'my-extension'); ?></button>
        </form>
    </div>
    <?php
}

Adding a Custom Tab to Individual Sites

// Add a custom tab to individual site pages
add_filter('mainwp_getsubpages_sites', function($subpages, $website) {
    $subpages[] = array(
        'title' => __('My Extension', 'my-extension'),
        'slug' => 'MyExtension',
        'callback' => array($this, 'render_site_tab'),
    );
    return $subpages;
}, 10, 2);

// Render the custom tab
public function render_site_tab($website) {
    // Render tab content
    ?>
    <div class="ui segment">
        <h2 class="ui header"><?php esc_html_e('My Extension', 'my-extension'); ?></h2>
        <p><?php esc_html_e('Site ID:', 'my-extension'); ?> <?php echo esc_html($website->id); ?></p>
        <!-- Tab content here -->
    </div>
    <?php
}

Adding a Bulk Action

// Add a bulk action to the sites page
add_filter('mainwp_managesites_bulk_actions', function($actions) {
    $actions['my_bulk_action'] = __('My Bulk Action', 'my-extension');
    return $actions;
});

// Handle the bulk action
add_action('mainwp_managesites_bulk_action', function($action, $sites) {
    if ($action == 'my_bulk_action') {
        // Process the bulk action for each site
        foreach ($sites as $site_id) {
            // Process the site
        }
        
        // Redirect back to the sites page
        wp_redirect(admin_url('admin.php?page=managesites&bulk_my_bulk_action=success'));
        exit;
    }
}, 10, 2);

Best Practices for Using Hooks

Use Unique Hook Names

If you need to create your own hooks for your extension, use a unique prefix to avoid conflicts:

do_action('my_extension_custom_hook', $data);
apply_filters('my_extension_custom_filter', $value);

Check Hook Availability

Some hooks may not be available in all versions of MainWP. Always check if a hook exists before using it:

if (has_action('mainwp_hook_name')) {
    // Hook exists, safe to use
}

Use Proper Priority

The priority parameter in add_action() and add_filter() determines the order in which callbacks are executed. Lower numbers run earlier:

// Run early (priority 5)
add_action('mainwp_hook_name', 'early_function', 5);

// Run at default priority (10)
add_action('mainwp_hook_name', 'default_function');

// Run late (priority 20)
add_action('mainwp_hook_name', 'late_function', 20);

Remove Hooks When Necessary

If you need to temporarily disable a hook, you can remove it:

// Remove a hook
remove_action('mainwp_hook_name', 'callback_function');

// Do something without the hook

// Add the hook back
add_action('mainwp_hook_name', 'callback_function');

Use Proper Error Handling

Always include error handling in your hook callbacks:

add_action('mainwp_hook_name', function() {
    try {
        // Do something that might fail
    } catch (Exception $e) {
        // Handle the error
        error_log('My Extension Error: ' . $e->getMessage());
    }
});

Debugging Hooks

Check if a Hook is Firing

To check if a hook is firing, you can add a temporary debug callback:

add_action('mainwp_hook_name', function() {
    error_log('mainwp_hook_name fired at ' . current_time('mysql'));
});

List All Hooks

To see all hooks that are registered, you can use a plugin like Query Monitor or add this code:

add_action('admin_footer', function() {
    global $wp_filter;
    echo '<pre>';
    print_r($wp_filter);
    echo '</pre>';
});

Check Hook Parameters

To see what parameters a hook passes, you can use this debug callback:

add_action('mainwp_hook_name', function() {
    $args = func_get_args();
    error_log('mainwp_hook_name parameters: ' . print_r($args, true));
}, 10, 99); // Accept up to 99 parameters

Finding Available Hooks

MainWP provides many hooks for extension developers. Here are some ways to find them:

MainWP Hooks Documentation

The MainWP Hooks Documentation provides a comprehensive list of available hooks, organized by category.

Source Code

You can search the MainWP source code for hooks:

  1. Look for do_action() and apply_filters() calls in the MainWP Dashboard and Child plugins
  2. Check the MainWP GitHub repositories

Hook Categories

MainWP hooks are generally organized into these categories:

  1. Dashboard UI Hooks: For modifying the MainWP Dashboard interface
  2. Site Management Hooks: For interacting with managed sites
  3. Data Hooks: For modifying data before it's used or displayed
  4. Communication Hooks: For handling communication between Dashboard and Child sites
  5. Extension Hooks: For registering and managing extensions

Complete Hook Reference

For a complete reference of all available hooks, see the MainWP Hooks Documentation:

Hooks for Third-Party Integrations

When building integrations with third-party services or plugins, you'll use the same MainWP hooks as you would for standalone extensions, but with some additional considerations:

API Communication

Integrations typically need to communicate with external APIs. Here's how to use MainWP hooks effectively in this context:

// Sync data from third-party API after site sync
add_action('mainwp_site_synced', function($website, $data) {
    // Get API credentials
    $api_key = get_option('my_integration_api_key', '');
    if (empty($api_key)) {
        return; // No API key configured
    }
    
    // Initialize API client
    $api_client = new ThirdPartyApiClient($api_key);
    
    // Fetch data from third-party API
    try {
        $api_data = $api_client->get_data_for_site($website->url);
        
        // Store the data for this site
        update_option('my_integration_data_' . $website->id, $api_data);
    } catch (Exception $e) {
        // Handle API errors
        error_log('API Error: ' . $e->getMessage());
    }
}, 10, 2);

Error Handling for APIs

Third-party APIs can fail for various reasons. Always implement robust error handling:

add_action('mainwp_dashboard_widgets', function() {
    add_meta_box(
        'my_integration_widget',
        __('Third-Party Integration', 'my-integration'),
        function() {
            // Try to get data from third-party API
            try {
                $api_client = new ThirdPartyApiClient(get_option('my_integration_api_key', ''));
                $data = $api_client->get_dashboard_data();
                
                // Display the data
                ?>
                <div class="ui segment">
                    <h3><?php esc_html_e('API Data', 'my-integration'); ?></h3>
                    <!-- Display API data -->
                </div>
                <?php
            } catch (ApiRateLimitException $e) {
                // Handle rate limiting
                ?>
                <div class="ui yellow message">
                    <?php esc_html_e('API rate limit exceeded. Data will refresh shortly.', 'my-integration'); ?>
                </div>
                <?php
            } catch (ApiAuthException $e) {
                // Handle authentication errors
                ?>
                <div class="ui red message">
                    <?php esc_html_e('API authentication failed. Please check your API credentials.', 'my-integration'); ?>
                </div>
                <?php
            } catch (Exception $e) {
                // Handle general errors
                ?>
                <div class="ui red message">
                    <?php esc_html_e('Error connecting to API. Please try again later.', 'my-integration'); ?>
                </div>
                <?php
            }
        },
        'mainwp_dashboard',
        'normal',
        'default'
    );
});

Caching API Responses

To avoid hitting API rate limits, use WordPress transients to cache API responses:

add_action('mainwp_site_synced', function($website, $data) {
    // Check if we need to refresh the cache
    $cached_data = get_transient('my_integration_api_data_' . $website->id);
    if (false !== $cached_data) {
        return; // Use cached data
    }
    
    // Get fresh data from API
    $api_client = new ThirdPartyApiClient(get_option('my_integration_api_key', ''));
    try {
        $api_data = $api_client->get_data_for_site($website->url);
        
        // Cache the data for 1 hour
        set_transient('my_integration_api_data_' . $website->id, $api_data, HOUR_IN_SECONDS);
    } catch (Exception $e) {
        // Log error but don't cache failures
        error_log('API Error: ' . $e->getMessage());
    }
}, 10, 2);

Handling API Credentials

Securely store and manage API credentials:

// Add a settings page for API credentials
add_action('mainwp_admin_menu', function() {
    add_submenu_page(
        'mainwp_tab',
        __('API Settings', 'my-integration'),
        __('API Settings', 'my-integration'),
        'read',
        'MyIntegrationApiSettings',
        array($this, 'render_api_settings_page')
    );
});

// Render the API settings page
public function render_api_settings_page() {
    // Check if user has permissions
    if (!mainwp_current_user_can('read')) {
        mainwp_do_not_have_permissions(__('API Settings', 'my-integration'));
        return;
    }
    
    // Save settings if form is submitted
    if (isset($_POST['submit'])) {
        // Verify nonce
        check_admin_referer('my_integration_api_settings_nonce', 'my_integration_api_settings_nonce');
        
        // Save API credentials
        $api_key = sanitize_text_field($_POST['api_key'] ?? '');
        update_option('my_integration_api_key', $api_key);
        
        // Test API connection
        if (!empty($api_key)) {
            try {
                $api_client = new ThirdPartyApiClient($api_key);
                $test_result = $api_client->test_connection();
                
                if ($test_result) {
                    ?>
                    <div class="ui green message"><?php esc_html_e('API connection successful.', 'my-integration'); ?></div>
                    <?php
                } else {
                    ?>
                    <div class="ui red message"><?php esc_html_e('API connection failed.', 'my-integration'); ?></div>
                    <?php
                }
            } catch (Exception $e) {
                ?>
                <div class="ui red message"><?php echo esc_html('API Error: ' . $e->getMessage()); ?></div>
                <?php
            }
        }
    }
    
    // Get current settings
    $api_key = get_option('my_integration_api_key', '');
    
    // Render settings form
    ?>
    <div class="ui segment">
        <h2 class="ui header"><?php esc_html_e('API Settings', 'my-integration'); ?></h2>
        <form class="ui form" method="post" action="">
            <?php wp_nonce_field('my_integration_api_settings_nonce', 'my_integration_api_settings_nonce'); ?>
            
            <div class="field">
                <label><?php esc_html_e('API Key', 'my-integration'); ?></label>
                <input type="password" name="api_key" value="<?php echo esc_attr($api_key); ?>">
                <p class="description"><?php esc_html_e('Enter your API key from your Third-Party Service account.', 'my-integration'); ?></p>
            </div>
            
            <button class="ui green button" type="submit" name="submit"><?php esc_html_e('Save API Settings', 'my-integration'); ?></button>
        </form>
    </div>
    <?php
}

Next Steps

Now that you understand how to use MainWP actions and filters, you can:

Building Admin Interfaces for MainWP Extensions

This guide is a placeholder and will be expanded in the future.

This guide will cover best practices for building intuitive admin interfaces for MainWP extensions, including:

Check back soon for the complete guide.

Creating a Basic MainWP Extension

This guide walks you through the process of creating a basic MainWP extension. By the end, you'll have a functional extension that integrates with the MainWP Dashboard.

Quick Start for Experienced Developers

// 1. Clone the development extension
// git clone https://github.com/mainwp/mainwp-development-extension.git my-extension

// 2. Rename files and update plugin header
// mainwp-development-extension.php → my-extension.php

// 3. Update namespace and class names
namespace MyCompany\MyExtension;

// 4. Register your extension with MainWP
add_filter('mainwp_getextensions', function($extensions) {
    $extensions[] = array(
        'plugin' => __FILE__,
        'api' => 'MyExtension',
        'mainwp' => true,
        'callback' => array(__NAMESPACE__ . '\\MyExtension', 'get_instance'),
        'name' => 'My Extension'
    );
    return $extensions;
});

// 5. Implement core functionality in your main class
class MyExtension {
    private static $instance = null;
    
    public static function get_instance() {
        if (null === self::$instance) {
            self::$instance = new self();
        }
        return self::$instance;
    }
    
    public function __construct() {
        // Hook into MainWP actions and filters
        add_action('mainwp_pageheader_extensions', [$this, 'page_header']);
        add_action('mainwp_pagefooter_extensions', [$this, 'page_footer']);
    }
    
    // Implement required methods...
}

Prerequisites

Before you begin creating a MainWP extension, ensure you have:

Step 1: Plan Your Extension

Before writing any code, clearly define what your extension will do:

  1. Purpose: What problem will your extension solve for MainWP users?
  2. Features: What specific functionality will it provide?
  3. Integration Points: How will it interact with the MainWP Dashboard and child sites?
  4. User Interface: What admin pages, settings, or widgets will it need?

Having a clear plan will guide your development process and help you create a more focused, useful extension.

Step 2: Start with the Development Extension

MainWP provides a development extension template that includes the basic structure and boilerplate code you need to get started quickly.

  1. Clone or download the MainWP Development Extension from GitHub:

    git clone https://github.com/mainwp/mainwp-development-extension.git my-extension
    
  2. Navigate to your new extension directory:

    cd my-extension
    

The development extension includes a well-structured codebase with classes for different aspects of extension functionality, making it easier to understand and extend.

Step 3: Customize the Plugin Information

  1. Rename the main plugin file from mainwp-development-extension.php to match your extension name (e.g., my-extension.php).

  2. Update the plugin header information in the main file:

    /**
     * Plugin Name: My Extension for MainWP
     * Plugin URI: https://yourwebsite.com/my-extension
     * Description: A brief description of what your extension does.
     * Version: 1.0
     * Author: Your Name
     * Author URI: https://yourwebsite.com
     * Text Domain: my-extension
     */
    
  3. Update the text domain throughout the plugin to match your extension's slug.

Step 4: Update Namespace and Class Names

  1. Change the namespace in all PHP files from MainWP\Dev to your own namespace (e.g., MyCompany\MyExtension).

  2. Rename the main class and other classes to match your extension name.

  3. Update all references to these classes throughout the codebase.

For example:

namespace MyCompany\MyExtension;

class MyExtension {
    // Class implementation
}

Step 5: Register Your Extension with MainWP

MainWP uses a filter to discover and load extensions. Update the mainwp_getextensions filter in your main plugin file:

namespace MyCompany\MyExtension;

add_filter('mainwp_getextensions', function($extensions) {
    $extensions[] = array(
        'plugin' => __FILE__,
        'api' => 'MyExtension',
        'mainwp' => true,
        'callback' => array(__NAMESPACE__ . '\\MyExtension', 'get_instance'),
        'name' => 'My Extension'
    );
    return $extensions;
});

This tells MainWP:

Step 6: Implement Core Functionality

The development extension includes several classes that handle different aspects of extension functionality:

  1. Main Class (class/class-mainwp-development-extension.php): The core class that initializes your extension and hooks into MainWP.

  2. Admin Class (class/class-mainwp-development-extension-admin.php): Handles admin pages, settings, and UI elements.

  3. Database Class (class/class-mainwp-development-extension-db.php): Manages database operations for your extension.

  4. Utility Class (class/class-mainwp-development-extension-utility.php): Contains helper functions and utilities.

  5. AJAX Class (class/class-mainwp-development-extension-ajax.php): Handles AJAX requests for your extension.

Modify these classes to implement your extension's specific functionality. The development extension includes examples and comments to guide you.

Step 7: Create Admin Pages

Most MainWP extensions provide admin pages for configuration and displaying information. The development extension includes examples of how to create these pages.

  1. Register your admin pages in the Admin class:
public function init_menu() {
    add_submenu_page(
        'mainwp_tab',
        __('My Extension', 'my-extension'),
        __('My Extension', 'my-extension'),
        'read',
        'MyExtension',
        array($this, 'render_admin_page')
    );
}
  1. Create the page rendering function:
public function render_admin_page() {
    // Check if user has permissions
    if (!mainwp_current_user_can('read')) {
        mainwp_do_not_have_permissions(__('My Extension', 'my-extension'));
        return;
    }
    
    // Render your admin page content
    ?>
    <div class="ui segment">
        <h2 class="ui header"><?php esc_html_e('My Extension', 'my-extension'); ?></h2>
        <!-- Your admin page content here -->
    </div>
    <?php
}

Step 8: Interact with Child Sites

If your extension needs to interact with child sites, you can use MainWP's communication system:

  1. Dashboard to Child: Send requests from the dashboard to child sites.
// Example of sending a request to a child site
use MainWP\Dashboard\MainWP_DB;
use MainWP\Dashboard\MainWP_Connect;

$data = array('action' => 'my_custom_action', 'param' => 'value');
$website = MainWP_DB::instance()->get_website_by_id($website_id);
$information = MainWP_Connect::fetch_url_authed($website, 'my_extension', $data);
  1. Child to Dashboard: Process requests on the child site.

You'll need to create a separate child plugin or use the MainWP Child plugin's hooks to handle these requests.

Step 9: Test Your Extension

Thoroughly test your extension to ensure it works correctly:

  1. Install and activate it on a test MainWP Dashboard.
  2. Test all features and functionality.
  3. Check for any errors or warnings in the browser console and PHP error logs.
  4. Test with multiple child sites to ensure compatibility.

Step 10: Package Your Extension

When your extension is ready for distribution:

  1. Remove any development or debugging code.
  2. Ensure all text is properly internationalized.
  3. Create a readme.txt file with installation and usage instructions.
  4. Package your extension as a ZIP file for distribution.

Common Integration Points

Here are some common MainWP hooks you might use in your extension:

Actions

Filters

For a complete list of available hooks, see the MainWP Hooks Reference.

Example: A Simple "Hello World" Extension

Here's a minimal example of a MainWP extension that adds a new admin page:

<?php
/**
 * Plugin Name: Hello World for MainWP
 * Plugin URI: https://example.com/hello-world
 * Description: A simple Hello World extension for MainWP.
 * Version: 1.0
 * Author: Your Name
 * Author URI: https://example.com
 * Text Domain: hello-world-mainwp
 */

namespace MyCompany\HelloWorld;

// Register the extension with MainWP
add_filter('mainwp_getextensions', function($extensions) {
    $extensions[] = array(
        'plugin' => __FILE__,
        'api' => 'HelloWorld',
        'mainwp' => true,
        'callback' => array(__NAMESPACE__ . '\\HelloWorld', 'get_instance'),
        'name' => 'Hello World'
    );
    return $extensions;
});

class HelloWorld {
    private static $instance = null;
    
    public static function get_instance() {
        if (null === self::$instance) {
            self::$instance = new self();
        }
        return self::$instance;
    }
    
    public function __construct() {
        add_action('admin_init', [$this, 'admin_init']);
        add_action('mainwp_admin_menu', [$this, 'init_menu']);
    }
    
    public function admin_init() {
        // Initialize admin functionality
    }
    
    public function init_menu() {
        // Add submenu page to MainWP menu
        add_submenu_page(
            'mainwp_tab',
            __('Hello World', 'hello-world-mainwp'),
            __('Hello World', 'hello-world-mainwp'),
            'read',
            'HelloWorld',
            [$this, 'render_admin_page']
        );
    }
    
    public function render_admin_page() {
        // Check if user has permissions
        if (!mainwp_current_user_can('read')) {
            mainwp_do_not_have_permissions(__('Hello World', 'hello-world-mainwp'));
            return;
        }
        
        // Render admin page
        ?>
        <div class="ui segment">
            <h2 class="ui header"><?php esc_html_e('Hello World Extension', 'hello-world-mainwp'); ?></h2>
            <p><?php esc_html_e('Welcome to your first MainWP extension!', 'hello-world-mainwp'); ?></p>
        </div>
        <?php
    }
}

Next Steps

After creating your basic extension, you might want to explore:

Creating a Basic MainWP Integration

This guide walks you through the process of creating a basic MainWP integration that connects with a third-party plugin or API. By the end, you'll have a functional integration that extends MainWP with third-party capabilities.

Quick Start for Experienced Developers

// 1. Clone the development extension
// git clone https://github.com/mainwp/mainwp-development-extension.git my-integration

// 2. Rename files and update plugin header
// mainwp-development-extension.php → my-integration.php

// 3. Update namespace and class names
namespace MyCompany\MyIntegration;

// 4. Register your integration with MainWP
add_filter('mainwp_getextensions', function($extensions) {
    $extensions[] = [
        'plugin' => __FILE__,
        'api' => 'MyIntegration',
        'mainwp' => true,
        'callback' => [__NAMESPACE__ . '\\MyIntegration', 'get_instance'],
        'name' => 'My Integration'
    ];
    return $extensions;
});

// 5. Implement core functionality in your main class
class MyIntegration {
    private static $instance = null;
    private $api_key;
    private $api_client;
    
    public static function get_instance() {
        if (null === self::$instance) {
            self::$instance = new self();
        }
        return self::$instance;
    }
    
    public function __construct() {
        // Hook into MainWP actions and filters
        add_action('mainwp_pageheader_extensions', [$this, 'page_header']);
        add_action('mainwp_pagefooter_extensions', [$this, 'page_footer']);
        
        // Initialize third-party API connection
        $this->init_api_connection();
    }
    
    private function init_api_connection() {
        // Initialize connection to third-party API
        // Check if required third-party plugin is active
        if ($this->is_third_party_plugin_active()) {
            // Set up API credentials
            $this->api_key = get_option('my_integration_api_key', '');
            // Initialize API client
            $this->api_client = new ApiClient($this->api_key);
        }
    }
    
    private function is_third_party_plugin_active() {
        // Check if the required third-party plugin is active
        return is_plugin_active('third-party-plugin/third-party-plugin.php');
    }
    
    // Implement required methods...
}

Prerequisites

Before you begin creating a MainWP integration, ensure you have:

Step 1: Plan Your Integration

Before writing any code, clearly define what your integration will do:

  1. Purpose: What problem will your integration solve for MainWP users?
  2. Features: What specific functionality will it provide?
  3. Third-Party Requirements: What API or plugin will you integrate with?
  4. Integration Points: How will it interact with both MainWP and the third-party service?
  5. User Interface: What admin pages, settings, or widgets will it need?

Having a clear plan will guide your development process and help you create a more focused, useful integration.

Step 2: Start with the Development Extension

MainWP provides a development extension template that includes the basic structure and boilerplate code you need to get started quickly.

  1. Clone or download the MainWP Development Extension from GitHub:

    git clone https://github.com/mainwp/mainwp-development-extension.git my-integration
    
  2. Navigate to your new integration directory:

    cd my-integration
    

The development extension includes a well-structured codebase with classes for different aspects of extension functionality, making it easier to understand and extend.

Step 3: Customize the Plugin Information

  1. Rename the main plugin file from mainwp-development-extension.php to match your integration name (e.g., my-integration.php).

  2. Update the plugin header information in the main file:

    /**
     * Plugin Name: My Integration for MainWP
     * Plugin URI: https://yourwebsite.com/my-integration
     * Description: Integrates MainWP with [Third-Party Service/Plugin].
     * Version: 1.0
     * Author: Your Name
     * Author URI: https://yourwebsite.com
     * Text Domain: my-integration
     * Requires at least: 4.6
     * Requires PHP: 7.0
     * WC requires at least: 3.0 (if integrating with WooCommerce)
     * WC tested up to: 6.0 (if integrating with WooCommerce)
     */
    
  3. Update the text domain throughout the plugin to match your integration's slug.

Step 4: Update Namespace and Class Names

  1. Change the namespace in all PHP files from MainWP\Dev to your own namespace (e.g., MyCompany\MyIntegration).

  2. Rename the main class and other classes to match your integration name.

  3. Update all references to these classes throughout the codebase.

For example:

namespace MyCompany\MyIntegration;

class MyIntegration {
    // Class implementation
}

Step 5: Register Your Integration with MainWP

MainWP uses a filter to discover and load extensions. Update the mainwp_getextensions filter in your main plugin file:

namespace MyCompany\MyIntegration;

add_filter('mainwp_getextensions', function($extensions) {
    $extensions[] = [
        'plugin' => __FILE__,
        'api' => 'MyIntegration',
        'mainwp' => true,
        'callback' => [__NAMESPACE__ . '\\MyIntegration', 'get_instance'],
        'name' => 'My Integration'
    ];
    return $extensions;
});

This tells MainWP:

Step 6: Add Third-Party Integration Code

This is where your integration differs from a standard extension. You'll need to add code to connect with the third-party API or plugin:

  1. Check for Third-Party Plugin: If your integration requires a specific plugin, check if it's installed and activated:
private function is_third_party_plugin_active() {
    // Check if the required third-party plugin is active
    if (!function_exists('is_plugin_active')) {
        include_once(ABSPATH . 'wp-admin/includes/plugin.php');
    }
    return is_plugin_active('third-party-plugin/third-party-plugin.php');
}
  1. Initialize API Connection: Set up the connection to the third-party API:
private function init_api_connection() {
    // Initialize connection to third-party API
    if ($this->is_third_party_plugin_active()) {
        // Set up API credentials
        $this->api_key = get_option('my_integration_api_key', '');
        // Initialize API client
        $this->api_client = new ApiClient($this->api_key);
    }
}
  1. Create API Client Class: Create a dedicated class for handling API communication:
namespace MyCompany\MyIntegration;

class ApiClient {
    private $api_key;
    private $api_url = 'https://api.third-party-service.com/v1/';
    
    public function __construct($api_key) {
        $this->api_key = $api_key;
    }
    
    public function get_data($endpoint, $params = []) {
        $url = $this->api_url . $endpoint;
        $args = [
            'headers' => [
                'Authorization' => 'Bearer ' . $this->api_key,
                'Content-Type' => 'application/json'
            ],
            'timeout' => 30
        ];
        
        if (!empty($params)) {
            $url = add_query_arg($params, $url);
        }
        
        $response = wp_remote_get($url, $args);
        
        if (is_wp_error($response)) {
            return ['error' => $response->get_error_message()];
        }
        
        $body = wp_remote_retrieve_body($response);
        return json_decode($body, true);
    }
    
    public function post_data($endpoint, $data) {
        $url = $this->api_url . $endpoint;
        $args = [
            'headers' => [
                'Authorization' => 'Bearer ' . $this->api_key,
                'Content-Type' => 'application/json'
            ],
            'body' => json_encode($data),
            'method' => 'POST',
            'timeout' => 30
        ];
        
        $response = wp_remote_post($url, $args);
        
        if (is_wp_error($response)) {
            return ['error' => $response->get_error_message()];
        }
        
        $body = wp_remote_retrieve_body($response);
        return json_decode($body, true);
    }
}

Step 7: Create Settings Page for API Credentials

Most integrations require API credentials or configuration settings:

namespace MyCompany\MyIntegration\Admin;

class Settings {
    public function render_settings_page() {
        // Check if user has permissions
        if (!mainwp_current_user_can('read')) {
            mainwp_do_not_have_permissions(__('My Integration Settings', 'my-integration'));
            return;
        }
        
        // Save settings if form is submitted
        if (isset($_POST['submit'])) {
            // Verify nonce
            check_admin_referer('my_integration_settings_nonce', 'my_integration_settings_nonce');
            
            // Save API key
            $api_key = sanitize_text_field($_POST['api_key'] ?? '');
            update_option('my_integration_api_key', $api_key);
            
            // Test API connection
            $test_result = $this->test_api_connection($api_key);
            
            if ($test_result['success']) {
                ?>
                <div class="ui green message"><?php esc_html_e('API connection successful.', 'my-integration'); ?></div>
                <?php
            } else {
                ?>
                <div class="ui red message"><?php echo esc_html($test_result['message']); ?></div>
                <?php
            }
        }
        
        // Get current settings
        $api_key = get_option('my_integration_api_key', '');
        
        // Render settings form
        ?>
        <div class="ui segment">
            <h2 class="ui header"><?php esc_html_e('My Integration Settings', 'my-integration'); ?></h2>
            <form class="ui form" method="post" action="">
                <?php wp_nonce_field('my_integration_settings_nonce', 'my_integration_settings_nonce'); ?>
                
                <div class="field">
                    <label><?php esc_html_e('API Key', 'my-integration'); ?></label>
                    <input type="text" name="api_key" value="<?php echo esc_attr($api_key); ?>">
                    <p class="description"><?php esc_html_e('Enter your API key from your Third-Party Service account.', 'my-integration'); ?></p>
                </div>
                
                <button class="ui green button" type="submit" name="submit"><?php esc_html_e('Save Settings', 'my-integration'); ?></button>
            </form>
        </div>
        <?php
    }

    private function test_api_connection($api_key) {
        // Create temporary API client with the provided key
        $api_client = new ApiClient($api_key);
        
        // Try to make a simple API request
        $response = $api_client->get_data('test-endpoint');
        
        if (isset($response['error'])) {
            return [
                'success' => false,
                'message' => sprintf(__('API connection failed: %s', 'my-integration'), $response['error'])
            ];
        }
        
        return [
            'success' => true,
            'message' => __('API connection successful.', 'my-integration')
        ];
    }
}

Step 8: Implement Integration Features

Now implement the specific features of your integration. This will depend on what your integration does, but here are some common patterns:

Syncing Data from Third-Party Service to MainWP

namespace MyCompany\MyIntegration\Features;

class DataSync {
    public function sync_third_party_data() {
        // Get data from third-party API
        $data = $this->api_client->get_data('some-endpoint');
        
        if (isset($data['error'])) {
            // Handle error
            return;
        }
        
        // Process and store the data
        update_option('my_integration_synced_data', $data);
        update_option('my_integration_last_sync', current_time('mysql'));
    }
}

Sending MainWP Data to Third-Party Service

namespace MyCompany\MyIntegration\Features;

use MainWP\Dashboard\MainWP_DB;

class DataTransfer {
    public function send_data_to_third_party($website_id, $data) {
        // Get website details
        $website = MainWP_DB::instance()->get_website_by_id($website_id);
        if (!$website) {
            return ['error' => 'Website not found'];
        }
        
        // Prepare data for third-party service
        $prepared_data = [
            'website_name' => $website->name,
            'website_url' => $website->url,
            'custom_data' => $data
        ];
        
        // Send data to third-party API
        $response = $this->api_client->post_data('some-endpoint', $prepared_data);
        
        return $response;
    }
}

Adding Integration-Specific UI Elements

namespace MyCompany\MyIntegration\UI;

class Dashboard {
    public function render_dashboard_widget() {
        // Get synced data
        $data = get_option('my_integration_synced_data', []);
        $last_sync = get_option('my_integration_last_sync', '');
        
        ?>
        <div class="ui segment">
            <h3 class="ui header"><?php esc_html_e('Third-Party Service Data', 'my-integration'); ?></h3>
            
            <?php if (!empty($last_sync)) : ?>
                <p><?php echo sprintf(__('Last synced: %s', 'my-integration'), $last_sync); ?></p>
            <?php endif; ?>
            
            <?php if (!empty($data)) : ?>
                <!-- Display data here -->
                <div class="ui list">
                    <?php foreach ($data as $item) : ?>
                        <div class="item">
                            <?php echo esc_html($item['name']); ?>
                        </div>
                    <?php endforeach; ?>
                </div>
            <?php else : ?>
                <p><?php esc_html_e('No data available. Please sync with the third-party service.', 'my-integration'); ?></p>
            <?php endif; ?>
            
            <button class="ui green button sync-button" data-nonce="<?php echo wp_create_nonce('my_integration_sync_nonce'); ?>">
                <?php esc_html_e('Sync Now', 'my-integration'); ?>
            </button>
        </div>
        
        <script type="text/javascript">
        jQuery(document).ready(function($) {
            $('.sync-button').on('click', function() {
                var button = $(this);
                button.addClass('loading');
                
                $.ajax({
                    url: ajaxurl,
                    type: 'POST',
                    data: {
                        action: 'my_integration_sync',
                        nonce: button.data('nonce')
                    },
                    success: function(response) {
                        button.removeClass('loading');
                        if (response.success) {
                            location.reload();
                        } else {
                            alert(response.data.message);
                        }
                    },
                    error: function() {
                        button.removeClass('loading');
                        alert('An error occurred. Please try again.');
                    }
                });
            });
        });
        </script>
        <?php
    }
}

Step 9: Handle Dependency Management

Your integration may depend on third-party libraries or plugins. Here's how to handle these dependencies:

Check for Required Plugins

namespace MyCompany\MyIntegration\Dependencies;

class PluginChecker {
    public function check_required_plugins() {
        if (!$this->is_third_party_plugin_active()) {
            add_action('admin_notices', function() {
                ?>
                <div class="notice notice-error">
                    <p><?php echo sprintf(__('My Integration requires the %s plugin to be installed and activated.', 'my-integration'), '<a href="https://example.com/third-party-plugin" target="_blank">Third-Party Plugin</a>'); ?></p>
                </div>
                <?php
            });
            return false;
        }
        return true;
    }
}

Include Third-Party Libraries

If your integration requires PHP libraries, you can use Composer to manage them:

  1. Create a composer.json file in your integration directory:
{
    "name": "mycompany/my-integration",
    "description": "MainWP integration with Third-Party Service",
    "type": "wordpress-plugin",
    "require": {
        "php": ">=7.0",
        "third-party/api-client": "^1.0"
    },
    "autoload": {
        "psr-4": {
            "MyCompany\\MyIntegration\\": "src/"
        }
    }
}
  1. Install dependencies:
composer install --no-dev
  1. Include the Composer autoloader in your main plugin file:
if (file_exists(__DIR__ . '/vendor/autoload.php')) {
    require_once __DIR__ . '/vendor/autoload.php';
}

Step 10: Test Your Integration

Thoroughly test your integration to ensure it works correctly:

  1. Install and activate it on a test MainWP Dashboard.
  2. Configure the API credentials or third-party plugin settings.
  3. Test all features and functionality.
  4. Check for any errors or warnings in the browser console and PHP error logs.
  5. Test with multiple child sites to ensure compatibility.
  6. Verify that data is correctly synced between MainWP and the third-party service.

Step 11: Package Your Integration

When your integration is ready for distribution:

  1. Remove any development or debugging code.
  2. Ensure all text is properly internationalized.
  3. Create a readme.txt file with installation and usage instructions.
  4. Package your integration as a ZIP file for distribution.

Example: A Simple "Third-Party Service" Integration

Here's a minimal example of a MainWP integration that connects with a third-party service:

<?php
/**
 * Plugin Name: Third-Party Service Integration for MainWP
 * Plugin URI: https://example.com/third-party-integration
 * Description: Integrates MainWP with Third-Party Service.
 * Version: 1.0
 * Author: Your Name
 * Author URI: https://example.com
 * Text Domain: third-party-integration
 */

namespace MyCompany\ThirdPartyIntegration;

// Register the integration with MainWP
add_filter('mainwp_getextensions', function($extensions) {
    $extensions[] = [
        'plugin' => __FILE__,
        'api' => 'ThirdPartyIntegration',
        'mainwp' => true,
        'callback' => [__NAMESPACE__ . '\\ThirdPartyIntegration', 'get_instance'],
        'name' => 'Third-Party Service Integration'
    ];
    return $extensions;
});

class ThirdPartyIntegration {
    private static $instance = null;
    private $api_client = null;
    
    public static function get_instance() {
        if (null === self::$instance) {
            self::$instance = new self();
        }
        return self::$instance;
    }
    
    public function __construct() {
        // Initialize the integration
        add_action('admin_init', [$this, 'admin_init']);
        add_action('mainwp_admin_menu', [$this, 'init_menu']);
        
        // Initialize API client
        $this->init_api_client();
    }
    
    public function admin_init() {
        // Check for required dependencies
        $this->check_required_plugins();
    }
    
    public function init_menu() {
        // Add submenu page to MainWP menu
        add_submenu_page(
            'mainwp_tab',
            __('Third-Party Service', 'third-party-integration'),
            __('Third-Party Service', 'third-party-integration'),
            'read',
            'ThirdPartyIntegration',
            [$this, 'render_admin_page']
        );
        
        // Add settings page
        add_submenu_page(
            'mainwp_tab',
            __('Third-Party Settings', 'third-party-integration'),
            __('Third-Party Settings', 'third-party-integration'),
            'read',
            'ThirdPartyIntegrationSettings',
            [$this, 'render_settings_page']
        );
    }
    
    private function init_api_client() {
        $api_key = get_option('third_party_integration_api_key', '');
        if (!empty($api_key)) {
            $this->api_client = new ApiClient($api_key);
        }
    }
    
    private function check_required_plugins() {
        if (!function_exists('is_plugin_active')) {
            include_once(ABSPATH . 'wp-admin/includes/plugin.php');
        }
        
        if (!is_plugin_active('third-party-plugin/third-party-plugin.php')) {
            add_action('admin_notices', function() {
                ?>
                <div class="notice notice-error">
                    <p><?php echo sprintf(__('Third-Party Service Integration requires the %s plugin to be installed and activated.', 'third-party-integration'), '<a href="https://example.com/third-party-plugin" target="_blank">Third-Party Plugin</a>'); ?></p>
                </div>
                <?php
            });
        }
    }
    
    public function render_admin_page() {
        // Check if user has permissions
        if (!mainwp_current_user_can('read')) {
            mainwp_do_not_have_permissions(__('Third-Party Service', 'third-party-integration'));
            return;
        }
        
        // Check if API key is set
        if (empty($this->api_client)) {
            ?>
            <div class="ui segment">
                <div class="ui yellow message">
                    <?php echo sprintf(__('Please configure your API key in the %s settings page.', 'third-party-integration'), '<a href="admin.php?page=ThirdPartyIntegrationSettings">Third-Party Service</a>'); ?>
                </div>
            </div>
            <?php
            return;
        }
        
        // Render admin page
        ?>
        <div class="ui segment">
            <h2 class="ui header"><?php esc_html_e('Third-Party Service Integration', 'third-party-integration'); ?></h2>
            
            <!-- Your integration UI here -->
            
        </div>
        <?php
    }
    
    public function render_settings_page() {
        // Check if user has permissions
        if (!mainwp_current_user_can('read')) {
            mainwp_do_not_have_permissions(__('Third-Party Settings', 'third-party-integration'));
            return;
        }
        
        // Save settings if form is submitted
        if (isset($_POST['submit'])) {
            // Verify nonce
            check_admin_referer('third_party_settings_nonce', 'third_party_settings_nonce');
            
            // Save API key
            $api_key = sanitize_text_field($_POST['api_key'] ?? '');
            update_option('third_party_integration_api_key', $api_key);
            
            // Reinitialize API client
            $this->init_api_client();
            
            ?>
            <div class="ui green message"><?php esc_html_e('Settings saved successfully.', 'third-party-integration'); ?></div>
            <?php
        }
        
        // Get current settings
        $api_key = get_option('third_party_integration_api_key', '');
        
        // Render settings form
        ?>
        <div class="ui segment">
            <h2 class="ui header"><?php esc_html_e('Third-Party Service Settings', 'third-party-integration'); ?></h2>
            <form class="ui form" method="post" action="">
                <?php wp_nonce_field('third_party_settings_nonce', 'third_party_settings_nonce'); ?>
                
                <div class="field">
                    <label><?php esc_html_e('API Key', 'third-party-integration'); ?></label>
                    <input type="text" name="api_key" value="<?php echo esc_attr($api_key); ?>">
                    <p class="description"><?php esc_html_e('Enter your API key from your Third-Party Service account.', 'third-party-integration'); ?></p>
                </div>
                
                <button class="ui green button" type="submit" name="submit"><?php esc_html_e('Save Settings', 'third-party-integration'); ?></button>
            </form>
        </div>
        <?php
    }
}

class ApiClient {
    private $api_key;
    private $api_url = 'https://api.third-party-service.com/v1/';
    
    public function __construct($api_key) {
        $this->api_key = $api_key;
    }
    
    public function get_data($endpoint, $params = []) {
        $url = $this->api_url . $endpoint;
        $args = [
            'headers' => [
                'Authorization' => 'Bearer ' . $this->api_key,
                'Content-Type' => 'application/json'
            ],
            'timeout' => 30
        ];
        
        if (!empty($params)) {
            $url = add_query_arg($params, $url);
        }
        
        $response = wp_remote_get($url, $args);
        
        if (is_wp_error($response)) {
            return ['error' => $response->get_error_message()];
        }
        
        $body = wp_remote_retrieve_body($response);
        return json_decode($body, true);
    }
    
    public function post_data($endpoint, $data) {
        $url = $this->api_url . $endpoint;
        $args = [
            'headers' => [
                'Authorization' => 'Bearer ' . $this->api_key,
                'Content-Type' => 'application/json'
            ],
            'body' => json_encode($data),
            'method' => 'POST',
            'timeout' => 30
        ];
        
        $response = wp_remote_post($url, $args);
        
        if (is_wp_error($response)) {
            return ['error' => $response->get_error_message()];
        }
        
        $body = wp_remote_retrieve_body($response);
        return json_decode($body, true);
    }
}

Next Steps

After creating your basic integration, you might want to explore:

Data Storage and Retrieval in MainWP Extensions

This guide is a placeholder and will be expanded in the future.

This guide will cover best practices for storing and retrieving data in MainWP extensions, including:

Check back soon for the complete guide.

Debugging MainWP Extensions

This guide is a placeholder and will be expanded in the future.

This guide will cover techniques and best practices for debugging MainWP extensions, including:

Check back soon for the complete guide.

Using the MainWP API Client PHP

This guide covers how to use the official MainWP API Client PHP library to interact with the MainWP Dashboard API. This client library simplifies the process of connecting to and working with MainWP-managed sites programmatically.

Quick Start for Experienced Developers

// Install via Composer
// composer require mainwp/mainwp-api-client-php

// Initialize the client
require_once 'vendor/autoload.php';

use MainWP\ApiClient\MainWP;

// Create a new client instance
$mainwp = new MainWP([
    'baseUrl' => 'https://your-mainwp-dashboard.com',
    'apiKey' => 'your-api-key',
    'apiSecret' => 'your-api-secret'
]);

// Get all managed sites
$sites = $mainwp->getSites();

// Get a specific site by ID
$site = $mainwp->getSite(123);

// Update a site
$mainwp->updateSite(123, [
    'name' => 'Updated Site Name'
]);

// Execute an action on a site
$result = $mainwp->executeAction(123, 'core_update');

// Error handling
try {
    $sites = $mainwp->getSites();
} catch (\MainWP\ApiClient\Exception\ApiException $e) {
    echo 'API Error: ' . $e->getMessage();
} catch (\MainWP\ApiClient\Exception\AuthenticationException $e) {
    echo 'Authentication Error: ' . $e->getMessage();
} catch (\Exception $e) {
    echo 'Error: ' . $e->getMessage();
}

Installation

The MainWP API Client PHP library can be installed via Composer:

composer require mainwp/mainwp-api-client-php

If you're not using Composer, you can download the library from GitHub and include it manually.

Configuration

Before using the API client, you need to set up API access in your MainWP Dashboard:

  1. Go to your MainWP Dashboard
  2. Navigate to Settings > API
  3. Enable the API
  4. Create a new API key and secret
  5. Note the API key and secret for use with the client

Then, initialize the client with your MainWP Dashboard URL, API key, and API secret:

require_once 'vendor/autoload.php';

use MainWP\ApiClient\MainWP;

$mainwp = new MainWP([
    'baseUrl' => 'https://your-mainwp-dashboard.com',
    'apiKey' => 'your-api-key',
    'apiSecret' => 'your-api-secret'
]);

Working with Sites

The MainWP API Client provides several methods for working with managed sites:

Get All Sites

// Get all sites
$sites = $mainwp->getSites();

// Get sites with specific tags
$sites = $mainwp->getSites(['tags' => ['production', 'client-a']]);

// Get sites with pagination
$sites = $mainwp->getSites(['page' => 1, 'per_page' => 10]);

Get a Specific Site

// Get site by ID
$site = $mainwp->getSite(123);

// Get site by URL
$site = $mainwp->getSiteByUrl('https://example.com');

Create a Site

$site = $mainwp->createSite([
    'url' => 'https://example.com',
    'name' => 'Example Site',
    'adminname' => 'admin',
    'adminpass' => 'password',
    'tags' => ['client-a', 'production']
]);

Update a Site

$mainwp->updateSite(123, [
    'name' => 'Updated Site Name',
    'tags' => ['client-a', 'staging']
]);

Delete a Site

$mainwp->deleteSite(123);

Executing Actions on Sites

You can execute various actions on managed sites:

Core Updates

// Update WordPress core
$result = $mainwp->executeAction(123, 'core_update');

Plugin Management

// Install a plugin
$result = $mainwp->executeAction(123, 'plugin_install', [
    'plugin' => 'akismet'
]);

// Activate a plugin
$result = $mainwp->executeAction(123, 'plugin_activate', [
    'plugin' => 'akismet/akismet.php'
]);

// Update a plugin
$result = $mainwp->executeAction(123, 'plugin_update', [
    'plugin' => 'akismet/akismet.php'
]);

// Deactivate a plugin
$result = $mainwp->executeAction(123, 'plugin_deactivate', [
    'plugin' => 'akismet/akismet.php'
]);

// Delete a plugin
$result = $mainwp->executeAction(123, 'plugin_delete', [
    'plugin' => 'akismet/akismet.php'
]);

Theme Management

// Install a theme
$result = $mainwp->executeAction(123, 'theme_install', [
    'theme' => 'twentytwentyone'
]);

// Activate a theme
$result = $mainwp->executeAction(123, 'theme_activate', [
    'theme' => 'twentytwentyone'
]);

// Update a theme
$result = $mainwp->executeAction(123, 'theme_update', [
    'theme' => 'twentytwentyone'
]);

// Delete a theme
$result = $mainwp->executeAction(123, 'theme_delete', [
    'theme' => 'twentytwentyone'
]);

Backup Management

// Create a backup
$result = $mainwp->executeAction(123, 'backup_create', [
    'type' => 'full', // 'full', 'db', or 'files'
    'destination' => 'remote', // 'remote' or 'local'
    'filename' => 'backup-' . date('Y-m-d')
]);

// Restore a backup
$result = $mainwp->executeAction(123, 'backup_restore', [
    'backup_file' => 'backup-2023-01-01.zip'
]);

Post and Page Management

// Create a post
$result = $mainwp->executeAction(123, 'post_create', [
    'title' => 'New Post',
    'content' => 'This is the content of the post.',
    'status' => 'publish',
    'categories' => ['Category 1', 'Category 2'],
    'tags' => ['tag1', 'tag2']
]);

// Update a post
$result = $mainwp->executeAction(123, 'post_update', [
    'post_id' => 456,
    'title' => 'Updated Post',
    'content' => 'This is the updated content.'
]);

// Delete a post
$result = $mainwp->executeAction(123, 'post_delete', [
    'post_id' => 456
]);

Error Handling

The MainWP API Client throws different types of exceptions for different error scenarios:

try {
    $sites = $mainwp->getSites();
} catch (\MainWP\ApiClient\Exception\ApiException $e) {
    // API-related errors (invalid parameters, resource not found, etc.)
    echo 'API Error: ' . $e->getMessage();
    echo 'Error Code: ' . $e->getCode();
} catch (\MainWP\ApiClient\Exception\AuthenticationException $e) {
    // Authentication errors (invalid API key/secret, expired token, etc.)
    echo 'Authentication Error: ' . $e->getMessage();
} catch (\MainWP\ApiClient\Exception\ConnectionException $e) {
    // Connection errors (network issues, timeout, etc.)
    echo 'Connection Error: ' . $e->getMessage();
} catch (\Exception $e) {
    // Other errors
    echo 'Error: ' . $e->getMessage();
}

Advanced Usage

Custom Requests

If you need to make a custom API request that isn't covered by the built-in methods:

// Make a GET request
$response = $mainwp->get('custom-endpoint', ['param' => 'value']);

// Make a POST request
$response = $mainwp->post('custom-endpoint', ['data' => 'value']);

// Make a PUT request
$response = $mainwp->put('custom-endpoint', ['data' => 'value']);

// Make a DELETE request
$response = $mainwp->delete('custom-endpoint');

Batch Operations

For performing operations on multiple sites at once:

// Execute the same action on multiple sites
$results = $mainwp->batchExecuteAction([123, 456, 789], 'core_update');

// Different actions on different sites
$batch = [
    ['site_id' => 123, 'action' => 'core_update'],
    ['site_id' => 456, 'action' => 'plugin_update', 'params' => ['plugin' => 'akismet/akismet.php']],
    ['site_id' => 789, 'action' => 'theme_update', 'params' => ['theme' => 'twentytwentyone']]
];
$results = $mainwp->batchExecute($batch);

Pagination Handling

For working with paginated results:

// Get all sites with pagination
$page = 1;
$per_page = 10;
$all_sites = [];

do {
    $sites = $mainwp->getSites(['page' => $page, 'per_page' => $per_page]);
    $all_sites = array_merge($all_sites, $sites);
    $page++;
} while (count($sites) === $per_page);

Integration with MainWP Extensions

If you're building a MainWP extension, you can use the API client to interact with the MainWP Dashboard:

class My_MainWP_Extension {
    private $mainwp;
    
    public function __construct() {
        // Initialize the API client
        $this->mainwp = new \MainWP\ApiClient\MainWP([
            'baseUrl' => get_option('mainwp_url'),
            'apiKey' => get_option('mainwp_api_key'),
            'apiSecret' => get_option('mainwp_api_secret')
        ]);
        
        // Add menu items
        add_action('admin_menu', [$this, 'admin_menu']);
    }
    
    public function admin_menu() {
        add_menu_page(
            'My Extension',
            'My Extension',
            'manage_options',
            'my-extension',
            [$this, 'render_main_page']
        );
    }
    
    public function render_main_page() {
        try {
            // Get all sites
            $sites = $this->mainwp->getSites();
            
            // Display sites
            echo '<h1>Managed Sites</h1>';
            echo '<ul>';
            foreach ($sites as $site) {
                echo '<li>' . esc_html($site['name']) . ' (' . esc_html($site['url']) . ')</li>';
            }
            echo '</ul>';
        } catch (\Exception $e) {
            echo '<div class="error"><p>Error: ' . esc_html($e->getMessage()) . '</p></div>';
        }
    }
}

// Initialize the extension
new My_MainWP_Extension();

Real-World Example: Site Health Monitor

Here's a complete example of a script that checks the site health status of all managed sites and sends an email if any issues are found:

<?php
// site-health-monitor.php

require_once 'vendor/autoload.php';

use MainWP\ApiClient\MainWP;

// Configuration
$config = [
    'baseUrl' => 'https://your-mainwp-dashboard.com',
    'apiKey' => 'your-api-key',
    'apiSecret' => 'your-api-secret',
    'emailTo' => 'admin@example.com',
    'emailFrom' => 'monitor@example.com'
];

// Initialize the MainWP API client
$mainwp = new MainWP([
    'baseUrl' => $config['baseUrl'],
    'apiKey' => $config['apiKey'],
    'apiSecret' => $config['apiSecret']
]);

try {
    // Get all sites
    $sites = $mainwp->getSites();
    
    $issues = [];
    
    // Check each site
    foreach ($sites as $site) {
        try {
            // Get site health data
            $health = $mainwp->executeAction($site['id'], 'site_health');
            
            // Check for issues
            if ($health['status'] === 'critical') {
                $issues[] = [
                    'site' => $site['name'],
                    'url' => $site['url'],
                    'issues' => $health['issues']
                ];
            }
        } catch (\Exception $e) {
            // Add connection issues to the list
            $issues[] = [
                'site' => $site['name'],
                'url' => $site['url'],
                'issues' => ['Connection error: ' . $e->getMessage()]
            ];
        }
    }
    
    // Send email if issues were found
    if (!empty($issues)) {
        $subject = 'Site Health Issues Detected';
        
        $message = "The following sites have health issues:\n\n";
        
        foreach ($issues as $issue) {
            $message .= "Site: {$issue['site']} ({$issue['url']})\n";
            $message .= "Issues:\n";
            
            foreach ($issue['issues'] as $detail) {
                $message .= "- {$detail}\n";
            }
            
            $message .= "\n";
        }
        
        $headers = "From: {$config['emailFrom']}\r\n";
        
        mail($config['emailTo'], $subject, $message, $headers);
        
        echo "Email sent with " . count($issues) . " site issues.\n";
    } else {
        echo "No issues found.\n";
    }
} catch (\Exception $e) {
    echo "Error: " . $e->getMessage() . "\n";
}

Conclusion

The MainWP API Client PHP library provides a simple and powerful way to interact with your MainWP Dashboard programmatically. Whether you're building a MainWP extension, integrating MainWP with other systems, or automating your WordPress management workflows, this client library makes it easy to work with the MainWP API.

For more information and the latest updates, visit the MainWP API Client PHP GitHub repository.

Understanding the MainWP Development Extension

The MainWP Development Extension is a template provided by MainWP to help developers create new extensions quickly and efficiently. This guide explains the structure and components of the development extension, helping you understand how to use it effectively in your own projects.

Quick Start for Experienced Developers

The MainWP Development Extension follows a standard object-oriented structure:

Key files to modify:

  1. mainwp-development-extension.php: Main plugin file with registration
  2. class/class-mainwp-development-extension.php: Core functionality
  3. class/class-mainwp-development-extension-admin.php: Admin interface

Repository Overview

The MainWP Development Extension is available on GitHub and serves as a starting point for creating new extensions. It includes:

Directory Structure

mainwp-development-extension/
├── assets/
│   ├── css/
│   │   └── mainwp-development-extension.css
│   ├── images/
│   └── js/
│       └── mainwp-development-extension.js
├── class/
│   ├── class-mainwp-development-extension.php
│   ├── class-mainwp-development-extension-admin.php
│   ├── class-mainwp-development-extension-ajax.php
│   ├── class-mainwp-development-extension-db.php
│   ├── class-mainwp-development-extension-individual.php
│   ├── class-mainwp-development-extension-overview.php
│   └── class-mainwp-development-extension-utility.php
├── includes/
│   └── functions.php
├── languages/
│   └── mainwp-development-extension.pot
├── readme.txt
└── mainwp-development-extension.php

Key Components

Main Plugin File

The mainwp-development-extension.php file is the entry point for your extension. It includes:

/**
 * Plugin Name: MainWP Development Extension
 * Plugin URI: https://mainwp.com
 * Description: MainWP Development Extension example.
 * Version: 4.0
 * Author: MainWP
 * Author URI: https://mainwp.com
 * Documentation URI: https://mainwp.com/help/
 */

// Registration with MainWP
add_filter('mainwp_getextensions', function($extensions) {
    $extensions[] = array(
        'plugin' => __FILE__,
        'api' => 'MainWP_Development_Extension',
        'mainwp' => true,
        'callback' => array('MainWP_Development_Extension', 'get_instance'),
        'name' => 'Development Extension'
    );
    return $extensions;
});

Main Class

The class-mainwp-development-extension.php file contains the main class that initializes your extension and hooks into MainWP. Key features include:

class MainWP_Development_Extension {
    // Singleton instance
    private static $instance = null;
    
    // Get singleton instance
    public static function get_instance() {
        if (null === self::$instance) {
            self::$instance = new self();
        }
        return self::$instance;
    }
    
    // Constructor
    public function __construct() {
        // Load classes
        $this->includes();
        
        // Register hooks
        $this->setup_hooks();
    }
    
    // Load required files
    private function includes() {
        // Load classes and dependencies
    }
    
    // Set up hooks and filters
    private function setup_hooks() {
        // Register actions and filters
    }
}

Admin Class

The class-mainwp-development-extension-admin.php file handles the admin interface for your extension, including:

class MainWP_Development_Extension_Admin {
    // Initialize admin functionality
    public function init() {
        // Add admin menu items
        add_action('mainwp_admin_menu', array($this, 'init_menu'));
    }
    
    // Register admin menu items
    public function init_menu() {
        // Add submenu pages
        add_submenu_page(
            'mainwp_tab',
            __('Development Extension', 'mainwp-development-extension'),
            __('Development Extension', 'mainwp-development-extension'),
            'read',
            'DevelopmentExtension',
            array($this, 'render_admin_page')
        );
    }
    
    // Render admin page
    public function render_admin_page() {
        // Display admin interface
    }
}

Database Class

The class-mainwp-development-extension-db.php file manages database operations for your extension, including:

class MainWP_Development_Extension_DB {
    // Database table name
    private $table_name;
    
    // Constructor
    public function __construct() {
        global $wpdb;
        $this->table_name = $wpdb->prefix . 'mainwp_development_extension';
    }
    
    // Create database tables
    public function create_table() {
        global $wpdb;
        
        // SQL for table creation
        $sql = "CREATE TABLE IF NOT EXISTS $this->table_name (
            id int(11) NOT NULL AUTO_INCREMENT,
            site_id int(11) NOT NULL,
            data text NOT NULL,
            created_at datetime NOT NULL,
            PRIMARY KEY (id)
        ) $wpdb->get_charset_collate();";
        
        // Create table
        require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
        dbDelta($sql);
    }
    
    // Add data
    public function add_data($site_id, $data) {
        global $wpdb;
        
        // Insert data
        $wpdb->insert(
            $this->table_name,
            array(
                'site_id' => $site_id,
                'data' => $data,
                'created_at' => current_time('mysql')
            ),
            array('%d', '%s', '%s')
        );
        
        return $wpdb->insert_id;
    }
    
    // Get data
    public function get_data($site_id) {
        global $wpdb;
        
        // Retrieve data
        return $wpdb->get_results(
            $wpdb->prepare(
                "SELECT * FROM $this->table_name WHERE site_id = %d",
                $site_id
            )
        );
    }
}

Utility Class

The class-mainwp-development-extension-utility.php file contains helper functions and utilities for your extension:

class MainWP_Development_Extension_Utility {
    // Format data for display
    public static function format_data($data) {
        // Format and return data
    }
    
    // Validate input
    public static function validate_input($input) {
        // Validate and sanitize input
    }
    
    // Generate unique ID
    public static function generate_id() {
        // Generate and return unique ID
    }
}

AJAX Class

The class-mainwp-development-extension-ajax.php file handles AJAX requests for your extension:

class MainWP_Development_Extension_Ajax {
    // Constructor
    public function __construct() {
        // Register AJAX handlers
        add_action('wp_ajax_mainwp_development_extension_action', array($this, 'ajax_action'));
    }
    
    // Handle AJAX request
    public function ajax_action() {
        // Verify nonce
        check_ajax_referer('mainwp_development_extension_nonce', 'security');
        
        // Process request
        $action = isset($_POST['action_type']) ? sanitize_text_field($_POST['action_type']) : '';
        
        switch ($action) {
            case 'get_data':
                // Handle get_data action
                break;
            case 'save_data':
                // Handle save_data action
                break;
            default:
                // Handle unknown action
                break;
        }
        
        // Return response
        wp_send_json_success(array('message' => 'Action completed'));
    }
}

Overview and Individual Classes

The development extension includes classes for managing site overview and individual site pages:

These classes allow your extension to integrate with MainWP's site management interface.

Common Integration Points

The development extension demonstrates several common integration points with MainWP:

Dashboard Menu Integration

add_action('mainwp_admin_menu', array($this, 'init_menu'));

Site Actions

add_filter('mainwp_site_actions', array($this, 'site_actions'), 10, 2);

AJAX Handlers

add_action('wp_ajax_mainwp_development_extension_action', array($this, 'ajax_action'));

Dashboard Widgets

add_action('mainwp_dashboard_widgets', array($this, 'dashboard_widgets'));

Site Sync Data

add_filter('mainwp_site_sync_data', array($this, 'sync_data'), 10, 2);

Customizing the Development Extension

When using the development extension as a template for your own extension, follow these steps:

  1. Rename Files and Classes: Change all file names and class names to match your extension name
  2. Update Namespaces: Change the namespace from MainWP\Dev to your own namespace
  3. Modify Plugin Information: Update the plugin header with your extension's details
  4. Customize Functionality: Modify the classes to implement your extension's specific functionality
  5. Update Text Domain: Change the text domain for internationalization
  6. Customize UI: Modify the admin interface to match your extension's needs

Best Practices

When working with the MainWP Development Extension, follow these best practices:

Maintain the Structure

The development extension follows a well-organized structure that separates concerns and makes the code maintainable. Try to maintain this structure in your own extension.

Use Proper Namespacing

Use proper PHP namespaces to avoid conflicts with other plugins and extensions:

namespace MyCompany\MyExtension;

Follow WordPress Coding Standards

Adhere to WordPress Coding Standards for consistent, readable code.

Implement Proper Error Handling

Include robust error handling to ensure your extension gracefully handles unexpected situations:

try {
    // Attempt an operation
} catch (Exception $e) {
    // Handle the error
    error_log('MyExtension Error: ' . $e->getMessage());
    // Display user-friendly message
}

Use Prepared SQL Statements

Always use prepared statements for database queries to prevent SQL injection:

$wpdb->get_results(
    $wpdb->prepare(
        "SELECT * FROM $table_name WHERE site_id = %d",
        $site_id
    )
);

Internationalize Your Extension

Make your extension translatable by using proper internationalization functions:

__('Text to translate', 'your-text-domain');
esc_html__('Text to translate and escape', 'your-text-domain');

Advanced Customization

Adding Custom Database Tables

If your extension requires custom database tables, modify the Database class:

  1. Define your table structure in the create_table() method
  2. Add methods for CRUD operations on your tables
  3. Call create_table() during plugin activation

Creating Custom Admin Pages

To add custom admin pages:

  1. Register your pages in the init_menu() method of the Admin class
  2. Create rendering methods for each page
  3. Implement the necessary functionality for each page

Extending MainWP Dashboard Widgets

To add widgets to the MainWP Dashboard:

  1. Hook into the mainwp_dashboard_widgets action
  2. Register your widget with a unique ID
  3. Create a callback function to render the widget content
public function dashboard_widgets() {
    add_meta_box(
        'my_extension_widget',
        __('My Extension Widget', 'my-extension'),
        array($this, 'render_dashboard_widget'),
        'mainwp_dashboard',
        'normal',
        'default'
    );
}

public function render_dashboard_widget() {
    // Widget content
}

Troubleshooting Common Issues

Class Not Found Errors

If you encounter "Class not found" errors:

  1. Check that all file paths in the includes() method are correct
  2. Ensure you've updated all class references when renaming
  3. Verify that your autoloader (if used) is properly configured

Database Errors

  1. Check that your table creation SQL is valid
  2. Ensure you're using proper data types and prepared statements
  3. Verify that your database prefix is correct

Integration Issues

If your extension doesn't integrate properly with MainWP:

  1. Verify that you've registered your extension correctly with the mainwp_getextensions filter
  2. Check that your callback function returns a valid instance
  3. Ensure you're hooking into the correct MainWP actions and filters

Next Steps

Now that you understand the MainWP Development Extension, you can:

Packaging and Distributing MainWP Extensions

This guide is a placeholder and will be expanded in the future.

This guide will cover best practices for packaging and distributing MainWP extensions, including:

Check back soon for the complete guide.

Setting Up a MainWP Development Environment

This guide walks you through setting up a development environment for MainWP extension development. A proper development environment will make your development process more efficient and help you avoid common issues.

Quick Start for Experienced Developers

  1. Install Laragon (or similar local development environment)
  2. Set up WordPress (latest version)
  3. Install and configure MainWP Dashboard plugin
  4. Add at least one test child site (local or remote)
  5. Clone the MainWP Development Extension:
    git clone https://github.com/mainwp/mainwp-development-extension.git
    
  6. Start developing your extension

Why Use a Local Development Environment?

Developing MainWP extensions in a local environment offers several advantages:

Local Development Environment

We recommend using Laragon for MainWP extension development because it:

Other options include:

Code Editor

A good code editor will make your development more efficient. We recommend:

Version Control

Using Git for version control is highly recommended:

Browser Developer Tools

Modern browsers include developer tools that are essential for debugging:

Step-by-Step Setup Guide

1. Install Laragon

  1. Download Laragon from laragon.org
  2. Install Laragon following the installation wizard
  3. Launch Laragon

2. Create a WordPress Installation

  1. In Laragon, click "Menu" > "Quick app" > "WordPress"
  2. Enter a name for your site (e.g., "mainwp-dev")
  3. Laragon will create a new WordPress installation
  4. Once complete, click the link to open your new WordPress site
  5. Complete the WordPress installation process

3. Install MainWP Dashboard

  1. Log in to your WordPress admin dashboard
  2. Go to "Plugins" > "Add New"
  3. Search for "MainWP Dashboard"
  4. Click "Install Now" and then "Activate"
  5. Follow the MainWP setup wizard to configure the dashboard

4. Set Up Test Child Sites

For a complete development environment, you'll need at least one child site to test your extension with.

Option 1: Create Local Child Sites

  1. In Laragon, create additional WordPress installations for child sites
  2. Install and activate the MainWP Child plugin on each site
  3. Connect these sites to your MainWP Dashboard

Option 2: Use Existing Remote Sites

If you have existing WordPress sites, you can use them as test child sites:

  1. Install and activate the MainWP Child plugin on each site
  2. Connect these sites to your MainWP Dashboard

5. Clone the MainWP Development Extension

  1. Open a terminal or command prompt
  2. Navigate to your WordPress plugins directory:
    cd path/to/laragon/www/mainwp-dev/wp-content/plugins
    
  3. Clone the MainWP Development Extension:
    git clone https://github.com/mainwp/mainwp-development-extension.git
    
  4. Activate the extension in your WordPress admin dashboard

6. Configure PHP Development Settings

For effective development and debugging, configure PHP with appropriate settings:

  1. In Laragon, click "Menu" > "PHP" > "php.ini"
  2. Ensure the following settings are enabled:
    display_errors = On
    error_reporting = E_ALL
    
  3. Save the file and restart Laragon

7. Install Additional Development Tools

Composer

Composer is a dependency manager for PHP that makes it easy to manage libraries and dependencies:

  1. Laragon includes Composer by default
  2. To use it, open Laragon Terminal and run:
    composer --version
    

WP-CLI

WP-CLI is a command-line tool for managing WordPress:

  1. Laragon includes WP-CLI by default
  2. To use it, open Laragon Terminal and run:
    wp --version
    

Query Monitor

Query Monitor is a debugging plugin for WordPress:

  1. In your WordPress admin, go to "Plugins" > "Add New"
  2. Search for "Query Monitor"
  3. Install and activate the plugin

Development Workflow

Once your environment is set up, here's a recommended workflow for developing MainWP extensions:

  1. Plan your extension: Define its purpose, features, and how it will integrate with MainWP
  2. Create a new extension based on the MainWP Development Extension template
  3. Implement features incrementally, testing each one as you go
  4. Test thoroughly with different configurations and child sites
  5. Debug and refine your code
  6. Package for distribution when ready

Debugging Tips

WordPress Debugging

Enable WordPress debugging by adding these lines to your wp-config.php file:

define('WP_DEBUG', true);
define('WP_DEBUG_LOG', true);
define('WP_DEBUG_DISPLAY', true);

Debug logs will be saved to wp-content/debug.log.

MainWP Specific Debugging

MainWP includes several debugging options:

  1. Go to "MainWP" > "Settings" > "Advanced Options"
  2. Enable "Development Mode" for additional debugging information
  3. Use the "MainWP Tools" section for diagnostics

Common Issues and Solutions

Connection Issues Between Dashboard and Child Sites

If you're having trouble connecting your MainWP Dashboard to child sites:

  1. Ensure both sites are accessible from the internet (or use a tool like ngrok for local sites)
  2. Check that the MainWP Child plugin is properly installed and activated
  3. Verify that your server meets the MainWP requirements
  4. Try using the Connection Recovery process in MainWP Dashboard

PHP Version Compatibility

MainWP requires PHP 7.0 or higher. To change PHP versions in Laragon:

  1. Click "Menu" > "PHP"
  2. Select the desired PHP version
  3. Restart Laragon

Next Steps

Now that your development environment is set up, you're ready to start creating your MainWP extension:

MainWP Social Media Integration: Advanced Features

This guide covers advanced features and techniques for MainWP social media integrations, building on the core concepts covered in part 1. Here we'll explore Bluesky integration, advanced LinkedIn features, background processing, and best practices.

Bluesky Integration

Bluesky offers a simpler API integration with fewer restrictions and an easier setup process compared to LinkedIn.

Bluesky API Client

namespace MyCompany\Bluesky;

use MainWP\Dashboard\MainWP_Connect;
use WP_Error;
use Exception;

/**
 * Bluesky API Client
 */
class BlueskyAPIClient {
    /** @var object Website object */
    private $website;
    /** @var string App password */
    private $app_password;
    /** @var string Username */
    private $username;
    /** @var int Cache expiration in seconds */
    private $cache_expiration = 300; // 5 minutes
    /** @var string API base URL */
    private $api_base_url = 'https://bsky.social/xrpc/';
    /** @var string|null Authentication token */
    private $auth_token = null;
    
    /**
     * Constructor
     * 
     * @param object $website Website object
     * @param string $username Bluesky username
     * @param string $app_password Bluesky app password
     */
    public function __construct($website, $username, $app_password) {
        $this->website = $website;
        $this->username = $username;
        $this->app_password = $app_password;
    }
    
    /**
     * Authenticate with Bluesky
     * 
     * @return bool|WP_Error True on success, error on failure
     */
    public function authenticate() {
        // Check if already authenticated
        if ($this->auth_token !== null) {
            return true;
        }
        
        // Prepare the request data for MainWP
        $data = [
            'bluesky_api' => true,
            'endpoint' => 'com.atproto.server.createSession',
            'method' => 'POST',
            'args' => [
                'identifier' => $this->username,
                'password' => $this->app_password
            ]
        ];
        
        try {
            // Send the request through MainWP
            $information = MainWP_Connect::fetch_url_authed(
                $this->website,
                'bluesky_integration',
                $data
            );
            
            // Check for errors in the response
            if (is_array($information) && isset($information['error'])) {
                return new WP_Error('api_error', $information['error']);
            }
            
            // Store authentication token
            if (isset($information['accessJwt'])) {
                $this->auth_token = $information['accessJwt'];
                return true;
            }
            
            return new WP_Error('auth_error', 'Authentication failed');
        } catch (Exception $e) {
            return new WP_Error('api_error', $e->getMessage());
        }
    }
    
    /**
     * Create a post (skeet)
     * 
     * @param string $text Post text
     * @param array $images Optional array of image URLs
     * @return array|WP_Error Post data or error
     */
    public function create_post($text, $images = []) {
        // Authenticate if needed
        $auth_result = $this->authenticate();
        if (is_wp_error($auth_result)) {
            return $auth_result;
        }
        
        // Prepare post data
        $post_data = [
            'collection' => 'app.bsky.feed.post',
            'repo' => $this->username,
            'record' => [
                'text' => $text,
                'createdAt' => date('c')
            ]
        ];
        
        // Add images if provided
        if (!empty($images)) {
            $post_data['record']['embed'] = [
                '$type' => 'app.bsky.embed.images',
                'images' => []
            ];
            
            foreach ($images as $image) {
                // Upload image first
                $blob_result = $this->upload_blob($image['url'], $image['mime_type']);
                if (is_wp_error($blob_result)) {
                    continue;
                }
                
                $post_data['record']['embed']['images'][] = [
                    'alt' => isset($image['alt']) ? $image['alt'] : '',
                    'image' => $blob_result
                ];
            }
        }
        
        // Prepare the request data for MainWP
        $data = [
            'bluesky_api' => true,
            'endpoint' => 'com.atproto.repo.createRecord',
            'method' => 'POST',
            'args' => $post_data,
            'auth_token' => $this->auth_token
        ];
        
        try {
            // Send the request through MainWP
            $information = MainWP_Connect::fetch_url_authed(
                $this->website,
                'bluesky_integration',
                $data
            );
            
            // Check for errors in the response
            if (is_array($information) && isset($information['error'])) {
                return new WP_Error('api_error', $information['error']);
            }
            
            return $information;
        } catch (Exception $e) {
            return new WP_Error('api_error', $e->getMessage());
        }
    }
    
    /**
     * Upload a blob (image)
     * 
     * @param string $image_url Image URL
     * @param string $mime_type Image MIME type
     * @return array|WP_Error Blob data or error
     */
    private function upload_blob($image_url, $mime_type) {
        // Get image data
        $response = wp_remote_get($image_url);
        if (is_wp_error($response)) {
            return $response;
        }
        
        $image_data = wp_remote_retrieve_body($response);
        
        // Prepare the request data for MainWP
        $data = [
            'bluesky_api' => true,
            'endpoint' => 'com.atproto.repo.uploadBlob',
            'method' => 'POST',
            'args' => [
                'b64' => base64_encode($image_data),
                'mime_type' => $mime_type
            ],
            'auth_token' => $this->auth_token
        ];
        
        try {
            // Send the request through MainWP
            $information = MainWP_Connect::fetch_url_authed(
                $this->website,
                'bluesky_integration',
                $data
            );
            
            // Check for errors in the response
            if (is_array($information) && isset($information['error'])) {
                return new WP_Error('api_error', $information['error']);
            }
            
            return $information['blob'];
        } catch (Exception $e) {
            return new WP_Error('api_error', $e->getMessage());
        }
    }
    
    /**
     * Get user profile
     * 
     * @param string $username Username to get profile for (defaults to authenticated user)
     * @return array|WP_Error Profile data or error
     */
    public function get_profile($username = null) {
        // Authenticate if needed
        $auth_result = $this->authenticate();
        if (is_wp_error($auth_result)) {
            return $auth_result;
        }
        
        // Use authenticated user if username not provided
        if ($username === null) {
            $username = $this->username;
        }
        
        // Prepare the request data for MainWP
        $data = [
            'bluesky_api' => true,
            'endpoint' => 'app.bsky.actor.getProfile',
            'method' => 'GET',
            'args' => [
                'actor' => $username
            ],
            'auth_token' => $this->auth_token
        ];
        
        try {
            // Send the request through MainWP
            $information = MainWP_Connect::fetch_url_authed(
                $this->website,
                'bluesky_integration',
                $data
            );
            
            // Check for errors in the response
            if (is_array($information) && isset($information['error'])) {
                return new WP_Error('api_error', $information['error']);
            }
            
            return $information;
        } catch (Exception $e) {
            return new WP_Error('api_error', $e->getMessage());
        }
    }
    
    /**
     * Get user timeline
     * 
     * @param string $username Username to get timeline for (defaults to authenticated user)
     * @param string $cursor Pagination cursor
     * @param int $limit Number of posts to retrieve (max 100)
     * @return array|WP_Error Timeline data or error
     */
    public function get_timeline($username = null, $cursor = null, $limit = 50) {
        // Authenticate if needed
        $auth_result = $this->authenticate();
        if (is_wp_error($auth_result)) {
            return $auth_result;
        }
        
        // Use authenticated user if username not provided
        if ($username === null) {
            $username = $this->username;
        }
        
        // Prepare arguments
        $args = [
            'actor' => $username,
            'limit' => min(100, max(1, $limit))
        ];
        
        if ($cursor !== null) {
            $args['cursor'] = $cursor;
        }
        
        // Prepare the request data for MainWP
        $data = [
            'bluesky_api' => true,
            'endpoint' => 'app.bsky.feed.getAuthorFeed',
            'method' => 'GET',
            'args' => $args,
            'auth_token' => $this->auth_token
        ];
        
        try {
            // Send the request through MainWP
            $information = MainWP_Connect::fetch_url_authed(
                $this->website,
                'bluesky_integration',
                $data
            );
            
            // Check for errors in the response
            if (is_array($information) && isset($information['error'])) {
                return new WP_Error('api_error', $information['error']);
            }
            
            return $information;
        } catch (Exception $e) {
            return new WP_Error('api_error', $e->getMessage());
        }
    }
}

Bluesky Content Manager

namespace MyCompany\Bluesky\Content;

use WP_Error;

/**
 * Bluesky Content Manager
 */
class BlueskyContentManager {
    /** @var BlueskyAPIClient API client */
    private $api_client;
    
    /**
     * Constructor
     * 
     * @param BlueskyAPIClient $api_client API client
     */
    public function __construct($api_client) {
        $this->api_client = $api_client;
    }
    
    /**
     * Create a text post
     * 
     * @param string $text Post text
     * @return array|WP_Error Post data or error
     */
    public function create_text_post($text) {
        return $this->api_client->create_post($text);
    }
    
    /**
     * Create a post with images
     * 
     * @param string $text Post text
     * @param array $images Array of image data (url, mime_type, alt)
     * @return array|WP_Error Post data or error
     */
    public function create_image_post($text, $images) {
        return $this->api_client->create_post($text, $images);
    }
    
    /**
     * Schedule a post
     * 
     * @param string $text Post text
     * @param array $images Optional array of image data
     * @param int $timestamp Timestamp to publish the post
     * @return bool Success or failure
     */
    public function schedule_post($text, $images = [], $timestamp) {
        // Validate timestamp
        if ($timestamp <= time()) {
            return new WP_Error('invalid_timestamp', 'Scheduled time must be in the future');
        }
        
        // Get scheduled posts
        $scheduled_posts = get_option('mainwp_bluesky_scheduled_posts', []);
        
        // Add new scheduled post
        $scheduled_posts[] = [
            'text' => $text,
            'images' => $images,
            'timestamp' => $timestamp
        ];
        
        // Save scheduled posts
        return update_option('mainwp_bluesky_scheduled_posts', $scheduled_posts);
    }
}

Setting Up Bluesky Integration

  1. Get Bluesky Credentials

    • Create a Bluesky account if you don't have one
    • Generate an app password in your account settings
    • Note your username and app password
  2. Configure the Integration

    • Add Bluesky settings to your integration settings page
    • Store credentials securely using WordPress options API
namespace MyCompany\Bluesky\Settings;

/**
 * Save Bluesky API credentials for a site
 * 
 * @param int $website_id The website ID
 * @param string $username Bluesky username
 * @param string $app_password Bluesky app password
 * @return bool Success or failure
 */
public function save_bluesky_credentials($website_id, $username, $app_password) {
    // Validate inputs
    $website_id = absint($website_id);
    if ($website_id <= 0) {
        return false;
    }
    
    $username = sanitize_text_field($username);
    $app_password = sanitize_text_field($app_password);
    
    // Get current settings
    $settings = get_option('mainwp_bluesky_settings', []);
    
    // Update settings for this site
    $settings[$website_id] = [
        'username' => $username,
        'app_password' => $app_password
    ];
    
    // Save settings
    return update_option('mainwp_bluesky_settings', $settings);
}

Advanced LinkedIn Features

Create models for LinkedIn data to improve code organization and maintainability:

namespace MyCompany\LinkedIn\Models;

/**
 * Company Page Model
 */
class CompanyPage {
    /** @var string Company ID */
    public $id;
    /** @var string Company name */
    public $name;
    /** @var string Company vanity name */
    public $vanity_name;
    /** @var string Company logo URL */
    public $logo_url;
    /** @var string Company website */
    public $website;
    /** @var string Company industry */
    public $industry;
    /** @var int Company follower count */
    public $follower_count;
    /** @var string Company description */
    public $description;
    /** @var array Company locations */
    public $locations;
    
    /**
     * Create a company page from API data
     * 
     * @param array $data Company page data from API
     * @return CompanyPage Company page object
     */
    public static function from_api_data($data) {
        $company = new self();
        
        // Map API data to object properties
        $company->id = isset($data['id']) ? $data['id'] : '';
        $company->name = isset($data['name']) ? $data['name'] : '';
        $company->vanity_name = isset($data['vanityName']) ? $data['vanityName'] : '';
        $company->logo_url = isset($data['logoUrl']) ? $data['logoUrl'] : '';
        $company->website = isset($data['website']) ? $data['website'] : '';
        $company->industry = isset($data['industry']) ? $data['industry'] : '';
        $company->follower_count = isset($data['followerCount']) ? $data['followerCount'] : 0;
        $company->description = isset($data['description']) ? $data['description'] : '';
        $company->locations = isset($data['locations']) ? $data['locations'] : [];
        
        return $company;
    }
}

/**
 * Post Model
 */
class Post {
    /** @var string Post ID */
    public $id;
    /** @var string Author ID */
    public $author;
    /** @var string Post content */
    public $content;
    /** @var string Created time */
    public $created_time;
    /** @var string Last modified time */
    public $last_modified_time;
    /** @var array Media content */
    public $media;
    /** @var array Visibility settings */
    public $visibility;
    /** @var array Analytics data */
    public $analytics;
    
    /**
     * Create a post from API data
     * 
     * @param array $data Post data from API
     * @return Post Post object
     */
    public static function from_api_data($data) {
        $post = new self();
        
        // Map API data to object properties
        $post->id = isset($data['id']) ? $data['id'] : '';
        $post->author = isset($data['author']) ? $data['author'] : '';
        $post->content = isset($data['specificContent']['com.linkedin.ugc.ShareContent']['shareCommentary']['text']) 
            ? $data['specificContent']['com.linkedin.ugc.ShareContent']['shareCommentary']['text'] 
            : '';
        $post->created_time = isset($data['created']['time']) ? $data['created']['time'] : '';
        $post->last_modified_time = isset($data['lastModified']['time']) ? $data['lastModified']['time'] : '';
        $post->media = isset($data['specificContent']['com.linkedin.ugc.ShareContent']['media']) 
            ? $data['specificContent']['com.linkedin.ugc.ShareContent']['media'] 
            : [];
        $post->visibility = isset($data['visibility']) ? $data['visibility'] : [];
        $post->analytics = isset($data['analytics']) ? $data['analytics'] : [];
        
        return $post;
    }
}

Analytics and Reporting

Implement analytics functionality to track post performance:

namespace MyCompany\LinkedIn\Analytics;

use WP_Error;

/**
 * Analytics Manager
 */
class AnalyticsManager {
    /** @var LinkedInAPIClient API client */
    private $api_client;
    
    /**
     * Constructor
     * 
     * @param LinkedInAPIClient $api_client API client
     */
    public function __construct($api_client) {
        $this->api_client = $api_client;
    }
    
    /**
     * Get post analytics
     * 
     * @param string $company_id Company ID
     * @param string $post_id Post ID
     * @return array|WP_Error Analytics data or error
     */
    public function get_post_analytics($company_id, $post_id) {
        $endpoint = "organizationalEntityShareStatistics?q=organizationalEntity&organizationalEntity=urn:li:organization:{$company_id}&shares[0]=urn:li:share:{$post_id}";
        return $this->api_client->make_api_request($endpoint);
    }
    
    /**
     * Get company page analytics
     * 
     * @param string $company_id Company ID
     * @param string $timeRange Time range (DAY, WEEK, MONTH)
     * @return array|WP_Error Analytics data or error
     */
    public function get_company_analytics($company_id, $timeRange = 'DAY') {
        $endpoint = "organizationPageStatistics?q=organization&organization=urn:li:organization:{$company_id}&timeIntervals.timeGranularityType={$timeRange}&timeIntervals.timeRange.start=1577836800000";
        return $this->api_client->make_api_request($endpoint);
    }
    
    /**
     * Get follower demographics
     * 
     * @param string $company_id Company ID
     * @return array|WP_Error Demographics data or error
     */
    public function get_follower_demographics($company_id) {
        $endpoint = "organizationalEntityFollowerStatistics?q=organizationalEntity&organizationalEntity=urn:li:organization:{$company_id}";
        return $this->api_client->make_api_request($endpoint);
    }
    
    /**
     * Generate analytics report
     * 
     * @param string $company_id Company ID
     * @return array Report data
     */
    public function generate_report($company_id) {
        $report = [
            'company_id' => $company_id,
            'generated_at' => current_time('mysql'),
            'company_analytics' => [],
            'post_analytics' => [],
            'demographics' => []
        ];
        
        // Get company analytics
        $company_analytics = $this->get_company_analytics($company_id);
        if (!is_wp_error($company_analytics)) {
            $report['company_analytics'] = $company_analytics;
        }
        
        // Get recent posts
        $posts = $this->api_client->get_company_posts($company_id, ['count' => 10]);
        if (!is_wp_error($posts) && !empty($posts)) {
            foreach ($posts as $post) {
                if (isset($post['id'])) {
                    $post_analytics = $this->get_post_analytics($company_id, $post['id']);
                    if (!is_wp_error($post_analytics)) {
                        $report['post_analytics'][$post['id']] = $post_analytics;
                    }
                }
            }
        }
        
        // Get demographics
        $demographics = $this->get_follower_demographics($company_id);
        if (!is_wp_error($demographics)) {
            $report['demographics'] = $demographics;
        }
        
        return $report;
    }
}

Background Processing

For operations that may take a long time, implement background processing:

namespace MyCompany\SocialMedia\Background;

use MainWP\Dashboard\MainWP_DB;
use WP_Error;

/**
 * Schedule background tasks
 */
public function schedule_background_tasks() {
    if (!wp_next_scheduled('mainwp_social_media_background_tasks')) {
        wp_schedule_event(time(), 'hourly', 'mainwp_social_media_background_tasks');
    }
    
    // Hook into the scheduled event
    add_action('mainwp_social_media_background_tasks', [$this, 'process_scheduled_posts']);
}

/**
 * Process scheduled posts
 */
public function process_scheduled_posts() {
    // Process LinkedIn scheduled posts
    $this->process_linkedin_scheduled_posts();
    
    // Process Bluesky scheduled posts
    $this->process_bluesky_scheduled_posts();
}

/**
 * Process LinkedIn scheduled posts
 */
private function process_linkedin_scheduled_posts() {
    // Get scheduled LinkedIn posts
    $scheduled_posts = get_option('mainwp_linkedin_scheduled_posts', []);
    $current_time = time();
    $updated_posts = [];
    
    foreach ($scheduled_posts as $post) {
        if ($post['timestamp'] <= $current_time) {
            // Time to publish
            $api_client = $this->get_api_client($post['website_id']);
            if ($api_client) {
                $api_client->create_company_post($post['company_id'], $post['post_data']);
                
                // Log successful publishing if WP_DEBUG_LOG is enabled
                if (defined('WP_DEBUG_LOG') && WP_DEBUG_LOG) {
                    error_log(sprintf(
                        '[MainWP LinkedIn] Published scheduled post for company ID %s on website ID %d',
                        $post['company_id'],
                        $post['website_id']
                    ));
                }
            }
        } else {
            // Keep for future
            $updated_posts[] = $post;
        }
    }
    
    // Update scheduled posts
    update_option('mainwp_linkedin_scheduled_posts', $updated_posts);
}

/**
 * Process Bluesky scheduled posts
 */
private function process_bluesky_scheduled_posts() {
    // Get scheduled Bluesky posts
    $scheduled_posts = get_option('mainwp_bluesky_scheduled_posts', []);
    $current_time = time();
    $updated_posts = [];
    
    foreach ($scheduled_posts as $post) {
        if ($post['timestamp'] <= $current_time) {
            // Time to publish
            $api_client = $this->get_bluesky_api_client($post['website_id']);
            if ($api_client) {
                $api_client->create_post($post['text'], $post['images']);
                
                // Log successful publishing if WP_DEBUG_LOG is enabled
                if (defined('WP_DEBUG_LOG') && WP_DEBUG_LOG) {
                    error_log(sprintf(
                        '[MainWP Bluesky] Published scheduled post on website ID %d',
                        $post['website_id']
                    ));
                }
            }
        } else {
            // Keep for future
            $updated_posts[] = $post;
        }
    }
    
    // Update scheduled posts
    update_option('mainwp_bluesky_scheduled_posts', $updated_posts);
}

Using Action Scheduler for More Complex Tasks

For more complex background processing, consider using the Action Scheduler library:

namespace MyCompany\SocialMedia\Background;

use WP_Error;

/**
 * Schedule a post using Action Scheduler
 * 
 * @param int $website_id Website ID
 * @param string $company_id Company ID
 * @param array $post_data Post data
 * @param int $timestamp Timestamp to publish the post
 * @return int Action ID
 */
public function schedule_post_with_action_scheduler($website_id, $company_id, $post_data, $timestamp) {
    return as_schedule_single_action(
        $timestamp,
        'mainwp_linkedin_publish_post',
        [
            'website_id' => $website_id,
            'company_id' => $company_id,
            'post_data' => $post_data
        ]
    );
}

/**
 * Handle Action Scheduler publish post action
 * 
 * @param int $website_id Website ID
 * @param string $company_id Company ID
 * @param array $post_data Post data
 */
public function handle_publish_post_action($website_id, $company_id, $post_data) {
    $api_client = $this->get_api_client($website_id);
    if ($api_client) {
        $api_client->create_company_post($company_id, $post_data);
        
        // Log successful publishing if WP_DEBUG_LOG is enabled
        if (defined('WP_DEBUG_LOG') && WP_DEBUG_LOG) {
            error_log(sprintf(
                '[MainWP LinkedIn] Published scheduled post for company ID %s on website ID %d via Action Scheduler',
                $company_id,
                $website_id
            ));
        }
    }
}

Webhook Handling

Implement webhook handling to receive real-time notifications from social media platforms:

namespace MyCompany\SocialMedia\Webhooks;

use WP_REST_Request;
use WP_REST_Response;
use WP_Error;
use Exception;

/**
 * Register webhook handlers
 */
public function register_webhook_handlers() {
    // Register webhook endpoint
    add_action('rest_api_init', function() {
        register_rest_route('mainwp-social-media/v1', '/linkedin-webhook/(?P<website_id>\d+)', [
            'methods' => 'POST',
            'callback' => [$this, 'handle_linkedin_webhook'],
            'permission_callback' => [$this, 'verify_linkedin_webhook'],
        ]);
        
        register_rest_route('mainwp-social-media/v1', '/bluesky-webhook/(?P<website_id>\d+)', [
            'methods' => 'POST',
            'callback' => [$this, 'handle_bluesky_webhook'],
            'permission_callback' => [$this, 'verify_bluesky_webhook'],
        ]);
    });
}

/**
 * Verify LinkedIn webhook request
 * 
 * @param WP_REST_Request $request Request object
 * @return bool Whether the request is valid
 */
public function verify_linkedin_webhook($request) {
    // Validate website ID
    $website_id = absint($request->get_param('website_id'));
    if ($website_id <= 0) {
        return false;
    }
    
    $signature = $request->get_header('X-LinkedIn-Signature');
    if (empty($signature)) {
        // Log missing signature if WP_DEBUG_LOG is enabled
        if (defined('WP_DEBUG_LOG') && WP_DEBUG_LOG) {
            error_log(sprintf(
                '[MainWP LinkedIn] Missing webhook signature for website ID: %d',
                $website_id
            ));
        }
        return false;
    }
    
    // Get webhook secret for this site
    $settings = get_option('mainwp_linkedin_settings', []);
    $site_settings = isset($settings[$website_id]) ? $settings[$website_id] : [];
    
    if (empty($site_settings['webhook_secret'])) {
        // Log missing webhook secret if WP_DEBUG_LOG is enabled
        if (defined('WP_DEBUG_LOG') && WP_DEBUG_LOG) {
            error_log(sprintf(
                '[MainWP LinkedIn] Webhook secret not configured for website ID: %d',
                $website_id
            ));
        }
        return false;
    }
    
    // Verify signature with constant-time comparison to prevent timing attacks
    $payload = $request->get_body();
    $calculated_signature = hash_hmac('sha256', $payload, $site_settings['webhook_secret']);
    
    return hash_equals($calculated_signature, $signature);
}

/**
 * Handle LinkedIn webhook request
 * 
 * @param WP_REST_Request $request Request object
 * @return WP_REST_Response Response object
 */
public function handle_linkedin_webhook($request) {
    $website_id = absint($request->get_param('website_id'));
    $payload = json_decode($request->get_body(), true);
    
    if (!$payload || !isset($payload['event'])) {
        return new WP_REST_Response([
            'success' => false,
            'message' => 'Invalid payload or missing event'
        ], 400);
    }
    
    try {
        // Process webhook based on event
        switch ($payload['event']) {
            case 'ORGANIZATION_UPDATED':
                $this->process_organization_update($website_id, $payload['data']);
                break;
            case 'SHARE_CREATED':
                $this->process_share_created($website_id, $payload['data']);
                break;
            default:
                // Log unknown event if WP_DEBUG_LOG is enabled
                if (defined('WP_DEBUG_LOG') && WP_DEBUG_LOG) {
                    error_log(sprintf(
                        '[MainWP LinkedIn] Unknown webhook event: %s for website ID: %d',
                        $payload['event'],
                        $website_id
                    ));
                }
                return new WP_REST_Response([
                    'success' => false,
                    '

Creating a MainWP Social Media Integration

This guide walks you through the process of creating a MainWP integration with social media platforms, allowing you to manage and monitor social media content across your MainWP network. We'll focus primarily on LinkedIn as our main example.

Quick Start Available: If you're an experienced developer looking for just the essential code, check out our Social Media Integration Quick Start Guide.

For advanced features and Bluesky integration, see Part 2 of this guide.

Prerequisites

Before you begin creating a MainWP social media integration, ensure you have:

Integration Overview

A MainWP social media integration allows you to centrally manage and monitor social media content across your MainWP network. This provides several benefits:

Data Flow Architecture

The integration works by communicating between the MainWP Dashboard and social media platforms via child sites:

flowchart TD A[MainWP Dashboard] -->|API Request| B[MainWP Child] B -->|Social Media API Request| C[LinkedIn API] C -->|API Response| B B -->|API Response| A D[Social Media Data] -->|Stored During Sync| A

Step 1: Setting Up LinkedIn API Access

To communicate with LinkedIn, you'll need to set up API access:

Creating a LinkedIn Application

  1. Go to the LinkedIn Developer Portal
  2. Click "Create App"
  3. Fill in the required information:
    • App name
    • Company page
    • App logo
    • Description
  4. Click "Create App"
  5. Under "Auth" tab, add authorized redirect URLs (your MainWP Dashboard URL)
  6. Request the necessary API permissions:
    • r_organization_social
    • w_organization_social
    • r_liteprofile
  7. Note your Client ID and Client Secret

Implementing OAuth 2.0 Authentication

LinkedIn uses OAuth 2.0 for authentication. Here's how to implement it:

namespace MyCompany\LinkedIn\Auth;

use WP_Error;
use Exception;

/**
 * Initiate OAuth authentication flow
 */
public function initiate_oauth() {
    // Validate nonce
    check_admin_referer('mainwp_linkedin_oauth_nonce', 'mainwp_linkedin_oauth_nonce');
    
    // Validate website ID
    $website_id = isset($_GET['website_id']) ? absint($_GET['website_id']) : 0;
    if ($website_id <= 0) {
        wp_die('Invalid website ID');
    }
    
    // Get settings
    $settings = get_option('mainwp_linkedin_settings', []);
    $site_settings = isset($settings[$website_id]) ? $settings[$website_id] : [];
    
    if (empty($site_settings['client_id']) || empty($site_settings['client_secret'])) {
        wp_die('LinkedIn API credentials not configured');
    }
    
    // Generate state parameter to prevent CSRF
    $state = wp_generate_password(12, false);
    update_option('mainwp_linkedin_oauth_state_' . $website_id, $state);
    
    // Build authorization URL
    $auth_url = add_query_arg([
        'response_type' => 'code',
        'client_id' => $site_settings['client_id'],
        'redirect_uri' => urlencode(admin_url('admin.php?page=MainWPLinkedInSettings&action=oauth_callback&website_id=' . $website_id)),
        'state' => $state,
        'scope' => 'r_organization_social w_organization_social r_liteprofile'
    ], 'https://www.linkedin.com/oauth/v2/authorization');
    
    // Redirect to LinkedIn authorization page
    wp_redirect($auth_url);
    exit;
}

/**
 * Handle OAuth callback
 */
public function handle_oauth_callback() {
    // Validate website ID
    $website_id = isset($_GET['website_id']) ? absint($_GET['website_id']) : 0;
    if ($website_id <= 0) {
        wp_die('Invalid website ID');
    }
    
    // Verify state parameter to prevent CSRF
    $state = get_option('mainwp_linkedin_oauth_state_' . $website_id);
    if (empty($state) || $state !== $_GET['state']) {
        wp_die('Invalid state parameter');
    }
    
    // Clean up state option
    delete_option('mainwp_linkedin_oauth_state_' . $website_id);
    
    // Check for error
    if (isset($_GET['error'])) {
        wp_die('LinkedIn OAuth error: ' . sanitize_text_field($_GET['error_description']));
    }
    
    // Check for authorization code
    if (!isset($_GET['code'])) {
        wp_die('Authorization code not received');
    }
    
    // Get settings
    $settings = get_option('mainwp_linkedin_settings', []);
    $site_settings = isset($settings[$website_id]) ? $settings[$website_id] : [];
    
    if (empty($site_settings['client_id']) || empty($site_settings['client_secret'])) {
        wp_die('LinkedIn API credentials not configured');
    }
    
    // Exchange authorization code for access token
    $response = wp_remote_post('https://www.linkedin.com/oauth/v2/accessToken', [
        'body' => [
            'grant_type' => 'authorization_code',
            'code' => $_GET['code'],
            'redirect_uri' => admin_url('admin.php?page=MainWPLinkedInSettings&action=oauth_callback&website_id=' . $website_id),
            'client_id' => $site_settings['client_id'],
            'client_secret' => $site_settings['client_secret']
        ]
    ]);
    
    if (is_wp_error($response)) {
        wp_die('Error exchanging authorization code: ' . $response->get_error_message());
    }
    
    $body = json_decode(wp_remote_retrieve_body($response), true);
    
    if (!isset($body['access_token'])) {
        wp_die('Access token not received');
    }
    
    // Save access token
    $site_settings['access_token'] = $body['access_token'];
    $site_settings['expires_in'] = $body['expires_in'];
    $site_settings['token_obtained'] = time();
    
    $settings[$website_id] = $site_settings;
    update_option('mainwp_linkedin_settings', $settings);
    
    // Redirect to settings page
    wp_redirect(admin_url('admin.php?page=MainWPLinkedInSettings&oauth_success=1'));
    exit;
}

Storing API Credentials Securely

In your integration, you'll need to securely store these credentials:

namespace MyCompany\LinkedIn\Settings;

/**
 * Save API credentials for a site
 * 
 * @param int $website_id The website ID
 * @param string $client_id LinkedIn API client ID
 * @param string $client_secret LinkedIn API client secret
 * @return bool Success or failure
 */
public function save_api_credentials($website_id, $client_id, $client_secret) {
    // Validate inputs
    $website_id = absint($website_id);
    if ($website_id <= 0) {
        return false;
    }
    
    // Sanitize credentials
    $client_id = sanitize_text_field($client_id);
    $client_secret = sanitize_text_field($client_secret);
    
    // Get current settings
    $settings = get_option('mainwp_linkedin_settings', []);
    
    // Update settings for this site
    $settings[$website_id] = [
        'client_id' => $client_id,
        'client_secret' => $client_secret
    ];
    
    // Save settings
    return update_option('mainwp_linkedin_settings', $settings);
}

Testing API Connectivity

Always test the API connection before performing operations:

namespace MyCompany\LinkedIn\Diagnostics;

use WP_Error;
use Exception;

/**
 * Test LinkedIn API connection for a site
 * 
 * @param int $website_id The website ID
 * @return bool|string True on success, error message on failure
 */
public function test_api_connection($website_id) {
    // Validate website ID
    $website_id = absint($website_id);
    if ($website_id <= 0) {
        return 'Invalid website ID';
    }
    
    // Get API client
    $api_client = $this->get_api_client($website_id);
    if (!$api_client) {
        return 'API credentials not configured';
    }
    
    try {
        // Try to get user profile
        $result = $api_client->get_user_profile();
        
        if (is_wp_error($result)) {
            $error_message = $result->get_error_message();
            
            // Log error if WP_DEBUG_LOG is enabled
            if (defined('WP_DEBUG_LOG') && WP_DEBUG_LOG) {
                error_log(sprintf(
                    '[MainWP LinkedIn] API connection test failed for website ID %d: %s',
                    $website_id,
                    $error_message
                ));
            }
            
            return $error_message;
        }
        
        return true;
    } catch (Exception $e) {
        $error_message = $e->getMessage();
        
        // Log exception if WP_DEBUG_LOG is enabled
        if (defined('WP_DEBUG_LOG') && WP_DEBUG_LOG) {
            error_log(sprintf(
                '[MainWP LinkedIn] API connection test exception for website ID %d: %s',
                $website_id,
                $error_message
            ));
        }
        
        return 'Connection error: ' . $error_message;
    }
}

Step 2: Core Integration Components

LinkedIn API Client

Create a dedicated class for handling API communication:

namespace MyCompany\LinkedIn;

use MainWP\Dashboard\MainWP_Connect;
use WP_Error;
use Exception;

/**
 * LinkedIn API Client
 */
class LinkedInAPIClient {
    /** @var object Website object */
    private $website;
    /** @var string Client ID */
    private $client_id;
    /** @var string Client secret */
    private $client_secret;
    /** @var string Access token */
    private $access_token;
    /** @var int Cache expiration in seconds */
    private $cache_expiration = 300; // 5 minutes
    /** @var string API version */
    private $api_version = 'v2';
    /** @var string API base URL */
    private $api_base_url = 'https://api.linkedin.com/';
    
    /**
     * Constructor
     * 
     * @param object $website Website object
     * @param string $client_id LinkedIn API client ID
     * @param string $client_secret LinkedIn API client secret
     * @param string $access_token LinkedIn API access token
     */
    public function __construct($website, $client_id, $client_secret, $access_token) {
        $this->website = $website;
        $this->client_id = $client_id;
        $this->client_secret = $client_secret;
        $this->access_token = $access_token;
    }
    
    /**
     * Get user profile
     * 
     * @return array|WP_Error User profile or error
     */
    public function get_user_profile() {
        return $this->make_api_request('me');
    }
    
    /**
     * Get company pages
     * 
     * @return array|WP_Error Company pages or error
     */
    public function get_company_pages() {
        // Check cache first
        $cache_key = 'mainwp_linkedin_company_pages_' . $this->website->id;
        $cached_data = get_transient($cache_key);
        
        if (false !== $cached_data) {
            return $cached_data;
        }
        
        $result = $this->make_api_request('organizationAcls?q=roleAssignee&role=ADMINISTRATOR');
        
        // Cache the result if not an error
        if (!is_wp_error($result)) {
            set_transient($cache_key, $result, $this->cache_expiration);
        }
        
        return $result;
    }
    
    /**
     * Get company page information
     * 
     * @param string $company_id Company ID
     * @return array|WP_Error Company info or error
     */
    public function get_company_info($company_id) {
        // Check cache first
        $cache_key = 'mainwp_linkedin_company_' . $this->website->id . '_' . $company_id;
        $cached_data = get_transient($cache_key);
        
        if (false !== $cached_data) {
            return $cached_data;
        }
        
        $result = $this->make_api_request("organizations/{$company_id}");
        
        // Cache the result if not an error
        if (!is_wp_error($result)) {
            set_transient($cache_key, $result, $this->cache_expiration);
        }
        
        return $result;
    }
    
    /**
     * Get company page posts
     * 
     * @param string $company_id Company ID
     * @param array $args Query arguments
     * @return array|WP_Error Posts or error
     */
    public function get_company_posts($company_id, $args = []) {
        // Check cache first
        $cache_key = 'mainwp_linkedin_posts_' . $this->website->id . '_' . $company_id . '_' . md5(serialize($args));
        $cached_data = get_transient($cache_key);
        
        if (false !== $cached_data) {
            return $cached_data;
        }
        
        $result = $this->make_api_request("organizations/{$company_id}/posts", 'GET', $args);
        
        // Cache the result if not an error
        if (!is_wp_error($result)) {
            set_transient($cache_key, $result, $this->cache_expiration);
        }
        
        return $result;
    }
    
    /**
     * Create a company page post
     * 
     * @param string $company_id Company ID
     * @param array $post_data Post data
     * @return array|WP_Error Post data or error
     */
    public function create_company_post($company_id, $post_data) {
        // Clear posts cache
        $this->clear_posts_cache($company_id);
        
        return $this->make_api_request("organizations/{$company_id}/posts", 'POST', $post_data);
    }
    
    /**
     * Clear posts cache for a company
     * 
     * @param string $company_id Company ID
     */
    private function clear_posts_cache($company_id) {
        global $wpdb;
        
        // Find and delete all posts cache transients for this company
        $like = $wpdb->esc_like('_transient_mainwp_linkedin_posts_' . $this->website->id . '_' . $company_id) . '%';
        $wpdb->query($wpdb->prepare("DELETE FROM $wpdb->options WHERE option_name LIKE %s", $like));
    }
    
    /**
     * Make an API request to the LinkedIn REST API
     * 
     * @param string $endpoint API endpoint
     * @param string $method HTTP method
     * @param array $args Request arguments
     * @return array|WP_Error Response or error
     */
    private function make_api_request($endpoint, $method = 'GET', $args = []) {
        // Prepare the request data for MainWP
        $data = [
            'linkedin_api' => true,
            'endpoint' => $endpoint,
            'method' => $method,
            'args' => $args,
            'client_id' => $this->client_id,
            'client_secret' => $this->client_secret,
            'access_token' => $this->access_token
        ];
        
        try {
            // Send the request through MainWP
            $information = MainWP_Connect::fetch_url_authed(
                $this->website,
                'linkedin_integration',
                $data
            );
            
            // Check for errors in the response
            if (is_array($information) && isset($information['error'])) {
                return new WP_Error('api_error', $information['error']);
            }
            
            return $information;
        } catch (Exception $e) {
            return new WP_Error('api_error', $e->getMessage());
        }
    }
}

Step 3: Content Publishing

Implement content publishing functionality:

namespace MyCompany\LinkedIn\Content;

use WP_Error;

/**
 * Content Manager
 */
class ContentManager {
    /** @var LinkedInAPIClient API client */
    private $api_client;
    
    /**
     * Constructor
     * 
     * @param LinkedInAPIClient $api_client API client
     */
    public function __construct($api_client) {
        $this->api_client = $api_client;
    }
    
    /**
     * Create a text post
     * 
     * @param string $company_id Company ID
     * @param string $text Post text
     * @param string $visibility Post visibility (PUBLIC, CONNECTIONS, or LOGGED_IN)
     * @return array|WP_Error Post data or error
     */
    public function create_text_post($company_id, $text, $visibility = 'PUBLIC') {
        $post_data = [
            'author' => 'urn:li:organization:' . $company_id,
            'lifecycleState' => 'PUBLISHED',
            'specificContent' => [
                'com.linkedin.ugc.ShareContent' => [
                    'shareCommentary' => [
                        'text' => $text
                    ],
                    'shareMediaCategory' => 'NONE'
                ]
            ],
            'visibility' => [
                'com.linkedin.ugc.MemberNetworkVisibility' => $visibility
            ]
        ];
        
        return $this->api_client->create_company_post($company_id, $post_data);
    }
    
    /**
     * Create a post with an image
     * 
     * @param string $company_id Company ID
     * @param string $text Post text
     * @param string $image_url Image URL
     * @param string $title Image title
     * @param string $description Image description
     * @param string $visibility Post visibility (PUBLIC, CONNECTIONS, or LOGGED_IN)
     * @return array|WP_Error Post data or error
     */
    public function create_image_post($company_id, $text, $image_url, $title, $description, $visibility = 'PUBLIC') {
        $post_data = [
            'author' => 'urn:li:organization:' . $company_id,
            'lifecycleState' => 'PUBLISHED',
            'specificContent' => [
                'com.linkedin.ugc.ShareContent' => [
                    'shareCommentary' => [
                        'text' => $text
                    ],
                    'shareMediaCategory' => 'IMAGE',
                    'media' => [
                        [
                            'status' => 'READY',
                            'description' => [
                                'text' => $description
                            ],
                            'media' => $image_url,
                            'title' => [
                                'text' => $title
                            ]
                        ]
                    ]
                ]
            ],
            'visibility' => [
                'com.linkedin.ugc.MemberNetworkVisibility' => $visibility
            ]
        ];
        
        return $this->api_client->create_company_post($company_id, $post_data);
    }
    
    /**
     * Create a post with a link
     * 
     * @param string $company_id Company ID
     * @param string $text Post text
     * @param string $link_url Link URL
     * @param string $title Link title
     * @param string $description Link description
     * @param string $thumbnail_url Thumbnail URL
     * @param string $visibility Post visibility (PUBLIC, CONNECTIONS, or LOGGED_IN)
     * @return array|WP_Error Post data or error
     */
    public function create_link_post($company_id, $text, $link_url, $title, $description, $thumbnail_url, $visibility = 'PUBLIC') {
        $post_data = [
            'author' => 'urn:li:organization:' . $company_id,
            'lifecycleState' => 'PUBLISHED',
            'specificContent' => [
                'com.linkedin.ugc.ShareContent' => [
                    'shareCommentary' => [
                        'text' => $text
                    ],
                    'shareMediaCategory' => 'ARTICLE',
                    'media' => [
                        [
                            'status' => 'READY',
                            'description' => [
                                'text' => $description
                            ],
                            'originalUrl' => $link_url,
                            'title' => [
                                'text' => $title
                            ],
                            'thumbnails' => [
                                [
                                    'url' => $thumbnail_url
                                ]
                            ]
                        ]
                    ]
                ]
            ],
            'visibility' => [
                'com.linkedin.ugc.MemberNetworkVisibility' => $visibility
            ]
        ];
        
        return $this->api_client->create_company_post($company_id, $post_data);
    }
    
    /**
     * Schedule a post
     * 
     * @param string $company_id Company ID
     * @param array $post_data Post data
     * @param int $timestamp Timestamp to publish the post
     * @return bool Success or failure
     */
    public function schedule_post($company_id, $post_data, $timestamp) {
        // Validate timestamp
        if ($timestamp <= time()) {
            return new WP_Error('invalid_timestamp', 'Scheduled time must be in the future');
        }
        
        // Get scheduled posts
        $scheduled_posts = get_option('mainwp_linkedin_scheduled_posts', []);
        
        // Add new scheduled post
        $scheduled_posts[] = [
            'company_id' => $company_id,
            'post_data' => $post_data,
            'timestamp' => $timestamp
        ];
        
        // Save scheduled posts
        return update_option('mainwp_linkedin_scheduled_posts', $scheduled_posts);
    }
}

Best Practices for Social Media Integration

Security Best Practices

  1. Secure API Credentials: Store API credentials securely and never expose them in client-side code:

    // Use WordPress options API for storing credentials
    update_option('mainwp_linkedin_settings', $settings, true); // true = autoload
    
    // For extra security, consider encrypting sensitive data
    function encrypt_data($data, $key) {
        $iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length('aes-256-cbc'));
        $encrypted = openssl_encrypt($data, 'aes-256-cbc', $key, 0, $iv);
        return base64_encode($encrypted . '::' . $iv);
    }
    
    function decrypt_data($data, $key) {
        list($encrypted_data, $iv) = explode('::', base64_decode($data), 2);
        return openssl_decrypt($encrypted_data, 'aes-256-cbc', $key, 0, $iv);
    }
    
  2. Validate All User Input: Sanitize and validate all user-provided data before using it in API requests:

    // Validate and sanitize text input
    $text = sanitize_textarea_field($_POST['post_text']);
    if (empty($text)) {
        return new WP_Error('empty_text', 'Post text cannot be empty');
    }
    
    // Validate image URLs
    $image_url = esc_url_raw($_POST['image_url']);
    if (!filter_var($image_url, FILTER_VALIDATE_URL)) {
        return new WP_Error('invalid_url', 'Invalid image URL');
    }
    
  3. Use Proper Authentication: Implement OAuth 2.0 correctly for LinkedIn:

    // For LinkedIn, always verify state parameter to prevent CSRF
    if (empty($state) || $state !== $_GET['state']) {
        wp_die('Invalid state parameter');
    }
    

Performance Best Practices

  1. Implement Smart Caching: Cache API responses to reduce API calls and improve performance:

    // Cache API responses with appropriate expiration times
    $cache_key = 'mainwp_linkedin_company_' . $website_id . '_' . $company_id;
    $cached_data = get_transient($cache_key);
    
    if (false !== $cached_data) {
        return $cached_data;
    }
    
    // Make API request
    $result = $this->make_api_request("organizations/{$company_id}");
    
    // Cache the result if not an error
    if (!is_wp_error($result)) {
        set_transient($cache_key, $result, $this->cache_expiration);
    }
    
  2. Use Pagination for Large Datasets: Always implement pagination when retrieving large datasets:

    // Get posts with pagination
    $posts = $api_client->get_company_posts($company_id, [
        'start' => $offset,
        'count' => $per_page
    ]);
    
  3. Implement Defensive Programming: Always validate data and handle edge cases:

    // Ensure args is an array
    $args = is_array($args) ? $args : [];
    
    // Check for errors before processing
    if (is_wp_error($posts) || !is_array($posts)) {
        return [];
    }
    

Next Steps

For advanced features, including Bluesky integration, analytics, background processing, and webhook handling, see Part 2 of this guide.

MainWP Social Media Integration: Quick Start Guide

This quick start guide provides the essential code and steps to implement a MainWP social media integration. For more detailed explanations and advanced features, see the complete Social Media Integration guide.

Prerequisites

Quick Implementation: LinkedIn Integration

<?php
/**
 * Plugin Name: MainWP LinkedIn Integration
 * Plugin URI: https://yourwebsite.com/mainwp-linkedin
 * Description: Integrates MainWP with LinkedIn for centralized social media management.
 * Version: 1.0.0
 * Author: Your Name
 * Author URI: https://yourwebsite.com
 * Text Domain: mainwp-linkedin
 */

namespace YourCompany\MainWPLinkedIn;

use MainWP\Dashboard\MainWP_DB;
use MainWP\Dashboard\MainWP_Connect;
use WP_Error;
use Exception;

// Prevent direct access
if (!defined('ABSPATH')) {
    exit;
}

// Register the integration with MainWP
add_filter('mainwp_getextensions', function($extensions) {
    $extensions[] = [
        'plugin' => __FILE__,
        'api' => 'MainWPLinkedIn',
        'mainwp' => true,
        'callback' => [__NAMESPACE__ . '\\MainWPLinkedIn', 'get_instance'],
        'name' => 'LinkedIn Integration'
    ];
    return $extensions;
});

class MainWPLinkedIn {
    /** @var MainWPLinkedIn|null Instance */
    private static $instance = null;
    /** @var LinkedInAPIClient|null API Client */
    private $api_client = null;
    
    /**
     * Get singleton instance
     * @return MainWPLinkedIn Instance of the plugin
     */
    public static function get_instance() {
        if (null === self::$instance) {
            self::$instance = new self();
        }
        return self::$instance;
    }
    
    /**
     * Constructor
     */
    public function __construct() {
        // Initialize the integration
        add_action('admin_init', [$this, 'admin_init']);
        add_action('mainwp_admin_menu', [$this, 'init_menu']);
        
        // Hook into site sync to collect LinkedIn data
        add_action('mainwp_site_synced', [$this, 'site_synced'], 10, 2);
        
        // Add LinkedIn data to individual site view
        add_filter('mainwp_getsubpages_sites', [$this, 'add_linkedin_site_tab'], 10, 2);
    }
    
    /**
     * Initialize admin functionality
     */
    public function admin_init() {
        // Register settings
        register_setting('mainwp_linkedin_settings_group', 'mainwp_linkedin_settings');
    }
    
    /**
     * Initialize menu items
     */
    public function init_menu() {
        // Add LinkedIn dashboard page
        add_submenu_page(
            'mainwp_tab',
            __('LinkedIn', 'mainwp-linkedin'),
            __('LinkedIn', 'mainwp-linkedin'),
            'manage_options', // Use manage_options instead of read for better security
            'MainWPLinkedIn',
            [$this, 'render_dashboard_page']
        );
        
        // Add settings page
        add_submenu_page(
            'mainwp_tab',
            __('LinkedIn Settings', 'mainwp-linkedin'),
            __('LinkedIn Settings', 'mainwp-linkedin'),
            'manage_options', // Use manage_options instead of read for better security
            'MainWPLinkedInSettings',
            [$this, 'render_settings_page']
        );
    }
    
    /**
     * Process data after a site is synced
     * 
     * @param object $website The website object
     * @param array $data The sync data
     */
    public function site_synced($website, $data) {
        // Check if LinkedIn data is available
        if (isset($data['linkedin'])) {
            // Store the LinkedIn data for this site
            update_option('mainwp_linkedin_data_' . $website->id, $data['linkedin']);
        }
    }
    
    /**
     * Add LinkedIn tab to individual site view
     * 
     * @param array $subpages Current subpages
     * @param object $website The website object
     * @return array Modified subpages
     */
    public function add_linkedin_site_tab($subpages, $website) {
        $subpages[] = [
            'title' => __('LinkedIn', 'mainwp-linkedin'),
            'slug' => 'LinkedIn',
            'callback' => [$this, 'render_site_linkedin_tab'],
        ];
        return $subpages;
    }
    
    /**
     * Get LinkedIn API client for a specific site
     * 
     * @param int $website_id The website ID
     * @return LinkedInAPIClient|null API client or null on failure
     */
    private function get_api_client($website_id) {
        // Validate website ID
        $website_id = absint($website_id);
        if ($website_id <= 0) {
            return null;
        }
        
        // Get the website
        $website = MainWP_DB::instance()->get_website_by_id($website_id);
        if (!$website) {
            return null;
        }
        
        // Get API credentials from options
        $settings = get_option('mainwp_linkedin_settings', []);
        $site_settings = isset($settings[$website_id]) ? $settings[$website_id] : [];
        
        if (empty($site_settings['client_id']) || empty($site_settings['client_secret']) || empty($site_settings['access_token'])) {
            return null;
        }
        
        // Create API client
        return new LinkedInAPIClient(
            $website,
            $site_settings['client_id'],
            $site_settings['client_secret'],
            $site_settings['access_token']
        );
    }
    
    /**
     * Save API credentials for a site
     * 
     * @param int $website_id The website ID
     * @param string $client_id LinkedIn API client ID
     * @param string $client_secret LinkedIn API client secret
     * @param string $access_token LinkedIn API access token
     * @return bool Success or failure
     */
    public function save_api_credentials($website_id, $client_id, $client_secret, $access_token) {
        // Validate inputs
        $website_id = absint($website_id);
        if ($website_id <= 0) {
            return false;
        }
        
        $client_id = sanitize_text_field($client_id);
        $client_secret = sanitize_text_field($client_secret);
        $access_token = sanitize_text_field($access_token);
        
        // Get current settings
        $settings = get_option('mainwp_linkedin_settings', []);
        
        // Update settings for this site
        $settings[$website_id] = [
            'client_id' => $client_id,
            'client_secret' => $client_secret,
            'access_token' => $access_token
        ];
        
        // Save settings
        return update_option('mainwp_linkedin_settings', $settings);
    }
}

/**
 * LinkedIn API Client
 */
class LinkedInAPIClient {
    /** @var object Website object */
    private $website;
    /** @var string Client ID */
    private $client_id;
    /** @var string Client secret */
    private $client_secret;
    /** @var string Access token */
    private $access_token;
    /** @var int Cache expiration in seconds */
    private $cache_expiration = 300; // 5 minutes
    
    /**
     * Constructor
     * 
     * @param object $website Website object
     * @param string $client_id LinkedIn API client ID
     * @param string $client_secret LinkedIn API client secret
     * @param string $access_token LinkedIn API access token
     */
    public function __construct($website, $client_id, $client_secret, $access_token) {
        $this->website = $website;
        $this->client_id = $client_id;
        $this->client_secret = $client_secret;
        $this->access_token = $access_token;
    }
    
    /**
     * Get company page information
     * 
     * @param string $company_id Company ID
     * @return array|WP_Error Company info or error
     */
    public function get_company_info($company_id) {
        // Check cache first
        $cache_key = 'mainwp_linkedin_company_' . $this->website->id . '_' . $company_id;
        $cached_data = get_transient($cache_key);
        
        if (false !== $cached_data) {
            return $cached_data;
        }
        
        $endpoint = "organizations/{$company_id}";
        $result = $this->make_api_request($endpoint);
        
        // Cache the result if not an error
        if (!is_wp_error($result)) {
            set_transient($cache_key, $result, $this->cache_expiration);
        }
        
        return $result;
    }
    
    /**
     * Get company page posts
     * 
     * @param string $company_id Company ID
     * @param array $args Query arguments
     * @return array|WP_Error Posts or error
     */
    public function get_company_posts($company_id, $args = []) {
        // Check cache first
        $cache_key = 'mainwp_linkedin_posts_' . $this->website->id . '_' . $company_id . '_' . md5(serialize($args));
        $cached_data = get_transient($cache_key);
        
        if (false !== $cached_data) {
            return $cached_data;
        }
        
        $endpoint = "organizations/{$company_id}/posts";
        $result = $this->make_api_request($endpoint, 'GET', $args);
        
        // Cache the result if not an error
        if (!is_wp_error($result)) {
            set_transient($cache_key, $result, $this->cache_expiration);
        }
        
        return $result;
    }
    
    /**
     * Create a company page post
     * 
     * @param string $company_id Company ID
     * @param array $post_data Post data
     * @return array|WP_Error Post data or error
     */
    public function create_company_post($company_id, $post_data) {
        // Clear posts cache
        $this->clear_posts_cache($company_id);
        
        $endpoint = "organizations/{$company_id}/posts";
        return $this->make_api_request($endpoint, 'POST', $post_data);
    }
    
    /**
     * Clear posts cache for a company
     * 
     * @param string $company_id Company ID
     */
    private function clear_posts_cache($company_id) {
        global $wpdb;
        
        // Find and delete all posts cache transients for this company
        $like = $wpdb->esc_like('_transient_mainwp_linkedin_posts_' . $this->website->id . '_' . $company_id) . '%';
        $wpdb->query($wpdb->prepare("DELETE FROM $wpdb->options WHERE option_name LIKE %s", $like));
    }
    
    /**
     * Make an API request to the LinkedIn REST API
     * 
     * @param string $endpoint API endpoint
     * @param string $method HTTP method
     * @param array $args Request arguments
     * @return array|WP_Error Response or error
     */
    private function make_api_request($endpoint, $method = 'GET', $args = []) {
        // Prepare the request data for MainWP
        $data = [
            'linkedin_api' => true,
            'endpoint' => $endpoint,
            'method' => $method,
            'args' => $args,
            'client_id' => $this->client_id,
            'client_secret' => $this->client_secret,
            'access_token' => $this->access_token
        ];
        
        try {
            // Send the request through MainWP
            $information = MainWP_Connect::fetch_url_authed(
                $this->website,
                'linkedin_integration',
                $data
            );
            
            // Check for errors in the response
            if (is_array($information) && isset($information['error'])) {
                $error_message = $information['error'];
                // Log error if WP_DEBUG_LOG is enabled
                if (defined('WP_DEBUG_LOG') && WP_DEBUG_LOG) {
                    error_log(sprintf(
                        '[MainWP LinkedIn] API Error: %s (Endpoint: %s, Method: %s, Site ID: %d)',
                        $error_message,
                        $endpoint,
                        $method,
                        $this->website->id
                    ));
                }
                return new WP_Error('api_error', $error_message);
            }
            
            return $information;
        } catch (Exception $e) {
            $error_message = $e->getMessage();
            // Log exception if WP_DEBUG_LOG is enabled
            if (defined('WP_DEBUG_LOG') && WP_DEBUG_LOG) {
                error_log(sprintf(
                    '[MainWP LinkedIn] API Exception: %s (Endpoint: %s, Method: %s, Site ID: %d)',
                    $error_message,
                    $endpoint,
                    $method,
                    $this->website->id
                ));
            }
            return new WP_Error('api_error', $error_message);
        }
    }
}

Step-by-Step Implementation

  1. Create the Plugin File

    • Create a new PHP file in your WordPress plugins directory
    • Copy the code above into the file
    • Customize the plugin header information
  2. Set Up LinkedIn API Access

    • Create a LinkedIn Developer account at LinkedIn Developer Portal
    • Create a new application
    • Request the necessary API permissions:
      • r_organization_social
      • w_organization_social
      • r_liteprofile
    • Note your Client ID and Client Secret
  3. Configure the Integration

    • Activate the plugin in your MainWP Dashboard
    • Go to the LinkedIn Settings page
    • Enter the API credentials for each child site
    • Test the connection
  4. Use the Integration

    • Navigate to the LinkedIn dashboard page
    • View and manage LinkedIn content across your network
    • Create and schedule posts

Common Pitfalls to Avoid

  1. API Authentication Issues

    • Ensure your API credentials are correct
    • Verify that your application has the necessary permissions
    • Check that your redirect URLs are properly configured
  2. Rate Limiting

    • Implement caching to reduce API calls
    • Handle rate limit errors gracefully
    • Use exponential backoff for retries
  3. Security Concerns

    • Always validate and sanitize user input
    • Store API credentials securely
    • Use proper capability checks (manage_options)
    • Implement nonce verification for forms
  4. Performance Issues

    • Cache API responses appropriately
    • Use pagination for large datasets
    • Implement background processing for long-running tasks

Next Steps

For more detailed information and advanced features, refer to:

Working with Third-Party APIs in MainWP Integrations

This guide provides comprehensive information on working with third-party APIs in MainWP integrations. You'll learn how to design robust API clients, handle authentication, manage errors, implement caching, and more.

Quick Start for Experienced Developers

namespace MyCompany\ThirdParty;

use Exception;
use WP_Error;

// 1. Create a reusable API client class
class ThirdPartyApiClient {
    /** @var string API key */
    private $api_key;
    /** @var string API URL */
    private $api_url = 'https://api.third-party-service.com/v1/';
    /** @var int Request timeout */
    private $timeout = 30;
    
    /**
     * Constructor
     * 
     * @param string $api_key API key
     */
    public function __construct($api_key) {
        $this->api_key = $api_key;
    }
    
    /**
     * Make a GET request
     * 
     * @param string $endpoint API endpoint
     * @param array $params Query parameters
     * @return array|WP_Error Response data
     */
    public function get($endpoint, $params = []) {
        return $this->request('GET', $endpoint, $params);
    }
    
    /**
     * Make a POST request
     * 
     * @param string $endpoint API endpoint
     * @param array $data Post data
     * @return array|WP_Error Response data
     */
    public function post($endpoint, $data = []) {
        return $this->request('POST', $endpoint, [], $data);
    }
    
    /**
     * Make an API request
     * 
     * @param string $method HTTP method
     * @param string $endpoint API endpoint
     * @param array $params Query parameters
     * @param array|null $data Request body data
     * @return array|WP_Error Response data
     */
    private function request($method, $endpoint, $params = [], $data = null) {
        $url = $this->api_url . $endpoint;
        $cache_key = 'tpapi_' . md5($method . $url . serialize($params) . serialize($data));
        
        // Check cache for GET requests
        if ($method === 'GET') {
            $cached = get_transient($cache_key);
            if (false !== $cached) {
                return $cached;
            }
        }
        
        // Prepare request
        $args = [
            'method' => $method,
            'headers' => [
                'Authorization' => 'Bearer ' . $this->api_key,
                'Content-Type' => 'application/json',
                'Accept' => 'application/json'
            ],
            'timeout' => $this->timeout
        ];
        
        // Add query parameters
        if (!empty($params)) {
            $url = add_query_arg($params, $url);
        }
        
        // Add body data for POST/PUT/PATCH
        if ($data !== null && in_array($method, ['POST', 'PUT', 'PATCH'])) {
            $args['body'] = json_encode($data);
        }
        
        // Make request
        $response = wp_remote_request($url, $args);
        
        // Handle errors
        if (is_wp_error($response)) {
            throw new Exception('API request failed: ' . $response->get_error_message());
        }
        
        $status_code = wp_remote_retrieve_response_code($response);
        $body = wp_remote_retrieve_body($response);
        $result = json_decode($body, true);
        
        // Handle API errors
        if ($status_code >= 400) {
            $error_message = isset($result['message']) ? $result['message'] : 'Unknown API error';
            throw new Exception("API error ($status_code): $error_message");
        }
        
        // Cache successful GET responses
        if ($method === 'GET') {
            set_transient($cache_key, $result, 5 * MINUTE_IN_SECONDS);
        }
        
        return $result;
    }
}

// 2. Use the API client in your MainWP integration
class MyIntegration {
    /** @var ThirdPartyApiClient API client */
    private $api_client;
    
    /**
     * Constructor
     */
    public function __construct() {
        // Initialize API client
        $api_key = get_option('my_integration_api_key', '');
        if (!empty($api_key)) {
            $this->api_client = new ThirdPartyApiClient($api_key);
        }
        
        // Hook into MainWP
        add_action('mainwp_site_synced', [$this, 'sync_third_party_data'], 10, 2);
    }
    
    /**
     * Sync data from third-party service
     * 
     * @param object $website Website object
     * @param array $data Sync data
     */
    public function sync_third_party_data($website, $data) {
        if (!$this->api_client) {
            return;
        }
        
        try {
            // Get data from third-party API
            $api_data = $this->api_client->get('sites/' . urlencode($website->url));
            
            // Store the data for this site
            update_option('my_integration_data_' . $website->id, $api_data);
            update_option('my_integration_last_sync_' . $website->id, time());
        } catch (Exception $e) {
            // Log error but continue
            error_log('API Error: ' . $e->getMessage());
        }
    }
}

Designing a Robust API Client

A well-designed API client is the foundation of any successful third-party integration. It should handle authentication, requests, responses, errors, and caching in a consistent way.

Basic API Client Structure

Here's a comprehensive API client class that you can adapt for your integration:

namespace MyCompany\ThirdParty;

use Exception;
use WP_Error;

/**
 * Third-party API client
 */
class ThirdPartyApiClient {
    /** @var string API key */
    private $api_key;
    /** @var string API URL */
    private $api_url;
    /** @var int Request timeout */
    private $timeout = 30;
    /** @var array|WP_Error Last response */
    private $last_response;
    /** @var array Last request */
    private $last_request;
    
    /**
     * Constructor
     *
     * @param string $api_key API key or token
     * @param string $api_url Base API URL
     */
    public function __construct($api_key, $api_url = 'https://api.third-party-service.com/v1/') {
        $this->api_key = $api_key;
        $this->api_url = $api_url;
    }
    
    /**
     * Make a GET request
     *
     * @param string $endpoint API endpoint
     * @param array $params Query parameters
     * @param bool $cache Whether to cache the response
     * @param int $cache_time Cache time in seconds
     * @return array|object Response data
     */
    public function get($endpoint, $params = [], $cache = true, $cache_time = 300) {
        return $this->request('GET', $endpoint, $params, null, $cache, $cache_time);
    }
    
    /**
     * Make a POST request
     *
     * @param string $endpoint API endpoint
     * @param array $data Post data
     * @param array $params Query parameters
     * @return array|object Response data
     */
    public function post($endpoint, $data = [], $params = []) {
        return $this->request('POST', $endpoint, $params, $data);
    }
    
    /**
     * Make a PUT request
     *
     * @param string $endpoint API endpoint
     * @param array $data Put data
     * @param array $params Query parameters
     * @return array|object Response data
     */
    public function put($endpoint, $data = [], $params = []) {
        return $this->request('PUT', $endpoint, $params, $data);
    }
    
    /**
     * Make a DELETE request
     *
     * @param string $endpoint API endpoint
     * @param array $params Query parameters
     * @return array|object Response data
     */
    public function delete($endpoint, $params = []) {
        return $this->request('DELETE', $endpoint, $params);
    }
    
    /**
     * Make an API request
     *
     * @param string $method HTTP method
     * @param string $endpoint API endpoint
     * @param array $params Query parameters
     * @param array $data Request body data
     * @param bool $cache Whether to cache the response
     * @param int $cache_time Cache time in seconds
     * @return array|object Response data
     * @throws Exception If the request fails
     */
    private function request($method, $endpoint, $params = [], $data = null, $cache = false, $cache_time = 300) {
        $url = $this->api_url . ltrim($endpoint, '/');
        $cache_key = 'tpapi_' . md5($method . $url . serialize($params) . serialize($data));
        
        // Check cache for GET requests
        if ($method === 'GET' && $cache) {
            $cached = get_transient($cache_key);
            if (false !== $cached) {
                return $cached;
            }
        }
        
        // Prepare request
        $args = [
            'method' => $method,
            'headers' => [
                'Authorization' => 'Bearer ' . $this->api_key,
                'Content-Type' => 'application/json',
                'Accept' => 'application/json'
            ],
            'timeout' => $this->timeout
        ];
        
        // Add query parameters
        if (!empty($params)) {
            $url = add_query_arg($params, $url);
        }
        
        // Add body data for POST/PUT/PATCH
        if ($data !== null && in_array($method, ['POST', 'PUT', 'PATCH'])) {
            $args['body'] = json_encode($data);
        }
        
        // Store request for debugging
        $this->last_request = [
            'url' => $url,
            'method' => $method,
            'args' => $args
        ];
        
        // Make request
        $response = wp_remote_request($url, $args);
        $this->last_response = $response;
        
        // Handle connection errors
        if (is_wp_error($response)) {
            throw new Exception('API request failed: ' . $response->get_error_message());
        }
        
        $status_code = wp_remote_retrieve_response_code($response);
        $body = wp_remote_retrieve_body($response);
        $result = json_decode($body, true);
        
        // Handle API errors
        if ($status_code >= 400) {
            $error_message = isset($result['message']) ? $result['message'] : 'Unknown API error';
            throw new Exception("API error ($status_code): $error_message", $status_code);
        }
        
        // Cache successful GET responses
        if ($method === 'GET' && $cache) {
            set_transient($cache_key, $result, $cache_time);
        }
        
        return $result;
    }
    
    /**
     * Get the last response
     *
     * @return array|WP_Error Last response
     */
    public function get_last_response() {
        return $this->last_response;
    }
    
    /**
     * Get the last request
     *
     * @return array Last request
     */
    public function get_last_request() {
        return $this->last_request;
    }
    
    /**
     * Test the API connection
     *
     * @return bool Whether the connection is successful
     */
    public function test_connection() {
        try {
            // Replace 'test-endpoint' with an actual lightweight endpoint from your API
            $this->get('test-endpoint', [], false);
            return true;
        } catch (Exception $e) {
            return false;
        }
    }
}

Key Features to Include

When designing your API client, consider including these features:

  1. Method-specific functions: Create dedicated methods for GET, POST, PUT, DELETE, etc.
  2. Consistent error handling: Throw exceptions with meaningful messages
  3. Response caching: Cache GET responses to reduce API calls
  4. Request/response logging: Store the last request and response for debugging
  5. Configurable timeouts: Allow adjustment of request timeouts
  6. Connection testing: Include a method to test the API connection

Authentication Methods

Different APIs use different authentication methods. Here's how to implement the most common ones:

API Key Authentication

Many APIs use API keys, either in the header or as a query parameter:

// API key in header
$args = [
    'headers' => [
        'X-API-Key' => $this->api_key
    ]
];

// API key in query parameter
$url = add_query_arg(['api_key' => $this->api_key], $url);

Bearer Token Authentication

Bearer tokens (including JWT) are typically sent in the Authorization header:

$args = [
    'headers' => [
        'Authorization' => 'Bearer ' . $this->api_token
    ]
];

Basic Authentication

Basic authentication uses base64-encoded username and password:

$args = [
    'headers' => [
        'Authorization' => 'Basic ' . base64_encode($this->username . ':' . $this->password)
    ]
];

OAuth 2.0 Authentication

OAuth 2.0 is more complex and typically involves:

  1. Redirecting the user to the authorization server
  2. Receiving an authorization code
  3. Exchanging the code for access and refresh tokens
  4. Using the access token for API requests
  5. Refreshing the access token when it expires

Here's a simplified implementation:

namespace MyCompany\ThirdParty\OAuth;

use Exception;
use WP_Error;

/**
 * OAuth2 client
 */
class OAuth2Client extends ThirdPartyApiClient {
    /** @var string Client ID */
    private $client_id;
    /** @var string Client secret */
    private $client_secret;
    /** @var string Redirect URI */
    private $redirect_uri;
    /** @var string Authorization URL */
    private $auth_url;
    /** @var string Token URL */
    private $token_url;
    /** @var string Access token */
    private $access_token;
    /** @var string Refresh token */
    private $refresh_token;
    /** @var int Token expiration timestamp */
    private $token_expires;
    
    /**
     * Constructor
     */
    public function __construct($client_id, $client_secret, $redirect_uri, $auth_url, $token_url, $api_url) {
        $this->client_id = $client_id;
        $this->client_secret = $client_secret;
        $this->redirect_uri = $redirect_uri;
        $this->auth_url = $auth_url;
        $this->token_url = $token_url;
        
        // Load tokens from options
        $this->access_token = get_option('my_integration_access_token', '');
        $this->refresh_token = get_option('my_integration_refresh_token', '');
        $this->token_expires = get_option('my_integration_token_expires', 0);
        
        parent::__construct($this->access_token, $api_url);
    }
    
    /**
     * Get the authorization URL
     *
     * @param array $scope API scopes to request
     * @param string $state State parameter for security
     * @return string Authorization URL
     */
    public function get_auth_url($scope = [], $state = '') {
        $params = [
            'client_id' => $this->client_id,
            'redirect_uri' => $this->redirect_uri,
            'response_type' => 'code',
            'state' => $state ?: wp_create_nonce('oauth2_state')
        ];
        
        if (!empty($scope)) {
            $params['scope'] = implode(' ', $scope);
        }
        
        return add_query_arg($params, $this->auth_url);
    }
    
    /**
     * Exchange authorization code for tokens
     *
     * @param string $code Authorization code
     * @return bool Whether the exchange was successful
     */
    public function exchange_code_for_tokens($code) {
        $args = [
            'method' => 'POST',
            'headers' => [
                'Content-Type' => 'application/x-www-form-urlencoded'
            ],
            'body' => [
                'grant_type' => 'authorization_code',
                'code' => $code,
                'client_id' => $this->client_id,
                'client_secret' => $this->client_secret,
                'redirect_uri' => $this->redirect_uri
            ]
        ];
        
        $response = wp_remote_post($this->token_url, $args);
        
        if (is_wp_error($response)) {
            return false;
        }
        
        $body = wp_remote_retrieve_body($response);
        $data = json_decode($body, true);
        
        if (empty($data['access_token'])) {
            return false;
        }
        
        // Save tokens
        $this->access_token = $data['access_token'];
        $this->refresh_token = $data['refresh_token'] ?? '';
        $this->token_expires = time() + ($data['expires_in'] ?? 3600);
        
        update_option('my_integration_access_token', $this->access_token);
        update_option('my_integration_refresh_token', $this->refresh_token);
        update_option('my_integration_token_expires', $this->token_expires);
        
        return true;
    }
    
    /**
     * Refresh the access token
     *
     * @return bool Whether the refresh was successful
     */
    public function refresh_access_token() {
        if (empty($this->refresh_token)) {
            return false;
        }
        
        $args = [
            'method' => 'POST',
            'headers' => [
                'Content-Type' => 'application/x-www-form-urlencoded'
            ],
            'body' => [
                'grant_type' => 'refresh_token',
                'refresh_token' => $this->refresh_token,
                'client_id' => $this->client_id,
                'client_secret' => $this->client_secret
            ]
        ];
        
        $response = wp_remote_post($this->token_url, $args);
        
        if (is_wp_error($response)) {
            return false;
        }
        
        $body = wp_remote_retrieve_body($response);
        $data = json_decode($body, true);
        
        if (empty($data['access_token'])) {
            return false;
        }
        
        // Save tokens
        $this->access_token = $data['access_token'];
        if (!empty($data['refresh_token'])) {
            $this->refresh_token = $data['refresh_token'];
            update_option('my_integration_refresh_token', $this->refresh_token);
        }
        $this->token_expires = time() + ($data['expires_in'] ?? 3600);
        
        update_option('my_integration_access_token', $this->access_token);
        update_option('my_integration_token_expires', $this->token_expires);
        
        return true;
    }
    
    /**
     * Override request method to handle token refresh
     */
    protected function request($method, $endpoint, $params = [], $data = null, $cache = false, $cache_time = 300) {
        // Check if token is expired and refresh if needed
        if (time() > $this->token_expires - 60) {
            $this->refresh_access_token();
        }
        
        // Update the access token in the parent class
        $this->api_key = $this->access_token;
        
        return parent::request($method, $endpoint, $params, $data, $cache, $cache_time);
    }
}

Error Handling and Retry Strategies

Robust error handling is essential for reliable API integrations. Here's a comprehensive approach:

Error Types to Handle

  1. Connection errors: Network issues, timeouts
  2. Authentication errors: Invalid or expired credentials
  3. Rate limiting: Too many requests
  4. Server errors: 5xx responses
  5. Client errors: 4xx responses
  6. Parsing errors: Invalid JSON responses

Custom Exception Classes

Create custom exception classes for different error types:

namespace MyCompany\ThirdParty\Exceptions;

use Exception;

/**
 * Base API exception
 */
class ApiException extends Exception {
    /** @var int HTTP status code */
    protected $status_code;
    
    /**
     * Constructor
     */
    public function __construct($message, $status_code = 0, $code = 0) {
        $this->status_code = $status_code;
        parent::__construct($message, $code);
    }
    
    /**
     * Get HTTP status code
     */
    public function getStatusCode() {
        return $this->status_code;
    }
}

class ApiConnectionException extends ApiException {}
class ApiAuthException extends ApiException {}
class ApiRateLimitException extends ApiException {
    /** @var int Retry after seconds */
    protected $retry_after;
    
    /**
     * Constructor
     */
    public function __construct($message, $status_code = 0, $retry_after = 60, $code = 0) {
        $this->retry_after = $retry_after;
        parent::__construct($message, $status_code, $code);
    }
    
    /**
     * Get retry after seconds
     */
    public function getRetryAfter() {
        return $this->retry_after;
    }
}
class ApiServerException extends ApiException {}
class ApiClientException extends ApiException {}

Using Try-Catch for Error Handling

When using your API client, wrap calls in try-catch blocks:

try {
    $data = $api_client->get('endpoint');
    // Process successful response
} catch (ApiAuthException $e) {
    // Handle authentication errors
    error_log('API Authentication Error: ' . $e->getMessage());
    // Prompt user to re-authenticate
} catch (ApiRateLimitException $e) {
    // Handle rate limiting
    error_log('API Rate Limit Error: ' . $e->getMessage());
    error_log('Retry After: ' . $e->getRetryAfter() . ' seconds');
    // Schedule a retry after the specified time
} catch (ApiServerException $e) {
    // Handle server errors
    error_log('API Server Error: ' . $e->getMessage());
    // Display a friendly message to the user
} catch (ApiClientException $e) {
    // Handle client errors
    error_log('API Client Error: ' . $e->getMessage());
    // Fix the request or display an error message
} catch (ApiConnectionException $e) {
    // Handle connection errors
    error_log('API Connection Error: ' . $e->getMessage());
    // Check network connectivity
} catch (Exception $e) {
    // Handle any other errors
    error_log('API Error: ' . $e->getMessage());
}

Rate Limiting Considerations

Most APIs implement rate limits to prevent abuse. Here's how to handle them:

Understanding Rate Limits

Rate limits can be:

Implementing a Rate Limit Handler

namespace MyCompany\ThirdParty\RateLimit;

/**
 * Rate limit handler using token bucket algorithm
 */
class RateLimitHandler {
    /** @var int Maximum number of tokens */
    private $bucket_size;
    /** @var int Tokens added per second */
    private $refill_rate;
    /** @var int Available tokens */
    private $available_tokens;
    /** @var int Last refill time */
    private $last_refill_time;
    /** @var string Option name for storing state */
    private $option_name;
    
    /**
     * Constructor
     */
    public function __construct($bucket_size = 60, $refill_rate = 1, $option_name = 'api_rate_limit_state') {
        $this->bucket_size = $bucket_size;
        $this->refill_rate = $refill_rate;
        $this->option_name = $option_name;
        
        // Load state
        $state = get_option($option_name, [
            'available_tokens' => $bucket_size,
            'last_refill_time' => time()
        ]);
        
        $this->available_tokens = $state['available_tokens'];
        $this->last_refill_time = $state['last_refill_time'];
        
        // Refill tokens based on elapsed time
        $this->refill_tokens();
    }
    
    /**
     * Refill tokens based on elapsed time
     */
    private function refill_tokens() {
        $now = time();
        $elapsed = $now - $this->last_refill_time;
        
        if ($elapsed > 0) {
            $new_tokens = $elapsed * $this->refill_rate;
            $this->available_tokens = min($this->bucket_size, $this->available_tokens + $new_tokens);
            $this->last_refill_time = $now;
            
            // Save state
            update_option($this->option_name, [
                'available_tokens' => $this->available_tokens,
                'last_refill_time' => $this->last_refill_time
            ]);
        }
    }
    
    /**
     * Check if a request can be made
     *
     * @param int $tokens Number of tokens required
     * @return bool Whether the request can be made
     */
    public function can_make_request($tokens = 1) {
        $this->refill_tokens();
        return $this->available_tokens >= $tokens;
    }
    
    /**
     * Consume tokens for a request
     *
     * @param int $tokens Number of tokens to consume
     * @return bool Whether tokens were consumed
     */
    public function consume_tokens($tokens = 1) {
        if (!$this->can_make_request($tokens)) {
            return false;
        }
        
        $this->available_tokens -= $tokens;
        
        // Save state
        update_option($this->option_name, [
            'available_tokens' => $this->available_tokens,
            'last_refill_time' => $this->last_refill_time
        ]);
        
        return true;
    }
    
    /**
     * Get time until next token is available
     *
     * @return int Seconds until next token
     */
    public function get_wait_time() {
        if ($this->available_tokens > 0) {
            return 0;
        }
        
        return ceil(1 / $this->refill_rate);
    }
}

Caching API Responses

Caching is essential for reducing API calls, improving performance, and avoiding rate limits. Here's how to implement effective caching:

When to Cache

  1. GET requests: Cache responses from GET requests, as they typically return the same data for the same parameters
  2. Rarely changing data: Cache data that doesn't change frequently
  3. Expensive operations: Cache responses from operations that are computationally expensive or have high latency

Implementing Caching with WordPress Transients

WordPress transients provide a simple way to cache API responses:

namespace MyCompany\ThirdParty\Cache;

/**
 * API response cache manager
 */
class CacheManager {
    /** @var int Default cache duration */
    private $default_duration = 300; // 5 minutes
    
    /**
     * Get cached data
     *
     * @param string $endpoint API endpoint
     * @param array $params Query parameters
     * @param int $duration Cache duration in seconds
     * @return array|false Response data or false if not cached
     */
    public function get_cached($endpoint, $params = [], $duration = null) {
        $cache_key = 'my_integration_' . md5($endpoint . serialize($params));
        
        // Check cache
        $cached = get_transient($cache_key);
        if (false !== $cached) {
            return $cached;
        }
        
        return false;
    }
    
    /**
     * Cache data
     *
     * @param string $endpoint API endpoint
     * @param array $params Query parameters
     * @param mixed $data Data to cache
     * @param int $duration Cache duration in seconds
     */
    public function cache_data($endpoint, $params, $data, $duration = null) {
        $cache_key = 'my_integration_' . md5($endpoint . serialize($params));
        $duration = $duration ?? $this->default_duration;
        
        set_transient($cache_key, $data, $duration);
    }
    
    /**
     * Invalidate cache
     *
     * @param string $endpoint API endpoint
     * @param array $params Query parameters
     */
    public function invalidate_cache($endpoint, $params = []) {
        $cache_key = 'my_integration_' . md5($endpoint . serialize($params));
        delete_transient($cache_key);
    }
    
    /**
     * Get cache duration for an endpoint
     *
     * @param string $endpoint API endpoint
     * @return int Cache duration in seconds
     */
    public function get_cache_duration($endpoint) {
        $durations = [
            'user' => 60,           // User data: 1 minute
            'posts' => 300,         // Posts: 5 minutes
            'comments' => 180,      // Comments: 3 minutes
            'settings' => 3600,     // Settings: 1 hour
            'static' => 86400       // Static data: 24 hours
        ];
        
        foreach ($durations as $key => $duration) {
            if (strpos($endpoint, $key) !== false) {
                return $duration;
            }
        }
        
        return $this->default_duration;
    }
}

Step 4: Advanced Integration Techniques

Step 4: Advanced Integration Techniques

Handling WooCommerce Webhooks

WooCommerce webhooks can be used to receive real-time notifications about events in your WooCommerce stores:

namespace MyCompany\WooCommerce\Webhooks;

use WP_REST_Request;
use WP_REST_Response;
use Exception;

/**
 * Register webhook handlers
 */
public function register_webhook_handlers() {
    // Register webhook endpoint
    add_action('rest_api_init', function() {
        register_rest_route('mainwp-woocommerce/v1', '/webhook/(?P<website_id>\d+)', [
            'methods' => 'POST',
            'callback' => [$this, 'handle_webhook'],
            'permission_callback' => [$this, 'verify_webhook'],
        ]);
    });
}

/**
 * Verify webhook request
 * 
 * @param WP_REST_Request $request Request object
 * @return bool Whether the request is valid
 */
public function verify_webhook($request) {
    // Validate website ID
    $website_id = absint($request->get_param('website_id'));
    if ($website_id <= 0) {
        return false;
    }
    
    $signature = $request->get_header('X-WC-Webhook-Signature');
    if (empty($signature)) {
        // Log missing signature if WP_DEBUG_LOG is enabled
        if (defined('WP_DEBUG_LOG') && WP_DEBUG_LOG) {
            error_log(sprintf(
                '[MainWP WooCommerce] Missing webhook signature for website ID: %d',
                $website_id
            ));
        }
        return false;
    }
    
    // Get webhook secret for this site
    $settings = get_option('mainwp_woocommerce_settings', []);
    $site_settings = isset($settings[$website_id]) ? $settings[$website_id] : [];
    
    if (empty($site_settings['webhook_secret'])) {
        // Log missing webhook secret if WP_DEBUG_LOG is enabled
        if (defined('WP_DEBUG_LOG') && WP_DEBUG_LOG) {
            error_log(sprintf(
                '[MainWP WooCommerce] Webhook secret not configured for website ID: %d',
                $website_id
            ));
        }
        return false;
    }
    
    // Verify signature with constant-time comparison to prevent timing attacks
    $payload = $request->get_body();
    $calculated_signature = hash_hmac('sha256', $payload, $site_settings['webhook_secret']);
    
    return hash_equals($calculated_signature, $signature);
}

/**
 * Handle webhook request
 * 
 * @param WP_REST_Request $request Request object
 * @return WP_REST_Response Response object
 */
public function handle_webhook($request) {
    $website_id = absint($request->get_param('website_id'));
    $payload = json_decode($request->get_body(), true);
    
    if (!$payload || !isset($payload['topic'])) {
        return new WP_REST_Response([
            'success' => false,
            'message' => 'Invalid payload or missing topic'
        ], 400);
    }
    
    try {
        // Process webhook based on topic
        switch ($payload['topic']) {
            case 'order.created':
                $this->process_new_order($website_id, $payload['data']);
                break;
            case 'product.updated':
                $this->process_product_update($website_id, $payload['data']);
                break;
            default:
                // Log unknown topic if WP_DEBUG_LOG is enabled
                if (defined('WP_DEBUG_LOG') && WP_DEBUG_LOG) {
                    error_log(sprintf(
                        '[MainWP WooCommerce] Unknown webhook topic: %s for website ID: %d',
                        $payload['topic'],
                        $website_id
                    ));
                }
                return new WP_REST_Response([
                    'success' => false,
                    'message' => 'Unknown webhook topic'
                ], 400);
        }
        
        return new WP_REST_Response(['success' => true]);
    } catch (Exception $e) {
        // Log exception if WP_DEBUG_LOG is enabled
        if (defined('WP_DEBUG_LOG') && WP_DEBUG_LOG) {
            error_log(sprintf(
                '[MainWP WooCommerce] Webhook processing error: %s for website ID: %d',
                $e->getMessage(),
                $website_id
            ));
        }
        
        return new WP_REST_Response([
            'success' => false,
            'message' => 'Error processing webhook: ' . $e->getMessage()
        ], 500);
    }
}

/**
 * Process new order webhook
 * 
 * @param int $website_id Website ID
 * @param array $data Order data
 */
private function process_new_order($website_id, $data) {
    // Validate inputs
    $website_id = absint($website_id);
    if ($website_id <= 0 || empty($data) || !is_array($data)) {
        return;
    }
    
    // Process new order
    // For example, send notification, update dashboard, etc.
}

Implementing Background Processing

For operations that may take a long time, implement background processing:

namespace MyCompany\WooCommerce\Background;

use MainWP\Dashboard\MainWP_DB;
use MainWP\Dashboard\MainWP_Connect;
use WP_Error;
use Exception;

/**
 * Schedule background sync
 */
public function schedule_background_sync() {
    if (!wp_next_scheduled('mainwp_woocommerce_background_sync')) {
        wp_schedule_event(time(), 'hourly', 'mainwp_woocommerce_background_sync');
    }
    
    // Hook into the scheduled event
    add_action('mainwp_woocommerce_background_sync', [$this, 'do_background_sync']);
}

/**
 * Perform background sync
 */
public function do_background_sync() {
    // Get all websites with WooCommerce
    $websites = MainWP_DB::instance()->get_websites_by_plugin_name('woocommerce/woocommerce.php');
    
    foreach ($websites as $website) {
        // Skip sites without API credentials
        $api_client = $this->get_api_client($website->id);
        if (!$api_client) {
            continue;
        }
        
        // Sync products
        $this->sync_products($website->id, $api_client);
        
        // Sync orders
        $this->sync_orders($website->id, $api_client);
        
        // Sync other data as needed
    }
}

/**
 * Sync products for a site
 * 
 * @param int $website_id Website ID
 * @param WooCommerceAPIClient $api_client API client
 */
private function sync_products($website_id, $api_client) {
    // Get products from the site
    $products = $api_client->get_products(['per_page' => 100]);
    
    if (is_wp_error($products)) {
        // Log error
        return;
    }
    
    // Process products
    // For example, store in database, update inventory, etc.
}

Synchronizing Inventory Across Stores

Implement inventory synchronization across multiple stores:

namespace MyCompany\WooCommerce\Sync;

use WP_Error;

/**
 * Synchronize inventory across stores
 * 
 * @param array $website_ids Array of website IDs to sync
 * @param array $product_data Array of product IDs and stock quantities
 * @return array Results
 */
public function sync_inventory($website_ids, $product_data) {
    $results = [];
    
    foreach ($website_ids as $website_id) {
        $api_client = $this->get_api_client($website_id);
        if (!$api_client) {
            $results[$website_id] = [
                'success' => false,
                'message' => 'API credentials not configured'
            ];
            continue;
        }
        
        $product_manager = new ProductManager($api_client);
        $site_results = [];
        
        foreach ($product_data as $product_id => $stock_quantity) {
            $result = $product_manager->update_stock($product_id, $stock_quantity);
            
            $site_results[$product_id] = [
                'success' => !is_wp_error($result),
                'message' => is_wp_error($result) ? $result->get_error_message() : 'Success'
            ];
        }
        
        $results[$website_id] = $site_results;
    }
    
    return $results;
}

Common Challenges and Solutions

API Authentication Issues

If you encounter API authentication issues:

  1. Check API Keys: Ensure the Consumer Key and Consumer Secret are correct.
  2. Verify Permissions: Make sure the API keys have the necessary permissions.
  3. Check SSL: WooCommerce API requires SSL for secure connections.
namespace MyCompany\WooCommerce\Diagnostics;

use MainWP\Dashboard\MainWP_DB;
use WP_Error;

/**
 * Troubleshoot API connection
 * 
 * @param int $website_id Website ID
 * @return array Troubleshooting results
 */
public function troubleshoot_api_connection($website_id) {
    $results = [];
    
    // Check if website exists
    $website = MainWP_DB::instance()->get_website_by_id($website_id);
    if (!$website) {
        $results['website'] = 'Website not found';
        return $results;
    }
    
    // Check if WooCommerce is active
    $is_woocommerce_active = MainWP_DB::instance()->is_plugin_active_on_site($website_id, 'woocommerce/woocommerce.php');
    $results['woocommerce'] = $is_woocommerce_active ? 'Active' : 'Not active';
    
    // Check API credentials
    $settings = get_option('mainwp_woocommerce_settings', []);
    $site_settings = isset($settings[$website_id]) ? $settings[$website_id] : [];
    
    $results['api_credentials'] = (!empty($site_settings['consumer_key']) && !empty($site_settings['consumer_secret'])) ? 'Configured' : 'Not configured';
    
    // Test API connection
    if ($results['api_credentials'] === 'Configured') {
        $api_client = $this->get_api_client($website_id);
        $test_result = $api_client->get_store_info();
        
        $results['api_connection'] = !is_wp_error($test_result) ? 'Success' : 'Failed: ' . $test_result->get_error_message();
    }
    
    return $results;
}

Handling Large Product Catalogs

For stores with large product catalogs:

  1. Implement Pagination: Use pagination to retrieve products in batches.
  2. Optimize Caching: Cache product data to reduce API calls.
  3. Use Background Processing: Process large catalogs in the background.
namespace MyCompany\WooCommerce\Products;

use WP_Error;

/**
 * Get all products with pagination
 * 
 * @param int $website_id Website ID
 * @return array|WP_Error Products or error
 */
public function get_all_products($website_id) {
    $api_client = $this->get_api_client($website_id);
    if (!$api_client) {
        return new WP_Error('api_error', 'API credentials not configured');
    }
    
    $all_products = [];
    $page = 1;
    $per_page = 100;
    
    while (true) {
        $products = $api_client->get_products([
            'page' => $page,
            'per_page' => $per_page
        ]);
        
        if (is_wp_error($products)) {
            return $products;
        }
        
        if (empty($products)) {
            break;
        }
        
        $all_products = array_merge($all_products, $products);
        $page++;
    }
    
    return $all_products;
}

Managing Different WooCommerce Versions

To handle different WooCommerce versions across your network:

  1. Version Detection: Detect WooCommerce version on each site.
  2. Conditional Logic: Implement conditional logic based on version.
  3. Fallback Mechanisms: Provide fallback mechanisms for older versions.
namespace MyCompany\WooCommerce\Compatibility;

use MainWP\Dashboard\MainWP_DB;
use WP_Error;

/**
 * Get WooCommerce version for a site
 * 
 * @param int $website_id Website ID
 * @return string|WP_Error WooCommerce version or error
 */
public function get_woocommerce_version($website_id) {
    $website = MainWP_DB::instance()->get_website_by_id($website_id);
    if (!$website) {
        return new WP_Error('website_error', 'Website not found');
    }
    
    // Get plugin data from the site
    $plugins = json_decode($website->plugins, true);
    if (!$plugins) {
        return new WP_Error('plugins_error', 'Unable to get plugins data');
    }
    
    // Find WooCommerce plugin
    foreach ($plugins as $plugin) {
        if ($plugin['slug'] === 'woocommerce/woocommerce.php') {
            return $plugin['version'];
        }
    }
    
    return new WP_Error('woocommerce_error', 'WooCommerce not found');
}

/**
 * Check if WooCommerce version is compatible
 * 
 * @param string $version WooCommerce version
 * @param string $min_version Minimum required version
 * @return bool Whether the version is compatible
 */
public function is_woocommerce_compatible($version, $min_version = '3.0.0') {
    return version_compare($version, $min_version, '>=');
}

Next Steps and Resources

After implementing your WooCommerce integration, you might want to explore:

Resources

Creating a MainWP WooCommerce Integration

This guide walks you through the process of creating a MainWP integration with WooCommerce, allowing you to manage and monitor WooCommerce stores across your MainWP network.

Quick Start Available: If you're an experienced developer looking for just the essential code, check out our WooCommerce Integration Quick Start Guide.

For advanced features and implementation details, see Part 2 of this guide.

Prerequisites

Before you begin creating a MainWP WooCommerce integration, ensure you have:

Integration Overview

A MainWP WooCommerce integration allows you to centrally manage and monitor WooCommerce stores across your MainWP network. This provides several benefits:

Data Flow Architecture

The integration works by communicating between the MainWP Dashboard and WooCommerce on child sites:

flowchart TD A[MainWP Dashboard] -->|API Request| B[MainWP Child] B -->|WC API Request| C[WooCommerce] C -->|WC API Response| B B -->|API Response| A D[WooCommerce Data] -->|Stored During Sync| A

Step 1: Setting Up WooCommerce API Access

To communicate with WooCommerce on child sites, you'll need to set up API access:

Creating API Keys on Child Sites

For each child site, you'll need to create WooCommerce API keys:

  1. On the child site, go to WooCommerce → Settings → Advanced → REST API
  2. Click "Add key"
  3. Enter a description (e.g., "MainWP Integration")
  4. Set permissions to "Read/Write"
  5. Click "Generate API key"
  6. Save the Consumer Key and Consumer Secret

Storing API Credentials Securely

In your integration, you'll need to securely store these credentials:

namespace MyCompany\WooCommerce;

/**
 * Save API credentials for a site
 * 
 * @param int $website_id The website ID
 * @param string $consumer_key WooCommerce API consumer key
 * @param string $consumer_secret WooCommerce API consumer secret
 * @return bool Success or failure
 */
public function save_api_credentials($website_id, $consumer_key, $consumer_secret) {
    // Validate inputs
    $website_id = absint($website_id);
    if ($website_id <= 0) {
        return false;
    }
    
    // Sanitize credentials
    $consumer_key = sanitize_text_field($consumer_key);
    $consumer_secret = sanitize_text_field($consumer_secret);
    
    // Get current settings
    $settings = get_option('mainwp_woocommerce_settings', []);
    
    // Update settings for this site
    $settings[$website_id] = [
        'consumer_key' => $consumer_key,
        'consumer_secret' => $consumer_secret
    ];
    
    // Save settings
    return update_option('mainwp_woocommerce_settings', $settings);
}

Testing API Connectivity

Always test the API connection before performing operations:

namespace MyCompany\WooCommerce;

use Exception;
use WP_Error;

/**
 * Test WooCommerce API connection for a site
 * 
 * @param int $website_id The website ID
 * @return bool|string True on success, error message on failure
 */
public function test_api_connection($website_id) {
    // Validate website ID
    $website_id = absint($website_id);
    if ($website_id <= 0) {
        return 'Invalid website ID';
    }
    
    // Get API client
    $api_client = $this->get_api_client($website_id);
    if (!$api_client) {
        return 'API credentials not configured';
    }
    
    try {
        // Try to get store information
        $result = $api_client->get_store_info();
        
        if (is_wp_error($result)) {
            $error_message = $result->get_error_message();
            
            // Log error if WP_DEBUG_LOG is enabled
            if (defined('WP_DEBUG_LOG') && WP_DEBUG_LOG) {
                error_log(sprintf(
                    '[MainWP WooCommerce] API connection test failed for website ID %d: %s',
                    $website_id,
                    $error_message
                ));
            }
            
            return $error_message;
        }
        
        return true;
    } catch (Exception $e) {
        $error_message = $e->getMessage();
        
        // Log exception if WP_DEBUG_LOG is enabled
        if (defined('WP_DEBUG_LOG') && WP_DEBUG_LOG) {
            error_log(sprintf(
                '[MainWP WooCommerce] API connection test exception for website ID %d: %s',
                $website_id,
                $error_message
            ));
        }
        
        return 'Connection error: ' . $error_message;
    }
}

Step 2: Core Integration Components

WooCommerce API Client

Create a dedicated class for handling API communication:

namespace MyCompany\WooCommerce;

use MainWP\Dashboard\MainWP_Connect;
use WP_Error;
use Exception;

/**
 * WooCommerce API Client
 */
class WooCommerceAPIClient {
    /** @var object Website object */
    private $website;
    /** @var string Consumer key */
    private $consumer_key;
    /** @var string Consumer secret */
    private $consumer_secret;
    /** @var int Cache expiration in seconds */
    private $cache_expiration = 300; // 5 minutes
    
    /**
     * Constructor
     * 
     * @param object $website Website object
     * @param string $consumer_key WooCommerce API consumer key
     * @param string $consumer_secret WooCommerce API consumer secret
     */
    public function __construct($website, $consumer_key, $consumer_secret) {
        $this->website = $website;
        $this->consumer_key = $consumer_key;
        $this->consumer_secret = $consumer_secret;
    }
    
    /**
     * Get store information
     * 
     * @return array|WP_Error Store info or error
     */
    public function get_store_info() {
        return $this->make_api_request('');
    }
    
    /**
     * Get products from the WooCommerce store
     * 
     * @param array $args Query arguments
     * @return array|WP_Error Products or error
     */
    public function get_products($args = []) {
        // Check cache first
        $cache_key = 'mainwp_wc_products_' . $this->website->id . '_' . md5(serialize($args));
        $cached_data = get_transient($cache_key);
        
        if (false !== $cached_data) {
            return $cached_data;
        }
        
        // Make API request
        $products = $this->make_api_request('products', 'GET', $args);
        
        // Cache the result if not an error
        if (!is_wp_error($products)) {
            set_transient($cache_key, $products, $this->cache_expiration);
        }
        
        return $products;
    }
    
    /**
     * Get orders from the WooCommerce store
     * 
     * @param array $args Query arguments
     * @return array|WP_Error Orders or error
     */
    public function get_orders($args = []) {
        // For orders, use a shorter cache time or no cache for recent orders
        $use_cache = !isset($args['after']) || strtotime($args['after']) < (time() - 3600);
        
        if ($use_cache) {
            $cache_key = 'mainwp_wc_orders_' . $this->website->id . '_' . md5(serialize($args));
            $cached_data = get_transient($cache_key);
            
            if (false !== $cached_data) {
                return $cached_data;
            }
        }
        
        // Make API request
        $orders = $this->make_api_request('orders', 'GET', $args);
        
        // Cache the result if using cache and not an error
        if ($use_cache && !is_wp_error($orders)) {
            set_transient($cache_key, $orders, $this->cache_expiration);
        }
        
        return $orders;
    }
    
    /**
     * Update a product on the WooCommerce store
     * 
     * @param int $product_id Product ID
     * @param array $data Product data
     * @return array|WP_Error Updated product or error
     */
    public function update_product($product_id, $data) {
        // Clear product cache
        $this->clear_product_cache();
        
        return $this->make_api_request("products/{$product_id}", 'PUT', $data);
    }
    
    /**
     * Clear product cache
     */
    private function clear_product_cache() {
        global $wpdb;
        
        // Find and delete all product cache transients for this site
        $like = $wpdb->esc_like('_transient_mainwp_wc_products_' . $this->website->id) . '%';
        $wpdb->query($wpdb->prepare("DELETE FROM $wpdb->options WHERE option_name LIKE %s", $like));
    }
    
    /**
     * Make an API request to the WooCommerce REST API
     * 
     * @param string $endpoint API endpoint
     * @param string $method HTTP method
     * @param array $args Request arguments
     * @return array|WP_Error Response or error
     */
    private function make_api_request($endpoint, $method = 'GET', $args = []) {
        // Prepare the request data for MainWP
        $data = [
            'wc_api' => true,
            'endpoint' => $endpoint,
            'method' => $method,
            'args' => $args,
            'consumer_key' => $this->consumer_key,
            'consumer_secret' => $this->consumer_secret
        ];
        
        try {
            // Send the request through MainWP
            $information = MainWP_Connect::fetch_url_authed(
                $this->website,
                'woocommerce_integration',
                $data
            );
            
            // Check for errors in the response
            if (is_array($information) && isset($information['error'])) {
                return new WP_Error('api_error', $information['error']);
            }
            
            return $information;
        } catch (Exception $e) {
            return new WP_Error('api_error', $e->getMessage());
        }
    }
}

Data Models

Create models for WooCommerce data:

namespace MyCompany\WooCommerce\Models;

/**
 * Product Model
 */
class Product {
    /** @var int Product ID */
    public $id;
    /** @var string Product name */
    public $name;
    /** @var string Product slug */
    public $slug;
    /** @var string Product permalink */
    public $permalink;
    /** @var string Date created */
    public $date_created;
    /** @var string Date modified */
    public $date_modified;
    /** @var string Product type */
    public $type;
    /** @var string Product status */
    public $status;
    /** @var bool Featured status */
    public $featured;
    /** @var string Catalog visibility */
    public $catalog_visibility;
    /** @var string Product description */
    public $description;
    /** @var string Short description */
    public $short_description;
    /** @var string SKU */
    public $sku;
    /** @var string Price */
    public $price;
    /** @var string Regular price */
    public $regular_price;
    /** @var string Sale price */
    public $sale_price;
    /** @var string Date on sale from */
    public $date_on_sale_from;
    /** @var string Date on sale to */
    public $date_on_sale_to;
    /** @var bool Virtual product */
    public $virtual;
    /** @var bool Downloadable product */
    public $downloadable;
    /** @var array Downloads */
    public $downloads;
    /** @var int Download limit */
    public $download_limit;
    /** @var int Download expiry */
    public $download_expiry;
    /** @var string Tax status */
    public $tax_status;
    /** @var string Tax class */
    public $tax_class;
    /** @var bool Manage stock */
    public $manage_stock;
    /** @var int|null Stock quantity */
    public $stock_quantity;
    /** @var string Stock status */
    public $stock_status;
    /** @var string Backorders */
    public $backorders;
    /** @var bool Backorders allowed */
    public $backorders_allowed;
    /** @var bool Backordered */
    public $backordered;
    /** @var bool Sold individually */
    public $sold_individually;
    /** @var string Weight */
    public $weight;
    /** @var array Dimensions */
    public $dimensions;
    /** @var bool Shipping required */
    public $shipping_required;
    /** @var bool Shipping taxable */
    public $shipping_taxable;
    /** @var string Shipping class */
    public $shipping_class;
    /** @var int Shipping class ID */
    public $shipping_class_id;
    /** @var bool Reviews allowed */
    public $reviews_allowed;
    /** @var string Average rating */
    public $average_rating;
    /** @var int Rating count */
    public $rating_count;
    /** @var array Related IDs */
    public $related_ids;
    /** @var array Upsell IDs */
    public $upsell_ids;
    /** @var array Cross sell IDs */
    public $cross_sell_ids;
    /** @var int Parent ID */
    public $parent_id;
    /** @var string Purchase note */
    public $purchase_note;
    /** @var array Categories */
    public $categories;
    /** @var array Tags */
    public $tags;
    /** @var array Images */
    public $images;
    /** @var array Attributes */
    public $attributes;
    /** @var array Default attributes */
    public $default_attributes;
    /** @var array Variations */
    public $variations;
    /** @var array Grouped products */
    public $grouped_products;
    /** @var int Menu order */
    public $menu_order;
    
    /**
     * Create a product from API data
     * 
     * @param array $data Product data from API
     * @return Product Product object
     */
    public static function from_api_data($data) {
        $product = new self();
        
        // Map API data to object properties
        foreach ($data as $key => $value) {
            if (property_exists($product, $key)) {
                $product->$key = $value;
            }
        }
        
        return $product;
    }
}

/**
 * Order Model
 */
class Order {
    /** @var int Order ID */
    public $id;
    /** @var int Parent ID */
    public $parent_id;
    /** @var string Order number */
    public $number;
    /** @var string Order key */
    public $order_key;
    /** @var string Created via */
    public $created_via;
    /** @var string Version */
    public $version;
    /** @var string Status */
    public $status;
    /** @var string Currency */
    public $currency;
    /** @var string Date created */
    public $date_created;
    /** @var string Date modified */
    public $date_modified;
    /** @var string Discount total */
    public $discount_total;
    /** @var string Discount tax */
    public $discount_tax;
    /** @var string Shipping total */
    public $shipping_total;
    /** @var string Shipping tax */
    public $shipping_tax;
    /** @var string Cart tax */
    public $cart_tax;
    /** @var string Total */
    public $total;
    /** @var string Total tax */
    public $total_tax;
    /** @var bool Prices include tax */
    public $prices_include_tax;
    /** @var int Customer ID */
    public $customer_id;
    /** @var string Customer note */
    public $customer_note;
    /** @var array Billing details */
    public $billing;
    /** @var array Shipping details */
    public $shipping;
    /** @var string Payment method */
    public $payment_method;
    /** @var string Payment method title */
    public $payment_method_title;
    /** @var string Transaction ID */
    public $transaction_id;
    /** @var string Customer IP address */
    public $customer_ip_address;
    /** @var string Customer user agent */
    public $customer_user_agent;
    /** @var array Line items */
    public $line_items;
    /** @var array Tax lines */
    public $tax_lines;
    /** @var array Shipping lines */
    public $shipping_lines;
    /** @var array Fee lines */
    public $fee_lines;
    /** @var array Coupon lines */
    public $coupon_lines;
    /** @var array Refunds */
    public $refunds;
    
    /**
     * Create an order from API data
     * 
     * @param array $data Order data from API
     * @return Order Order object
     */
    public static function from_api_data($data) {
        $order = new self();
        
        // Map API data to object properties
        foreach ($data as $key => $value) {
            if (property_exists($order, $key)) {
                $order->$key = $value;
            }
        }
        
        return $order;
    }
}

Caching Strategy

Implement caching to improve performance and reduce API calls:

namespace MyCompany\WooCommerce;

/**
 * Cache manager for WooCommerce data
 */
class CacheManager {
    /** @var int Default cache expiration in seconds */
    private $default_expiration = 300; // 5 minutes
    
    /**
     * Get cached data
     * 
     * @param string $key Cache key
     * @return mixed|false Cached data or false if not found
     */
    public function get($key) {
        return get_transient($key);
    }
    
    /**
     * Set cached data
     * 
     * @param string $key Cache key
     * @param mixed $data Data to cache
     * @param int|null $expiration Cache expiration in seconds
     * @return bool Success or failure
     */
    public function set($key, $data, $expiration = null) {
        if (null === $expiration) {
            $expiration = $this->default_expiration;
        }
        
        return set_transient($key, $data, $expiration);
    }
    
    /**
     * Delete cached data
     * 
     * @param string $key Cache key
     * @return bool Success or failure
     */
    public function delete($key) {
        return delete_transient($key);
    }
    
    /**
     * Delete all cache for a specific site
     * 
     * @param int $website_id Website ID
     * @return void
     */
    public function clear_site_cache($website_id) {
        global $wpdb;
        
        // Find and delete all cache transients for this site
        $like = $wpdb->esc_like('_transient_mainwp_wc_') . $website_id . '%';
        $wpdb->query($wpdb->prepare("DELETE FROM $wpdb->options WHERE option_name LIKE %s", $like));
    }
}

Step 3: Implementing Key Features

Product Management

Implement product management functionality:

namespace MyCompany\WooCommerce;

use MyCompany\WooCommerce\Models\Product;
use WP_Error;

/**
 * Product Manager
 */
class ProductManager {
    /** @var WooCommerceAPIClient API client */
    private $api_client;
    
    /**
     * Constructor
     * 
     * @param WooCommerceAPIClient $api_client API client
     */
    public function __construct($api_client) {
        $this->api_client = $api_client;
    }
    
    /**
     * Get products
     * 
     * @param array $args Query arguments
     * @return array Products
     */
    public function get_products($args = []) {
        $products = $this->api_client->get_products($args);
        
        if (is_wp_error($products)) {
            return [];
        }
        
        // Convert to product objects
        $product_objects = [];
        foreach ($products as $product_data) {
            $product_objects[] = Product::from_api_data($product_data);
        }
        
        return $product_objects;
    }
    
    /**
     * Get a single product
     * 
     * @param int $product_id Product ID
     * @return Product|null Product or null on failure
     */
    public function get_product($product_id) {
        $product = $this->api_client->make_api_request("products/{$product_id}");
        
        if (is_wp_error($product)) {
            return null;
        }
        
        return Product::from_api_data($product);
    }
    
    /**
     * Update a product
     * 
     * @param int $product_id Product ID
     * @param array $data Product data
     * @return Product|WP_Error Updated product or error
     */
    public function update_product($product_id, $data) {
        $result = $this->api_client->update_product($product_id, $data);
        
        if (is_wp_error($result)) {
            return $result;
        }
        
        return Product::from_api_data($result);
    }
    
    /**
     * Update product stock
     * 
     * @param int $product_id Product ID
     * @param int $stock_quantity Stock quantity
     * @return Product|WP_Error Updated product or error
     */
    public function update_stock($product_id, $stock_quantity) {
        return $this->update_product($product_id, [
            'stock_quantity' => $stock_quantity
        ]);
    }
    
    /**
     * Bulk update products
     * 
     * @param array $products Array of product IDs and data
     * @return array Results
     */
    public function bulk_update_products($products) {
        $results = [];
        
        foreach ($products as $product) {
            if (!isset($product['id']) || !isset($product['data'])) {
                continue;
            }
            
            $result = $this->update_product($product['id'], $product['data']);
            
            $results[] = [
                'id' => $product['id'],
                'success' => !is_wp_error($result),
                'message' => is_wp_error($result) ? $result->get_error_message() : 'Success'
            ];
        }
        
        return $results;
    }
}

Order Monitoring

Implement order monitoring functionality:

namespace MyCompany\WooCommerce;

use MyCompany\WooCommerce\Models\Order;
use WP_Error;

/**
 * Order Manager
 */
class OrderManager {
    /** @var WooCommerceAPIClient API client */
    private $api_client;
    
    /**
     * Constructor
     * 
     * @param WooCommerceAPIClient $api_client API client
     */
    public function __construct($api_client) {
        $this->api_client = $api_client;
    }
    
    /**
     * Get orders
     * 
     * @param array $args Query arguments
     * @return array Orders
     */
    public function get_orders($args = []) {
        $orders = $this->api_client->get_orders($args);
        
        if (is_wp_error($orders)) {
            return [];
        }
        
        // Convert to order objects
        $order_objects = [];
        foreach ($orders as $order_data) {
            $order_objects[] = Order::from_api_data($order_data);
        }
        
        return $order_objects;
    }
    
    /**
     * Get a single order
     * 
     * @param int $order_id Order ID
     * @return Order|null Order or null on failure
     */
    public function get_order($order_id) {
        $order = $this->api_client->make_api_request("orders/{$order_id}");
        
        if (is_wp_error($order)) {
            return null;
        }
        
        return Order::from_api_data($order);
    }
}

Best Practices for WooCommerce Integration

When developing your WooCommerce integration, consider these best practices:

Security Best Practices

  1. Use HTTPS: Always ensure your MainWP Dashboard and child sites use HTTPS to protect API credentials during transmission.

  2. Validate All User Input: Sanitize and validate all user-provided data before using it in database queries or API requests:

    // Validate website ID
    $website_id = absint($website_id);
    if ($website_id <= 0) {
        return false;
    }
    
    // Sanitize API credentials
    $consumer_key = sanitize_text_field($consumer_key);
    $consumer_secret = sanitize_text_field($consumer_secret);
    
  3. Use Appropriate Capability Checks: Ensure admin pages use manage_options capability instead of just read:

    // Check if user has permissions
    if (!current_user_can('manage_options')) {
        wp_die(__('You do not have sufficient permissions to access this page.', 'mainwp-woocommerce'));
        return;
    }
    

Performance Best Practices

  1. Implement Smart Caching: Adjust cache expiration times based on data volatility. For example, product data might be cached longer than order data:

    // For relatively static data like products
    set_transient($cache_key, $products, 3600); // 1 hour
    
    // For more volatile data like recent orders
    set_transient($cache_key, $orders, 300); // 5 minutes
    
  2. Use Pagination for Large Datasets: Always implement pagination when retrieving large datasets to avoid memory issues and timeout errors:

    $products = $api_client->get_products([
        'page' => $page,
        'per_page' => 100
    ]);
    
  3. Implement Defensive Programming: Always validate data and handle edge cases:

    // Ensure args is an array
    $args = is_array($args) ? $args : [];
    
    // Check for errors before processing
    if (is_wp_error($products) || !is_array($products)) {
        return [];
    }
    

Step 4: Advanced Integration Techniques

For advanced integration techniques and more detailed examples, please see Part 2 of this guide.

This continuation covers:

MainWP WooCommerce Integration: Quick Start Guide

This quick start guide provides the essential code and steps to implement a MainWP WooCommerce integration. For more detailed explanations and advanced features, see the complete WooCommerce Integration guide.

Prerequisites

Quick Implementation

<?php
/**
 * Plugin Name: MainWP WooCommerce Integration
 * Plugin URI: https://yourwebsite.com/mainwp-woocommerce
 * Description: Integrates MainWP with WooCommerce for centralized store management.
 * Version: 1.0.0
 * Author: Your Name
 * Author URI: https://yourwebsite.com
 * Text Domain: mainwp-woocommerce
 * WC requires at least: 5.0.0
 * WC tested up to: 8.0.0
 */

namespace YourCompany\MainWPWooCommerce;

use MainWP\Dashboard\MainWP_DB;
use MainWP\Dashboard\MainWP_Connect;
use WP_Error;
use Exception;

// Prevent direct access
if (!defined('ABSPATH')) {
    exit;
}

// Register the integration with MainWP
add_filter('mainwp_getextensions', function($extensions) {
    $extensions[] = [
        'plugin' => __FILE__,
        'api' => 'MainWPWooCommerce',
        'mainwp' => true,
        'callback' => [__NAMESPACE__ . '\\MainWPWooCommerce', 'get_instance'],
        'name' => 'WooCommerce Integration'
    ];
    return $extensions;
});

class MainWPWooCommerce {
    /** @var MainWPWooCommerce|null Instance */
    private static $instance = null;
    /** @var WooCommerceAPIClient|null API Client */
    private $api_client = null;
    
    /**
     * Get singleton instance
     * @return MainWPWooCommerce Instance of the plugin
     */
    public static function get_instance() {
        if (null === self::$instance) {
            self::$instance = new self();
        }
        return self::$instance;
    }
    
    /**
     * Constructor
     */
    public function __construct() {
        // Initialize the integration
        add_action('admin_init', [$this, 'admin_init']);
        add_action('mainwp_admin_menu', [$this, 'init_menu']);
        
        // Hook into site sync to collect WooCommerce data
        add_action('mainwp_site_synced', [$this, 'site_synced'], 10, 2);
        
        // Add WooCommerce data to individual site view
        add_filter('mainwp_getsubpages_sites', [$this, 'add_woocommerce_site_tab'], 10, 2);
    }
    
    /**
     * Initialize admin functionality
     */
    public function admin_init() {
        // Register settings
        register_setting('mainwp_woocommerce_settings_group', 'mainwp_woocommerce_settings');
    }
    
    /**
     * Initialize menu items
     */
    public function init_menu() {
        // Add WooCommerce dashboard page
        add_submenu_page(
            'mainwp_tab',
            __('WooCommerce', 'mainwp-woocommerce'),
            __('WooCommerce', 'mainwp-woocommerce'),
            'manage_options', // Use manage_options instead of read for better security
            'MainWPWooCommerce',
            [$this, 'render_dashboard_page']
        );
        
        // Add settings page
        add_submenu_page(
            'mainwp_tab',
            __('WooCommerce Settings', 'mainwp-woocommerce'),
            __('WooCommerce Settings', 'mainwp-woocommerce'),
            'manage_options', // Use manage_options instead of read for better security
            'MainWPWooCommerceSettings',
            [$this, 'render_settings_page']
        );
    }
    
    /**
     * Process data after a site is synced
     * 
     * @param object $website The website object
     * @param array $data The sync data
     */
    public function site_synced($website, $data) {
        // Check if WooCommerce data is available
        if (isset($data['woocommerce'])) {
            // Store the WooCommerce data for this site
            update_option('mainwp_woocommerce_data_' . $website->id, $data['woocommerce']);
        }
    }
    
    /**
     * Add WooCommerce tab to individual site view
     * 
     * @param array $subpages Current subpages
     * @param object $website The website object
     * @return array Modified subpages
     */
    public function add_woocommerce_site_tab($subpages, $website) {
        $subpages[] = [
            'title' => __('WooCommerce', 'mainwp-woocommerce'),
            'slug' => 'WooCommerce',
            'callback' => [$this, 'render_site_woocommerce_tab'],
        ];
        return $subpages;
    }
    
    /**
     * Get WooCommerce API client for a specific site
     * 
     * @param int $website_id The website ID
     * @return WooCommerceAPIClient|null API client or null on failure
     */
    private function get_api_client($website_id) {
        // Validate website ID
        $website_id = absint($website_id);
        if ($website_id <= 0) {
            return null;
        }
        
        // Get the website
        $website = MainWP_DB::instance()->get_website_by_id($website_id);
        if (!$website) {
            return null;
        }
        
        // Get API credentials from options
        $settings = get_option('mainwp_woocommerce_settings', []);
        $site_settings = isset($settings[$website_id]) ? $settings[$website_id] : [];
        
        if (empty($site_settings['consumer_key']) || empty($site_settings['consumer_secret'])) {
            return null;
        }
        
        // Create API client
        return new WooCommerceAPIClient(
            $website,
            $site_settings['consumer_key'],
            $site_settings['consumer_secret']
        );
    }
    
    /**
     * Save API credentials for a site
     * 
     * @param int $website_id The website ID
     * @param string $consumer_key WooCommerce API consumer key
     * @param string $consumer_secret WooCommerce API consumer secret
     * @return bool Success or failure
     */
    public function save_api_credentials($website_id, $consumer_key, $consumer_secret) {
        // Validate inputs
        $website_id = absint($website_id);
        if ($website_id <= 0) {
            return false;
        }
        
        $consumer_key = sanitize_text_field($consumer_key);
        $consumer_secret = sanitize_text_field($consumer_secret);
        
        // Get current settings
        $settings = get_option('mainwp_woocommerce_settings', []);
        
        // Update settings for this site
        $settings[$website_id] = [
            'consumer_key' => $consumer_key,
            'consumer_secret' => $consumer_secret
        ];
        
        // Save settings
        return update_option('mainwp_woocommerce_settings', $settings);
    }
    
    // Additional methods for rendering pages and handling API requests...
}

/**
 * WooCommerce API Client
 */
class WooCommerceAPIClient {
    /** @var object Website object */
    private $website;
    /** @var string Consumer key */
    private $consumer_key;
    /** @var string Consumer secret */
    private $consumer_secret;
    
    /**
     * Constructor
     * 
     * @param object $website Website object
     * @param string $consumer_key WooCommerce API consumer key
     * @param string $consumer_secret WooCommerce API consumer secret
     */
    public function __construct($website, $consumer_key, $consumer_secret) {
        $this->website = $website;
        $this->consumer_key = $consumer_key;
        $this->consumer_secret = $consumer_secret;
    }
    
    /**
     * Get products from the WooCommerce store
     * 
     * @param array $args Query arguments
     * @return array|WP_Error Products or error
     */
    public function get_products($args = []) {
        // Ensure args is an array
        $args = is_array($args) ? $args : [];
        
        return $this->make_api_request('products', 'GET', $args);
    }
    
    /**
     * Get orders from the WooCommerce store
     * 
     * @param array $args Query arguments
     * @return array|WP_Error Orders or error
     */
    public function get_orders($args = []) {
        // Ensure args is an array
        $args = is_array($args) ? $args : [];
        
        return $this->make_api_request('orders', 'GET', $args);
    }
    
    /**
     * Make an API request to the WooCommerce REST API
     * 
     * @param string $endpoint API endpoint
     * @param string $method HTTP method
     * @param array $args Request arguments
     * @return array|WP_Error Response or error
     */
    private function make_api_request($endpoint, $method = 'GET', $args = []) {
        // Prepare the request data for MainWP
        $data = [
            'wc_api' => true,
            'endpoint' => $endpoint,
            'method' => $method,
            'args' => $args,
            'consumer_key' => $this->consumer_key,
            'consumer_secret' => $this->consumer_secret
        ];
        
        try {
            // Send the request through MainWP
            $information = MainWP_Connect::fetch_url_authed(
                $this->website,
                'woocommerce_integration',
                $data
            );
            
            // Check for errors in the response
            if (is_array($information) && isset($information['error'])) {
                $error_message = $information['error'];
                // Log error if WP_DEBUG_LOG is enabled
                if (defined('WP_DEBUG_LOG') && WP_DEBUG_LOG) {
                    error_log(sprintf(
                        '[MainWP WooCommerce] API Error: %s (Endpoint: %s, Method: %s, Site ID: %d)',
                        $error_message,
                        $endpoint,
                        $method,
                        $this->website->id
                    ));
                }
                return new WP_Error('api_error', $error_message);
            }
            
            return $information;
        } catch (Exception $e) {
            $error_message = $e->getMessage();
            // Log exception if WP_DEBUG_LOG is enabled
            if (defined('WP_DEBUG_LOG') && WP_DEBUG_LOG) {
                error_log(sprintf(
                    '[MainWP WooCommerce] API Exception: %s (Endpoint: %s, Method: %s, Site ID: %d)',
                    $error_message,
                    $endpoint,
                    $method,
                    $this->website->id
                ));
            }
            return new WP_Error('api_error', $error_message);
        }
    }
}

Step-by-Step Implementation

  1. Create the Plugin File

    • Create a new PHP file in your WordPress plugins directory
    • Copy the code above into the file
    • Customize the plugin header information
  2. Set Up WooCommerce API Access

    • On each child site, go to WooCommerce → Settings → Advanced → REST API
    • Click "Add key"
    • Enter a description (e.g., "MainWP Integration")
    • Set permissions to "Read/Write"
    • Click "Generate API key"
    • Save the Consumer Key and Consumer Secret
  3. Configure the Integration

    • Activate the plugin in your MainWP Dashboard
    • Go to the WooCommerce Settings page
    • Enter the API credentials for each child site
    • Test the connection
  4. Use the Integration

    • Navigate to the WooCommerce dashboard page
    • View and manage WooCommerce data across your network
    • Perform bulk operations on products and orders

Common Pitfalls to Avoid

  1. API Authentication Issues

    • Ensure your API credentials are correct
    • Verify that your child sites have WooCommerce REST API enabled
    • Check that your permissions are set correctly (Read/Write)
  2. Performance Considerations

    • Implement caching to reduce API calls
    • Use pagination for large datasets
    • Consider background processing for bulk operations
  3. Security Best Practices

    • Always use HTTPS for API communication
    • Store API credentials securely
    • Validate and sanitize all user input
    • Use proper capability checks (manage_options)

Next Steps

For more detailed information and advanced features, refer to:

API Integration Patterns for MainWP Extensions

This reference guide documents common patterns and architectures for integrating third-party APIs with MainWP extensions. These patterns provide proven solutions to recurring integration challenges.

Pattern Selection Decision Tree

Use this decision tree to help you choose the right pattern for your integration needs:

flowchart TD Start[Start] --> Q1{Are you using an
existing API client?} Q1 -->|Yes| Q1a{Does it match your
needs exactly?} Q1 -->|No| Q2
Q1a -->|Yes| P1[Use Adapter Pattern<br>to integrate it]
Q1a -->|No| Q2

Q2{Do you need multiple<br>API client types?}
Q2 -->|Yes| P2[Use Factory Pattern]
Q2 -->|No| Q3

Q3{Do you need a single<br>consistent instance?}
Q3 -->|Yes| P3[Use Singleton Pattern]
Q3 -->|No| P4[Use Basic API Client]

P1 --> Auth{Authentication<br>Method?}
P2 --> Auth
P3 --> Auth
P4 --> Auth

Auth -->|API Key| A1[API Key Authentication]
Auth -->|OAuth| A2[OAuth 2.0 Authentication]
Auth -->|HMAC| A3[HMAC Authentication]

A1 --> Sync{Sync Strategy?}
A2 --> Sync
A3 --> Sync

Sync -->|Real-time| S1[Webhook-Based Sync]
Sync -->|Periodic| S2[Polling-Based Sync]
Sync -->|Event-driven| S3[Event-Driven Sync]

S1 --> Error{Error Handling?}
S2 --> Error
S3 --> Error

Error -->|Prevent cascading failures| E1[Circuit Breaker Pattern]
Error -->|Retry failed calls| E2[Retry Pattern]
Error -->|Provide alternatives| E3[Fallback Pattern]

E1 --> Cache{Caching Strategy?}
E2 --> Cache
E3 --> Cache

Cache -->|Simple time-based| C1[Time-Based Caching]
Cache -->|Based on headers| C2[Conditional Caching]
Cache -->|Different durations| C3[Hierarchical Caching]

C1 --> Rate{Rate Limiting?}
C2 --> Rate
C3 --> Rate

Rate -->|Token bucket| R1[Token Bucket Pattern]
Rate -->|Constant rate| R2[Leaky Bucket Pattern]

Overview

When integrating third-party APIs with MainWP, you'll encounter common challenges that have established solutions. This guide documents these patterns to help you implement robust, maintainable integrations.

API Client Patterns

Singleton API Client

The Singleton pattern ensures only one instance of the API client exists throughout the application lifecycle.

class ApiClient {
    private static $instance = null;
    private $api_key;
    private $api_url;
    
    private function __construct($api_key, $api_url) {
        $this->api_key = $api_key;
        $this->api_url = $api_url;
    }
    
    public static function getInstance($api_key = null, $api_url = null) {
        if (self::$instance === null) {
            if ($api_key === null) {
                $api_key = get_option('my_extension_api_key', '');
            }
            
            if ($api_url === null) {
                $api_url = get_option('my_extension_api_url', 'https://api.example.com/v1');
            }
            
            self::$instance = new self($api_key, $api_url);
        }
        
        return self::$instance;
    }
    
    // API methods...
}

// Usage
$api_client = ApiClient::getInstance();
$data = $api_client->get('endpoint');

When to use: When you need a single, consistent API client throughout your extension.

Factory API Client

The Factory pattern creates different API client instances based on configuration or context.

class ApiClientFactory {
    public static function create($type, $config = []) {
        switch ($type) {
            case 'oauth':
                return new OAuthApiClient(
                    $config['client_id'] ?? '',
                    $config['client_secret'] ?? '',
                    $config['redirect_uri'] ?? ''
                );
            
            case 'api_key':
                return new ApiKeyClient(
                    $config['api_key'] ?? ''
                );
            
            case 'basic_auth':
                return new BasicAuthClient(
                    $config['username'] ?? '',
                    $config['password'] ?? ''
                );
            
            default:
                throw new Exception("Unknown API client type: $type");
        }
    }
}

// Usage
$oauth_client = ApiClientFactory::create('oauth', [
    'client_id' => 'your_client_id',
    'client_secret' => 'your_client_secret',
    'redirect_uri' => 'your_redirect_uri'
]);

When to use: When your extension needs to support multiple authentication methods or API versions.

Adapter API Client

The Adapter pattern wraps a third-party API client to provide a consistent interface.

// Third-party library
class ThirdPartyClient {
    public function sendRequest($method, $endpoint, $params) {
        // Implementation...
    }
    
    public function setAuthToken($token) {
        // Implementation...
    }
}

// Adapter
class ApiClientAdapter {
    private $client;
    
    public function __construct($api_key) {
        $this->client = new ThirdPartyClient();
        $this->client->setAuthToken($api_key);
    }
    
    public function get($endpoint, $params = []) {
        return $this->client->sendRequest('GET', $endpoint, $params);
    }
    
    public function post($endpoint, $data = []) {
        return $this->client->sendRequest('POST', $endpoint, $data);
    }
    
    // Other methods...
}

// Usage
$api_client = new ApiClientAdapter('your_api_key');
$data = $api_client->get('endpoint');

When to use: When integrating with a third-party API client library that has a different interface than your extension's API client.

Authentication Patterns

API Key Authentication

The simplest authentication pattern, using a single API key.

class ApiKeyClient {
    private $api_key;
    private $api_url;
    
    public function __construct($api_key, $api_url = 'https://api.example.com/v1') {
        $this->api_key = $api_key;
        $this->api_url = $api_url;
    }
    
    public function request($method, $endpoint, $params = [], $data = null) {
        $url = $this->api_url . '/' . ltrim($endpoint, '/');
        
        $args = [
            'method' => $method,
            'headers' => [
                'Authorization' => 'Bearer ' . $this->api_key,
                'Content-Type' => 'application/json',
                'Accept' => 'application/json'
            ]
        ];
        
        // Add query parameters
        if (!empty($params)) {
            $url = add_query_arg($params, $url);
        }
        
        // Add body data
        if ($data !== null) {
            $args['body'] = json_encode($data);
        }
        
        // Make request
        $response = wp_remote_request($url, $args);
        
        // Process response...
        
        return $result;
    }
}

When to use: For simple APIs that use a single API key for authentication.

OAuth 2.0 Authentication

A more complex authentication pattern for APIs that use OAuth 2.0.

class OAuth2Client {
    private $client_id;
    private $client_secret;
    private $redirect_uri;
    private $auth_url;
    private $token_url;
    private $api_url;
    private $access_token;
    private $refresh_token;
    private $token_expires;
    
    public function __construct($client_id, $client_secret, $redirect_uri, $auth_url, $token_url, $api_url) {
        $this->client_id = $client_id;
        $this->client_secret = $client_secret;
        $this->redirect_uri = $redirect_uri;
        $this->auth_url = $auth_url;
        $this->token_url = $token_url;
        $this->api_url = $api_url;
        
        // Load tokens from options
        $this->access_token = get_option('my_extension_access_token', '');
        $this->refresh_token = get_option('my_extension_refresh_token', '');
        $this->token_expires = get_option('my_extension_token_expires', 0);
    }
    
    public function get_auth_url($scope = [], $state = '') {
        $params = [
            'client_id' => $this->client_id,
            'redirect_uri' => $this->redirect_uri,
            'response_type' => 'code',
            'state' => $state ?: wp_create_nonce('oauth2_state')
        ];
        
        if (!empty($scope)) {
            $params['scope'] = implode(' ', $scope);
        }
        
        return add_query_arg($params, $this->auth_url);
    }
    
    public function exchange_code_for_tokens($code) {
        $args = [
            'method' => 'POST',
            'headers' => [
                'Content-Type' => 'application/x-www-form-urlencoded'
            ],
            'body' => [
                'grant_type' => 'authorization_code',
                'code' => $code,
                'client_id' => $this->client_id,
                'client_secret' => $this->client_secret,
                'redirect_uri' => $this->redirect_uri
            ]
        ];
        
        $response = wp_remote_post($this->token_url, $args);
        
        // Process response and save tokens...
        
        return true;
    }
    
    public function refresh_access_token() {
        // Implementation...
    }
    
    public function request($method, $endpoint, $params = [], $data = null) {
        // Check if token is expired and refresh if needed
        if (time() > $this->token_expires - 60) {
            $this->refresh_access_token();
        }
        
        $url = $this->api_url . '/' . ltrim($endpoint, '/');
        
        $args = [
            'method' => $method,
            'headers' => [
                'Authorization' => 'Bearer ' . $this->access_token,
                'Content-Type' => 'application/json',
                'Accept' => 'application/json'
            ]
        ];
        
        // Add query parameters and body data...
        
        // Make request
        $response = wp_remote_request($url, $args);
        
        // Process response...
        
        return $result;
    }
}

When to use: For APIs that require OAuth 2.0 authentication, especially when user authorization is needed.

HMAC Authentication

A secure authentication pattern for APIs that require request signing.

class HmacClient {
    private $api_key;
    private $api_secret;
    private $api_url;
    
    public function __construct($api_key, $api_secret, $api_url = 'https://api.example.com/v1') {
        $this->api_key = $api_key;
        $this->api_secret = $api_secret;
        $this->api_url = $api_url;
    }
    
    public function request($method, $endpoint, $params = [], $data = null) {
        $url = $this->api_url . '/' . ltrim($endpoint, '/');
        $timestamp = time();
        $nonce = wp_generate_password(12, false);
        
        // Add query parameters
        if (!empty($params)) {
            $url = add_query_arg($params, $url);
        }
        
        // Create signature base string
        $signature_base = $method . "\n" . 
                          parse_url($url, PHP_URL_HOST) . "\n" . 
                          parse_url($url, PHP_URL_PATH) . "\n" . 
                          ($data ? json_encode($data) : '') . "\n" . 
                          $timestamp . "\n" . 
                          $nonce;
        
        // Create signature
        $signature = base64_encode(hash_hmac('sha256', $signature_base, $this->api_secret, true));
        
        $args = [
            'method' => $method,
            'headers' => [
                'X-API-Key' => $this->api_key,
                'X-Signature' => $signature,
                'X-Timestamp' => $timestamp,
                'X-Nonce' => $nonce,
                'Content-Type' => 'application/json',
                'Accept' => 'application/json'
            ]
        ];
        
        // Add body data
        if ($data !== null) {
            $args['body'] = json_encode($data);
        }
        
        // Make request
        $response = wp_remote_request($url, $args);
        
        // Process response...
        
        return $result;
    }
}

When to use: For APIs that require request signing for enhanced security.

Data Synchronization Patterns

Webhook-Based Sync

Synchronize data in real-time using webhooks.

class WebhookSyncHandler {
    public function __construct() {
        // Register webhook endpoint
        add_action('rest_api_init', [$this, 'register_webhook_endpoint']);
    }
    
    public function register_webhook_endpoint() {
        register_rest_route('my-extension/v1', '/webhook', [
            'methods' => 'POST',
            'callback' => [$this, 'process_webhook'],
            'permission_callback' => [$this, 'verify_webhook']
        ]);
    }
    
    public function verify_webhook($request) {
        // Get signature from header
        $signature = $request->get_header('X-Webhook-Signature');
        if (empty($signature)) {
            return false;
        }
        
        // Get webhook secret
        $secret = get_option('my_extension_webhook_secret', '');
        if (empty($secret)) {
            return false;
        }
        
        // Get request body
        $body = $request->get_body();
        
        // Calculate expected signature
        $expected_signature = hash_hmac('sha256', $body, $secret);
        
        // Compare signatures
        return hash_equals($expected_signature, $signature);
    }
    
    public function process_webhook($request) {
        // Get request data
        $data = $request->get_json_params();
        
        // Process webhook data
        $event_type = $data['event_type'] ?? '';
        $resource_id = $data['resource_id'] ?? '';
        
        switch ($event_type) {
            case 'site.updated':
                // Find MainWP website ID from resource ID
                $website_id = $this->get_website_id_from_resource_id($resource_id);
                if ($website_id) {
                    // Sync data from service
                    $this->sync_from_service($website_id);
                }
                break;
            
            case 'site.deleted':
                // Handle site deletion
                $website_id = $this->get_website_id_from_resource_id($resource_id);
                if ($website_id) {
                    delete_option('my_extension_data_' . $website_id);
                }
                break;
            
            // Handle other event types
        }
        
        return new WP_REST_Response(['status' => 'success'], 200);
    }
    
    private function get_website_id_from_resource_id($resource_id) {
        // Implementation...
    }
    
    private function sync_from_service($website_id) {
        // Implementation...
    }
}

When to use: When you need real-time data synchronization and the third-party API supports webhooks.

Polling-Based Sync

Synchronize data by periodically polling the API.

class PollingSyncHandler {
    public function __construct() {
        // Register cron event
        add_action('my_extension_sync_cron', [$this, 'sync_all_websites']);
        
        // Schedule cron event if not already scheduled
        if (!wp_next_scheduled('my_extension_sync_cron')) {
            wp_schedule_event(time(), 'hourly', 'my_extension_sync_cron');
        }
    }
    
    public function sync_all_websites() {
        // Get all MainWP websites
        $websites = MainWP\Dashboard\MainWP_DB::instance()->get_websites();
        
        foreach ($websites as $website) {
            // Check if extension is enabled for this website
            $enabled = get_option('my_extension_enabled_' . $website->id, false);
            if (!$enabled) {
                continue;
            }
            
            // Get last sync timestamp
            $last_sync = get_option('my_extension_last_sync_' . $website->id, 0);
            
            // Sync data
            $this->sync_website($website->id, $last_sync);
            
            // Update last sync timestamp
            update_option('my_extension_last_sync_' . $website->id, time());
        }
    }
    
    private function sync_website($website_id, $last_sync) {
        // Get website details
        $website = MainWP\Dashboard\MainWP_DB::instance()->get_website_by_id($website_id);
        if (!$website) {
            return;
        }
        
        // Get API client
        $api_client = ApiClient::getInstance();
        
        // Get changes since last sync
        $changes = $api_client->get('sites/' . urlencode($website->url) . '/changes', [
            'since' => $last_sync
        ]);
        
        // Process changes
        foreach ($changes as $change) {
            // Process change...
        }
    }
}

When to use: When the third-party API doesn't support webhooks or when you need more control over the synchronization process.

Event-Driven Sync

Synchronize data based on MainWP events.

class EventDrivenSyncHandler {
    public function __construct() {
        // Register event handlers
        add_action('mainwp_site_added', [$this, 'handle_site_added'], 10, 1);
        add_action('mainwp_site_updated', [$this, 'handle_site_updated'], 10, 1);
        add_action('mainwp_site_deleted', [$this, 'handle_site_deleted'], 10, 1);
        add_action('mainwp_site_synced', [$this, 'handle_site_synced'], 10, 2);
    }
    
    public function handle_site_added($website_id) {
        // Get website details
        $website = MainWP\Dashboard\MainWP_DB::instance()->get_website_by_id($website_id);
        if (!$website) {
            return;
        }
        
        // Get API client
        $api_client = ApiClient::getInstance();
        
        // Create site in third-party service
        $api_client->post('sites', [
            'url' => $website->url,
            'name' => $website->name
        ]);
    }
    
    public function handle_site_updated($website_id) {
        // Implementation...
    }
    
    public function handle_site_deleted($website_id) {
        // Implementation...
    }
    
    public function handle_site_synced($website, $data) {
        // Implementation...
    }
}

When to use: When you need to synchronize data based on MainWP events.

Error Handling Patterns

Circuit Breaker Pattern

The Circuit Breaker pattern acts like an electrical circuit breaker - it automatically stops API calls when too many failures occur, preventing a cascade of errors that could crash your system. After a cooling-off period, it cautiously allows calls again to see if the problem is resolved.

class CircuitBreaker {
    private $failures = 0;
    private $threshold = 5;
    private $reset_timeout = 60;
    private $last_failure_time = 0;
    private $state = 'closed'; // closed, open, half-open
    
    public function execute($func) {
        // Check if circuit is open
        if ($this->is_open()) {
            // Check if reset timeout has passed
            if (time() - $this->last_failure_time > $this->reset_timeout) {
                // Set circuit to half-open
                $this->state = 'half-open';
            } else {
                throw new Exception('Circuit is open');
            }
        }
        
        try {
            // Execute function
            $result = $func();
            
            // Reset failures on success
            if ($this->state === 'half-open') {
                $this->reset();
            }
            
            return $result;
        } catch (Exception $e) {
            // Increment failures
            $this->failures++;
            $this->last_failure_time = time();
            
            // Open circuit if threshold is reached
            if ($this->failures >= $this->threshold) {
                $this->state = 'open';
            }
            
            throw $e;
        }
    }
    
    public function is_open() {
        return $this->state === 'open';
    }
    
    public function reset() {
        $this->failures = 0;
        $this->state = 'closed';
    }
}

// Usage
$circuit_breaker = new CircuitBreaker();
try {
    $result = $circuit_breaker->execute(function() use ($api_client) {
        return $api_client->get('endpoint');
    });
} catch (Exception $e) {
    // Handle error
}

When to use: When you need to prevent cascading failures in your extension.

Retry Pattern

The Retry Pattern automatically attempts failed API calls again, using a smart "exponential backoff" approach - waiting a little longer between each retry. This gives temporary API issues time to resolve themselves without requiring manual intervention.

class RetryHandler {
    private $max_retries;
    
    public function __construct($max_retries = 3) {
        $this->max_retries = $max_retries;
    }
    
    public function execute($func, $retry_count = 0) {
        try {
            return $func();
        } catch (ApiRateLimitException $e) {
            // Handle rate limiting
            $retry_after = $e->getRetryAfter();
            if ($retry_count < $this->max_retries) {
                sleep($retry_after);
                return $this->execute($func, $retry_count + 1);
            }
            throw $e;
        } catch (ApiServerException $e) {
            // Handle server errors
            if ($retry_count < $this->max_retries) {
                $backoff = pow(2, $retry_count);
                sleep($backoff);
                return $this->execute($func, $retry_count + 1);
            }
            throw $e;
        } catch (ApiConnectionException $e) {
            // Handle connection errors
            if ($retry_count < $this->max_retries) {
                $backoff = pow(2, $retry_count);
                sleep($backoff);
                return $this->execute($func, $retry_count + 1);
            }
            throw $e;
        }
    }
}

// Usage
$retry_handler = new RetryHandler();
try {
    $result = $retry_handler->execute(function() use ($api_client) {
        return $api_client->get('endpoint');
    });
} catch (Exception $e) {
    // Handle error
}

When to use: When you need to automatically retry failed API calls.

Fallback Pattern

The Fallback Pattern is like having a backup plan - when an API call fails, it automatically provides alternative data from a cache or predefined defaults. This ensures your application continues to function even when the external service is unavailable, giving users a smoother experience.

class FallbackHandler {
    private $api_client;
    
    public function __construct($api_client) {
        $this->api_client = $api_client;
    }
    
    public function get_with_fallback($endpoint, $params = []) {
        try {
            // Try to get data from API
            return $this->api_client->get($endpoint, $params);
        } catch (Exception $e) {
            // Log error
            error_log('API Error: ' . $e->getMessage());
            
            // Try to get cached data
            $cache_key = 'my_extension_' . md5($endpoint . serialize($params));
            $cached = get_transient($cache_key);
            if (false !== $cached) {
                return $cached;
            }
            
            // Return fallback data
            return $this->get_fallback_data($endpoint);
        }
    }
    
    private function get_fallback_data($endpoint) {
        // Return default data based on endpoint
        switch ($endpoint) {
            case 'users':
                return ['status' => 'offline', 'data' => []];
            case 'stats':
                return ['status' => 'offline', 'data' => ['visits' => 0, 'conversions' => 0]];
            default:
                return ['status' => 'offline', 'data' => []];
        }
    }
}

// Usage
$fallback_handler = new FallbackHandler($api_client);
$data = $fallback_handler->get_with_fallback('endpoint');

When to use: When you need to provide fallback data when the API is unavailable.

Caching Patterns

Time-Based Caching

Cache API responses for a fixed period of time.

class TimeCacheHandler {
    private $api_client;
    
    public function __construct($api_client) {
        $this->api_client = $api_client;
    }
    
    public function get_cached($endpoint, $params = [], $cache_time = 300) {
        $cache_key = 'my_extension_' . md5($endpoint . serialize($params));
        
        // Check cache
        $cached = get_transient($cache_key);
        if (false !== $cached) {
            return $cached;
        }
        
        // Get fresh data
        $data = $this->api_client->get($endpoint, $params);
        
        // Cache the data
        set_transient($cache_key, $data, $cache_time);
        
        return $data;
    }
    
    public function invalidate_cache($endpoint, $params = []) {
        $cache_key = 'my_extension_' . md5($endpoint . serialize($params));
        delete_transient($cache_key);
    }
}

// Usage
$cache_handler = new TimeCacheHandler($api_client);
$data = $cache_handler->get_cached('endpoint');

When to use: When you need to cache API responses for a fixed period of time.

Conditional Caching

Cache API responses based on ETag or Last-Modified headers.

class ConditionalCacheHandler {
    private $api_client;
    
    public function __construct($api_client) {
        $this->api_client = $api_client;
    }
    
    public function get_cached($endpoint, $params = []) {
        $cache_key = 'my_extension_' . md5($endpoint . serialize($params));
        $etag_key = $cache_key . '_etag';
        $last_modified_key = $cache_key . '_last_modified';
        
        // Get cached data and headers
        $cached = get_transient($cache_key);
        $etag = get_transient($etag_key);
        $last_modified = get_transient($last_modified_key);
        
        // Prepare headers for conditional request
        $headers = [];
        if ($etag) {
            $headers['If-None-Match'] = $etag;
        }
        if ($last_modified) {
            $headers['If-Modified-Since'] = $last_modified;
        }
        
        // Make conditional request
        $response = $this->api_client->request('GET', $endpoint, $params, null, $headers);
        
        // Check if response is 304 Not Modified
        if ($response['status'] === 304) {
            // Return cached data
            return $cached;
        }
        
        // Get new data
        $data = $response['data'];
        
        // Get new headers
        $new_etag = $response['headers']['etag'] ?? '';
        $new_last_modified = $response['headers']['last-modified'] ?? '';
        
        // Cache data and headers
        set_transient($cache_key, $data, DAY_IN_SECONDS);
        if ($new_etag) {
            set_transient($etag_key, $new_etag, DAY_IN_SECONDS);
        }
        if ($new_last_modified) {
            set_transient($last_modified_key, $new_last_modified, DAY_IN_SECONDS);
        }
        
        return $data;
    }
}

// Usage
$cache_handler = new ConditionalCacheHandler($api_client);
$data = $cache_handler->get_cached('endpoint');

When to use: When the API supports conditional requests with ETag or Last-Modified headers.

Hierarchical Caching

Cache different types of data for different durations.

class HierarchicalCacheHandler {
    private $api_client;
    private $cache_durations;
    
    public function __construct($api_client, $cache_durations = []) {
        $this->api_client = $api_client;
        $this->cache_durations = array_merge([
            'user' => 60,           // User data: 1 minute
            'posts' => 300,         // Posts: 5 minutes
            'comments' => 180,      // Comments: 3 minutes
            'settings' => 3600,     // Settings: 1 hour
            'static' => 86400       // Static data: 24 hours
        ], $cache_durations);
    }
    
    public function get_cached($endpoint, $params = []) {
        $cache_key = 'my_extension_' . md5($endpoint . serialize($params));
        
        // Check cache
        $cached = get_transient($cache_key);
        if (false !== $cached) {
            return $cached;
        }
        
        // Get fresh data
        $data = $this->api_client->get($endpoint, $params);
        
        // Determine cache duration
        $cache_time = $this->get_cache_duration($endpoint);
        
        // Cache the data
        set_transient($cache_key, $data, $cache_time);
        
        return $data;
    }
    
    private function get_cache_duration($endpoint) {
        foreach ($this->cache_durations as $key => $duration) {
            if (strpos($endpoint, $key) !== false) {
                return $duration;
            }
        }
        
        return 300; // Default: 5 minutes
    }
}

// Usage
$cache_handler = new HierarchicalCacheHandler($api_client);
$data = $cache_handler->get_cached('endpoint');

When to use: When you need to cache different types of data for different durations.

Rate Limiting Patterns

Token Bucket Pattern

Limit API requests using a token bucket algorithm.

class TokenBucketRateLimiter {
    private $bucket_size;
    private $refill_rate;
    private $available_tokens;
    private $last_refill_time;
    private $option_name;
    
    public function __construct($bucket_size = 60, $refill_rate = 1, $option_name = 'my_extension_rate_limit') {
        $this->bucket_size = $bucket_size;
        $this->refill_rate = $refill_rate;
        $this->option_name = $option_name;
        
        // Load state
        $state = get_option($option_name, [
            'available_tokens' => $bucket_size,
            'last_refill_time' => time()
        ]);
        
        $this->available_tokens = $state['available_tokens'];
        $this->last_refill_time = $state['last_refill_time'];
        
        // Refill tokens based on elapsed time
        $this->refill_tokens();
    }
    
    private function refill_tokens() {
        $now = time();
        $elapsed = $now - $this->last_refill_time;
        
        if ($elapsed > 0) {
            $new_tokens = $elapsed * $this->refill_rate;
            $this->available_tokens = min($this->bucket_size, $this->available_tokens + $new_tokens);
            $this->last_refill_time = $now;
            
            // Save state
            update_option($this->option_name, [
                'available_tokens' => $this->available_tokens,
                'last_refill_time' => $this->last_refill_time
            ]);
        }
    }
    
    public function can_make_request($tokens = 1) {
        $this->refill_tokens();
        return $this->available_tokens >= $tokens;
    }
    
    public function consume_tokens($tokens = 1) {
        if (!$this->can_make_request($tokens)) {
            return false;
        }
        
        $this->available_tokens -= $tokens;
        
        // Save state
        update_option($this->option_name, [
            'available_tokens' => $this->available_tokens,
            'last_refill_time' => $this->last_refill_time
        ]);
        
        return true;
    }
    
    public function get_wait_time($tokens = 1) {
        $this->refill_tokens();
        
        if ($this->available_tokens >= $tokens) {
            return 0;
        }
        
        $tokens_needed = $tokens - $this->available_tokens;
        return ceil($tokens_needed / $this->refill_rate);
    }
}

// Usage
$rate_limiter = new TokenBucketRateLimiter(60, 1); // 60 requests per minute
if ($rate_limiter->can_make_request()) {
    $data = $api_client->get('endpoint');
    $rate_limiter->consume_tokens();
} else {
    $wait_time = $rate_limiter->get_wait_time();
    error_log("Rate limit exceeded. Try again in $wait_time seconds.");
}

When to use: When you need to limit the rate of API requests to avoid hitting API rate limits.

Leaky Bucket Pattern

Limit API requests using a leaky bucket algorithm.

class LeakyBucketRateLimiter {
    private $queue = [];
    private $processing = false;
    private $rate;
    private $option_name;
    
    public function __construct($rate = 1, $option_name = 'my_extension_leaky_bucket') {
        $this->rate = $rate; // Requests per second
        $this->option_name = $option_name;
        
        // Load queue from options
        $this->queue = get_option($option_name, []);
        
        // Start processing queue if not empty
        if (!empty($this->queue) && !$this->processing) {
            $this->process_queue();
        }
    }
    
    public function add_request($method, $endpoint, $params = [], $data = null, $callback = null) {
        // Add request to queue
        $this->queue[] = [
            'method' => $method,
            'endpoint' => $endpoint,
            'params' => $params,
            'data' => $data,
            'callback' => $callback,
            'added_at' => time()
        ];
        
        // Save queue
        update_option($this->option_name, $this->queue);
        
        // Start processing queue if not already processing
        if (!$this->processing) {
            $this->process_queue();
        }
    }
    
    private function process_queue() {
        $this->processing = true;
        
        // Process first request in queue
        if (!empty($this->queue)) {
            $request = array_shift($this->queue);
            
            // Save updated queue
            update_option($this->option_name, $this->queue);
            
            // Process request
            try {
                $api_client = ApiClient::getInstance();
                $result = $api_client->request(
                    $request['method'],
                    $request['endpoint'],
                    $request['params'],
                    $request['data']
                );
                
                // Call callback if provided
                if (is_callable($request['callback'])) {
                    call_user_func($request['callback'], $result);
                }
            } catch (Exception $e) {
                error_log('API Error: ' . $e->getMessage());
            }
            
            // Schedule next request
            wp_schedule_single_event(time() + (1 / $this->rate), 'my_extension_process_queue');
        } else {
            $this->processing = false;
        }
    }
}

// Usage
$leaky_bucket = new LeakyBucketRateLimiter(1); // 1 request per second
$leaky_bucket->add_request('GET', 'endpoint', [], null, function($result) {
    // Process result
});

When to use: When you need to process API requests at a constant rate.

Conclusion

These patterns provide proven solutions to common challenges when integrating third-party APIs with MainWP extensions. By leveraging these patterns, you can create robust, maintainable integrations that provide a great user experience.

Remember that these patterns are not mutually exclusive - you can combine them to create a solution that meets your specific needs. For example, you might use the Singleton API Client pattern with the Circuit Breaker pattern and Time-Based Caching to create a robust API client that handles failures gracefully and optimizes performance.

Common Hooks Reference

This guide is a placeholder and will be expanded in the future.

This guide will provide a reference for the most commonly used MainWP hooks (actions and filters), organized by functionality. It will include:

Check back soon for the complete guide.

Code Review Checklist for MainWP Documentation

This checklist should be used when reviewing code examples in MainWP documentation to ensure consistency, security, and adherence to WordPress coding standards.

Formatting and Style

WordPress Coding Standards

Security Practices

Error Handling

MainWP-Specific Standards

Performance Considerations

Defensive Programming

Documentation

Compatibility

Testing Considerations

MainWP Developer Documentation

Welcome to the MainWP Developer Documentation! This comprehensive resource is designed to help you create powerful add-ons for the MainWP platform.

Documentation Overview

flowchart TD A[Getting Started] --> B[Core Concepts] A --> C[How-To Guides] A --> D[Reference] B --> E[Extension Development] B --> F[Integration Development] C --> G[Basic Tasks] C --> H[Advanced Techniques] D --> I[API Reference] D --> J[Hooks Reference]
style A fill:#4CAF50,stroke:#388E3C,color:white
style B fill:#2196F3,stroke:#1976D2,color:white
style C fill:#FFC107,stroke:#FFA000,color:white
style D fill:#9C27B0,stroke:#7B1FA2,color:white

Getting Started

New to MainWP development? Start here to learn the basics and set up your development environment.

Complete Beginner's Guide

Beginner 30 minutes

A step-by-step guide to getting started with MainWP development, from setting up your environment to creating your first add-on.

Start Here
<div class="card beginner">
    <div class="card-header">
        <h3>Development Environment Setup</h3>
        <span class="difficulty">Beginner</span>
        <span class="time">15 minutes</span>
    </div>
    <div class="card-body">
        <p>Learn how to set up a local development environment for MainWP add-on development.</p>
        <a href="../how-to/setup-environment.md" class="button">View Guide</a>
    </div>
</div>

<div class="card beginner">
    <div class="card-header">
        <h3>Choose Your Add-on Type</h3>
        <span class="difficulty">Beginner</span>
        <span class="time">5 minutes</span>
    </div>
    <div class="card-body">
        <p>Understand the difference between Extensions and Integrations to choose the right approach for your project.</p>
        <a href="../concepts/addon-types.md" class="button">View Guide</a>
    </div>
</div>

Extension Development

Learn how to create standalone add-ons that extend MainWP's functionality without requiring third-party services.

Creating a Basic Extension

Beginner 45 minutes

A comprehensive guide to creating your first MainWP extension.

View Guide
<div class="card intermediate">
    <div class="card-header">
        <h3>Extension Development Lifecycle</h3>
        <span class="difficulty">Intermediate</span>
        <span class="time">20 minutes</span>
    </div>
    <div class="card-body">
        <p>Understand the complete lifecycle of a MainWP extension from development to deployment.</p>
        <a href="../concepts/extension-lifecycle.md" class="button">View Guide</a>
    </div>
</div>

<div class="card intermediate">
    <div class="card-header">
        <h3>Building Admin Interfaces</h3>
        <span class="difficulty">Intermediate</span>
        <span class="time">30 minutes</span>
    </div>
    <div class="card-body">
        <p>Learn how to create effective admin interfaces for your MainWP extension.</p>
        <a href="../how-to/admin-interfaces.md" class="button">View Guide</a>
    </div>
</div>

Integration Development

Learn how to create add-ons that connect MainWP with third-party plugins or services.

Creating a Basic Integration

Beginner 45 minutes

A comprehensive guide to creating your first MainWP integration with a third-party service.

View Guide
<div class="card intermediate">
    <div class="card-header">
        <h3>Working with Third-Party APIs</h3>
        <span class="difficulty">Intermediate</span>
        <span class="time">30 minutes</span>
    </div>
    <div class="card-body">
        <p>Learn how to effectively integrate third-party APIs with MainWP.</p>
        <a href="../how-to/third-party-apis.md" class="button">View Guide</a>
    </div>
</div>

<div class="card advanced">
    <div class="card-header">
        <h3>API Integration Patterns</h3>
        <span class="difficulty">Advanced</span>
        <span class="time">25 minutes</span>
    </div>
    <div class="card-body">
        <p>Advanced patterns for robust API integrations with MainWP.</p>
        <a href="../reference/api-integration-patterns.md" class="button">View Guide</a>
    </div>
</div>

Core Development Topics

Essential guides for both extension and integration developers.

Using MainWP Actions & Filters

Beginner 30 minutes

Learn how to use MainWP hooks to extend and customize functionality.

View Guide
<div class="card intermediate">
    <div class="card-header">
        <h3>Data Storage and Retrieval</h3>
        <span class="difficulty">Intermediate</span>
        <span class="time">25 minutes</span>
    </div>
    <div class="card-body">
        <p>Learn how to effectively store and retrieve data in your MainWP add-ons.</p>
        <a href="../how-to/data-storage.md" class="button">View Guide</a>
    </div>
</div>

<div class="card intermediate">
    <div class="card-header">
        <h3>Debugging Add-ons</h3>
        <span class="difficulty">Intermediate</span>
        <span class="time">20 minutes</span>
    </div>
    <div class="card-body">
        <p>Techniques for debugging MainWP add-ons effectively.</p>
        <a href="../how-to/debugging.md" class="button">View Guide</a>
    </div>
</div>

<div class="card intermediate">
    <div class="card-header">
        <h3>Packaging and Distribution</h3>
        <span class="difficulty">Intermediate</span>
        <span class="time">15 minutes</span>
    </div>
    <div class="card-body">
        <p>Learn how to package and distribute your MainWP add-ons.</p>
        <a href="../how-to/packaging-distribution.md" class="button">View Guide</a>
    </div>
</div>

Best Practices

Recommendations for creating high-quality MainWP add-ons.

Coding Standards

Intermediate 15 minutes

Best practices for writing clean, maintainable code for MainWP add-ons.

View Guide
<div class="card intermediate">
    <div class="card-header">
        <h3>API Integration Best Practices</h3>
        <span class="difficulty">Intermediate</span>
        <span class="time">20 minutes</span>
    </div>
    <div class="card-body">
        <p>Best practices for integrating third-party APIs with MainWP.</p>
        <a href="../best-practices/api-integration.md" class="button">View Guide</a>
    </div>
</div>

Reference Documentation

Comprehensive reference material for MainWP developers.

MainWP Dashboard API

Reference

Complete API reference for the MainWP Dashboard.

View Reference
<div class="card reference">
    <div class="card-header">
        <h3>MainWP Child API</h3>
        <span class="type">Reference</span>
    </div>
    <div class="card-body">
        <p>Complete API reference for the MainWP Child plugin.</p>
        <a href="../../source-code/child/" class="button">View Reference</a>
    </div>
</div>

<div class="card reference">
    <div class="card-header">
        <h3>MainWP Dashboard Hooks</h3>
        <span class="type">Reference</span>
    </div>
    <div class="card-body">
        <p>Comprehensive list of actions and filters available in the MainWP Dashboard.</p>
        <a href="../../mainwp-hooks/dashboard/" class="button">View Reference</a>
    </div>
</div>

<div class="card reference">
    <div class="card-header">
        <h3>MainWP Child Hooks</h3>
        <span class="type">Reference</span>
    </div>
    <div class="card-body">
        <p>Comprehensive list of actions and filters available in the MainWP Child plugin.</p>
        <a href="../../mainwp-hooks/child/" class="button">View Reference</a>
    </div>
</div>

<div class="card reference">
    <div class="card-header">
        <h3>Common Hooks Reference</h3>
        <span class="type">Reference</span>
    </div>
    <div class="card-body">
        <p>Quick reference guide to the most commonly used MainWP hooks.</p>
        <a href="../reference/common-hooks.md" class="button">View Reference</a>
    </div>
</div>

<div class="card reference">
    <div class="card-header">
        <h3>Terminology Glossary</h3>
        <span class="type">Reference</span>
    </div>
    <div class="card-body">
        <p>Definitions of key terms used in MainWP development.</p>
        <a href="../reference/terminology.md" class="button">View Reference</a>
    </div>
</div>

Example Integrations

Complete examples of MainWP integrations with popular third-party services.

WooCommerce Integration

Example

Learn how to integrate MainWP with WooCommerce.

View Example
<div class="card example">
    <div class="card-header">
        <h3>Social Media Integration</h3>
        <span class="type">Example</span>
    </div>
    <div class="card-body">
        <p>Learn how to integrate MainWP with social media APIs.</p>
        <a href="../how-to/social-media-integration.md" class="button">View Example</a>
    </div>
</div>

Additional Resources

Your Development Journey

Getting Started with MainWP Development

Welcome to MainWP development! This guide will walk you through the essential steps to start building add-ons for MainWP whether you're creating a standalone extension or integrating with a third-party service.

Your Development Journey

Follow these steps to begin your MainWP development journey:

flowchart LR A[1. Setup Environment] --> B[2. Choose Add-on Type] B --> C1[3a. Create Basic Extension] B --> C2[3b. Create Basic Integration] C1 --> D[4. Test & Deploy] C2 --> D
style A fill:#4CAF50stroke:#388E3Ccolor:white
style B fill:#2196F3stroke:#1976D2color:white
style C1 fill:#FFC107stroke:#FFA000color:white
style C2 fill:#FFC107stroke:#FFA000color:white
style D fill:#9C27B0stroke:#7B1FA2color:white

Step 1: Set Up Your Development Environment

You are here: Step 1 of 4

Before you start coding you'll need to set up a proper development environment.

Essential Requirements

Quick Setup Instructions

  1. Install a local WordPress development environment like:

  2. Install and activate the MainWP Dashboard plugin:

    • Download from WordPress.org
    • Or install directly from your WordPress admin area
  3. Connect at least one child site:

    • Install WordPress on another local site
    • Install and activate the MainWP Child plugin
    • Connect it to your MainWP Dashboard
  4. Install the MainWP Development Extension:

    • Clone from GitHub
    • Or download the ZIP file and install manually

For detailed instructions see the Setting Up a Development Environment guide.

Step 2: Choose Your Add-on Type

Next step: Step 2 of 4

MainWP uses specific terminology to distinguish between different types of add-ons:

Which Type Should You Build?

Use this decision tree to determine whether you should build an Extension or an Integration:

flowchart TD A[Do you need to connect with a third-party service or plugin?] -->|Yes| B[Integration] A -->|No| C[Extension] B --> D[Examples: WooCommerce Integration Google Analytics Integration] C --> E[Examples: Advanced Uptime Monitor Advanced Reports Extension]
style A fill:#2196F3stroke:#1976D2color:white
style B fill:#FFC107stroke:#FFA000color:white
style C fill:#FFC107stroke:#FFA000color:white
style D fill:#9E9E9Estroke:#757575color:white
style E fill:#9E9E9Estroke:#757575color:white

Extension Examples

Extensions are standalone add-ons that extend MainWP's functionality without requiring third-party services:

Integration Examples

Integrations connect MainWP with third-party plugins or services:

Step 3a: Create a Basic Extension

Next step (if building an Extension): Step 3a of 4

If you're building a standalone extension follow these steps:

  1. Start with the MainWP Development Extension template
  2. Customize the plugin information
  3. Implement your core functionality
  4. Create admin pages and settings
  5. Test thoroughly

For detailed instructions see the Creating a Basic Extension guide.

Step 3b: Create a Basic Integration

Next step (if building an Integration): Step 3b of 4

If you're building an integration with a third-party service follow these steps:

  1. Start with the MainWP Development Extension template
  2. Customize the plugin information
  3. Implement third-party API connectivity
  4. Create admin pages and settings
  5. Implement data synchronization
  6. Test thoroughly

For detailed instructions see the Creating a Basic Integration guide.

Step 4: Test and Deploy Your Add-on

Final step: Step 4 of 4

Before releasing your add-on make sure to:

  1. Test on multiple child sites
  2. Check for any errors or warnings
  3. Ensure compatibility with different WordPress versions
  4. Package your add-on for distribution
  5. Create documentation for users

For detailed instructions see the Packaging and Distribution guide.

Essential Resources

Documentation

Tools and Templates

Community Support

Next Steps

After completing the getting started process explore these topics to enhance your MainWP development skills:

Guide Title

Difficulty Level: Beginner/Intermediate/Advanced

What You'll Learn

In this guide, you will learn:

Estimated Time: XX minutes

Prerequisites

Before starting this guide, make sure you have:

Quick Start for Experienced Developers

// Essential code for experienced developers
// This should be a condensed version of the full implementation
// that experienced developers can quickly copy and adapt

// Example:
function example_function() {
    // Core implementation
    return $result;
}

Want more details? Continue reading for a step-by-step explanation, or check out the Quick Start Guide for a minimal implementation.

Introduction

A brief introduction to the topic that explains:

flowchart TD A[First Step] --> B[Second Step] B --> C[Third Step] C --> D[Final Result]

Step 1: First Major Step

Clear explanation of the first step with context about why this step is necessary.

Code Example

/**
 * Example function with proper documentation
 *
 * @param string $param1 Description of parameter
 * @param array $param2 Description of parameter
 * @return mixed Description of return value
 */
function example_function($param1, $param2 = array()) {
    // Validate inputs
    if (empty($param1)) {
        return new WP_Error('missing_param', __('Parameter is required', 'text-domain'));
    }
    
    // Sanitize inputs
    $param1 = sanitize_text_field($param1);
    
    // Function implementation
    $result = do_something_with($param1, $param2);
    
    // Error handling
    if (is_wp_error($result)) {
        // Log error if WP_DEBUG_LOG is enabled
        if (defined('WP_DEBUG_LOG') && WP_DEBUG_LOG) {
            error_log(sprintf('[MainWP] Error in example_function: %s', $result->get_error_message()));
        }
        return $result;
    }
    
    return $result;
}

Key Points

Step 2: Second Major Step

Clear explanation of the second step with context about why this step is necessary.

Code Example

// Code for step 2

Key Points

Step 3: Third Major Step

Clear explanation of the third step with context about why this step is necessary.

Code Example

// Code for step 3

Key Points

Common Challenges and Solutions

Challenge 1: Description of Common Issue

Explanation of the challenge and why it occurs.

Solution: Step-by-step solution to the challenge with code examples if applicable.

Challenge 2: Description of Common Issue

Explanation of the challenge and why it occurs.

Solution: Step-by-step solution to the challenge with code examples if applicable.

Best Practices

Security Best Practices

Performance Best Practices

Testing Your Implementation

Steps to test the implementation:

  1. Test step 1
  2. Test step 2
  3. Test step 3
  4. Verify the expected outcome

Next Steps

After completing this guide, you might want to explore:

MainWP Terminology Glossary

This glossary defines the key terms used throughout the MainWP documentation to ensure consistency and clarity.

Core Concepts

Add-on

The umbrella term for all plugins that extend MainWP's functionality, including both extensions and integrations.

Extension

A type of add-on that works WITHOUT requiring a third-party plugin or API. Extensions add standalone functionality to MainWP.

Integration

A type of add-on that works WITH a third-party plugin or API. Integrations connect MainWP with external services or plugins.

Dashboard

The MainWP Dashboard plugin installed on a single WordPress site that manages multiple child sites.

Child Site

A WordPress site that is managed by a MainWP Dashboard. Child sites have the MainWP Child plugin installed.

Child Plugin

The MainWP Child plugin that is installed on sites to be managed by a MainWP Dashboard.

Connection

Development Concepts

Hook

A WordPress concept that allows code to be injected at specific points in the execution flow. Includes both actions and filters.

Action

A type of hook that allows you to execute custom code at specific points in the execution flow.

Filter

A type of hook that allows you to modify data at specific points in the execution flow.

API

Application Programming Interface. In MainWP, this can refer to:

  1. The MainWP API for communication between Dashboard and Child sites
  2. Third-party APIs that integrations connect with

Callback

A function that is passed as an argument to another function and is executed at a specific time or in response to a specific event.

Extension API

The interface that MainWP provides for extensions to register themselves and interact with the MainWP Dashboard.

MainWP API Client

A PHP library that simplifies communication with the MainWP API.

User Interface Terms

Admin Page

A page in the WordPress admin area that is added by a MainWP extension or integration.

Dashboard Widget

A widget that appears on the MainWP Dashboard overview page, providing quick access to information or functionality.

Settings Page

A page where users can configure options for a MainWP extension or integration.

Overview Page

The main page of the MainWP Dashboard that displays summary information about child sites.

Individual Dashboard

A feature that allows users to create custom dashboards with specific widgets and information.

Data Management Terms

Sync

The process of updating information between the MainWP Dashboard and child sites.

Backup

A copy of a child site's files and/or database that can be used for restoration.

Restore

The process of reverting a child site to a previous state using a backup.

Update

The process of upgrading WordPress core, plugins, or themes on child sites.

Bulk Update

Updating multiple sites, plugins, or themes simultaneously.

Security Terms

Connection Key

A unique identifier used to establish a secure connection between the MainWP Dashboard and a child site.

OpenSSL

A cryptographic library used by MainWP for secure communication between the Dashboard and child sites.

Capability

A WordPress permission that determines what actions a user can perform.

Authentication

The process of verifying the identity of a user or system.

Authorization

The process of determining whether an authenticated user or system has permission to perform a specific action.

Technical Terms

Transient

A WordPress feature for temporarily storing cached data with an expiration time.

REST API

A way of providing API access using HTTP methods and JSON data format.

Webhook

A user-defined HTTP callback that is triggered by a specific event.

Cron Job

A scheduled task that runs automatically at specified intervals.

Background Process

A task that runs in the background without blocking the user interface.

MainWP-Specific Features

Extension Page

The page in the MainWP Dashboard where extensions are listed and can be activated or deactivated.

Site Group

A way to organize child sites into logical groups for easier management.

Tag

A label that can be applied to child sites for filtering and organization.

Client Report

A customizable report that can be generated for clients showing work performed on their sites.

Uptime Monitoring

A feature that checks if child sites are accessible and functioning properly.

Site Health

A feature that checks the health and performance of child sites.

Third-Party Integration Terms

API Key

A unique identifier used to authenticate with a third-party API.

OAuth

An open standard for access delegation, commonly used for secure API authentication.

Webhook

A mechanism for a third-party service to notify MainWP when certain events occur.

Rate Limit

A restriction on the number of API requests that can be made within a certain time period.

Endpoint

A specific URL in a third-party API that accepts requests and returns responses.