Home / Admin / DIR SYNC 4
Duplicate Snippet

Embed Snippet on Your Site

DIR SYNC 4

ismail daugherty PRO
<10
Code Preview
php
<?php
/**
 * Main LifterLMS Course Sync Engine
 * 
 * PURPOSE:
 * - Syncs LifterLMS courses to llms_content_sync CPT
 * - Handles both automatic and manual synchronization
 * - Fires completion action for detail population extensions
 * - Cloudflare-safe with proper debouncing and guards
 * 
 * HOOKS FIRED:
 * - llms_content_sync_complete ($course_id, $sync_post_id, $is_update)
 * 
 * DEPENDENCIES:
 * - LifterLMS plugin
 * - llms_content_sync CPT registered
 */
// ============================================
// ADMIN UI: MANUAL SYNC BUTTON
// ============================================
add_action('admin_menu', 'llms_sync_admin_menu', 99);
function llms_sync_admin_menu() {
    add_submenu_page(
        'lifterlms',
        'Course Sync',
        'Course Sync',
        'manage_options',
        'llms-course-sync',
        'llms_sync_admin_page'
    );
}
function llms_sync_admin_page() {
    // Handle manual sync
    if (isset($_POST['sync_all_courses']) && check_admin_referer('llms_manual_sync', 'llms_sync_nonce')) {
        llms_manual_sync_all_courses();
        echo '<div class="notice notice-success"><p>Sync completed successfully!</p></div>';
    }
    
    ?>
    <div class="wrap">
        <h1>LifterLMS Course Sync Control</h1>
        
        <div class="card" style="max-width: 600px;">
            <h2>Manual Sync</h2>
            <p>Manually sync all courses to the llms_content_sync custom post type. Use this if automatic sync fails or for initial setup.</p>
            
            <form method="post">
                <?php wp_nonce_field('llms_manual_sync', 'llms_sync_nonce'); ?>
                <p>
                    <button type="submit" name="sync_all_courses" class="button button-primary button-hero">
                        Sync All Courses Now
                    </button>
                </p>
            </form>
            
            <hr>
            
            <h3>Sync Status</h3>
            <?php
            $courses = get_posts(array(
                'post_type' => 'course',
                'posts_per_page' => -1,
                'fields' => 'ids'
            ));
            
            $synced = get_posts(array(
                'post_type' => 'llms_content_sync',
                'posts_per_page' => -1,
                'fields' => 'ids',
                'meta_query' => array(
                    array(
                        'key' => '_source_type',
                        'value' => 'lifterlms_course'
                    )
                )
            ));
            ?>
            <p><strong>Total Courses:</strong> <?php echo count($courses); ?></p>
            <p><strong>Synced Items:</strong> <?php echo count($synced); ?></p>
        </div>
        
        <div class="card" style="max-width: 600px; margin-top: 20px;">
            <h2>Automatic Sync Settings</h2>
            <p>Automatic sync triggers on course updates. The system uses smart debouncing to prevent Cloudflare blocks.</p>
            <ul>
                <li>✓ Skips autosaves and revisions</li>
                <li>✓ Prevents duplicate operations with transient locks</li>
                <li>✓ Rate-limited to avoid triggering WAF</li>
                <li>✓ Only syncs published courses</li>
            </ul>
        </div>
    </div>
    <?php
}
// ============================================
// MANUAL SYNC FUNCTION
// ============================================
function llms_manual_sync_all_courses() {
    // Increase memory limit for bulk operations
    @ini_set('memory_limit', '512M');
    set_time_limit(300);
    
    // Get all courses
    $courses = get_posts(array(
        'post_type' => 'course',
        'posts_per_page' => -1,
        'post_status' => 'publish'
    ));
    
    // Suspend cache operations for performance
    $cache_suspended = wp_suspend_cache_invalidation(true);
    
    $synced_count = 0;
    foreach ($courses as $course) {
        $result = llms_sync_course_to_cpt($course->ID, $course, true);
        if ($result) {
            $synced_count++;
        }
        
        // Free memory
        unset($course);
    }
    
    // Restore cache operations
    wp_suspend_cache_invalidation($cache_suspended);
    
    return $synced_count;
}
// ============================================
// AUTOMATIC SYNC HOOKS
// ============================================
/**
 * Hook into course updates
 * Using edit_post_course instead of save_post_course
 * Reason: save_post fires 7+ times with access plans, causing Cloudflare blocks
 */
add_action('edit_post_course', 'llms_auto_sync_course', 10, 2);
function llms_auto_sync_course($post_id, $post) {
    // CRITICAL GUARDS: Prevent Cloudflare WAF triggers
    
    // Skip autosaves (causes multiple rapid requests)
    if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
        return;
    }
    
    // Skip revisions
    if (wp_is_post_revision($post_id)) {
        return;
    }
    
    // Skip REST API calls (Gutenberg causes duplicate fires)
    if (defined('REST_REQUEST') && REST_REQUEST) {
        return;
    }
    
    // Skip AJAX and CRON
    if (wp_doing_ajax() || wp_doing_cron()) {
        return;
    }
    
    // Only sync published courses
    if ($post->post_status !== 'publish') {
        return;
    }
    
    // Check permissions
    if (!current_user_can('edit_post', $post_id)) {
        return;
    }
    
    // TRANSIENT-BASED DEBOUNCING
    // Critical: Prevents rapid-fire operations that trigger Cloudflare
    $transient_key = 'llms_sync_lock_' . $post_id;
    if (get_transient($transient_key)) {
        return; // Already processing, skip
    }
    
    // Set 60-second lock
    set_transient($transient_key, 1, 60);
    
    // Perform sync
    llms_sync_course_to_cpt($post_id, $post, false);
    
    // Clean up transient after successful sync
    delete_transient($transient_key);
}
/**
 * Hook into course deletion
 */
add_action('before_delete_post', 'llms_delete_synced_course', 10, 2);
function llms_delete_synced_course($post_id, $post) {
    if ($post->post_type !== 'course') {
        return;
    }
    
    // Find and delete synced post
    $synced_posts = get_posts(array(
        'post_type' => 'llms_content_sync',
        'posts_per_page' => 1,
        'meta_query' => array(
            array(
                'key' => '_source_course_id',
                'value' => $post_id
            )
        )
    ));
    
    if (!empty($synced_posts)) {
        wp_delete_post($synced_posts[0]->ID, true);
    }
}
// ============================================
// CORE SYNC FUNCTION
// ============================================
function llms_sync_course_to_cpt($course_id, $post, $manual = false) {
    // Verify LifterLMS is active
    if (!function_exists('llms_get_post')) {
        error_log('LifterLMS Sync Error: LifterLMS not active');
        return false;
    }
    
    try {
        // Get LifterLMS course object
        $course = new LLMS_Course($course_id);
        
        if (!$course || !$course->get('id')) {
            return false;
        }
        
        // Check if already synced
        $existing_sync = get_posts(array(
            'post_type' => 'llms_content_sync',
            'posts_per_page' => 1,
            'meta_query' => array(
                array(
                    'key' => '_source_course_id',
                    'value' => $course_id
                )
            ),
            'fields' => 'ids'
        ));
        
        $is_update = !empty($existing_sync);
        $sync_post_id = $is_update ? $existing_sync[0] : 0;
        
        // Prepare post data
        $sync_data = array(
            'post_title'   => $course->get('title'),
            'post_content' => $course->get('content'),
            'post_excerpt' => $course->get('excerpt'),
            'post_status'  => 'publish',
            'post_type'    => 'llms_content_sync',
            'post_author'  => $course->get('author')
        );
        
        if ($is_update) {
            $sync_data['ID'] = $sync_post_id;
            $result = wp_update_post($sync_data, true);
        } else {
            $result = wp_insert_post($sync_data, true);
            $sync_post_id = $result;
        }
        
        // Check for errors
        if (is_wp_error($result)) {
            error_log('LifterLMS Sync Error: ' . $result->get_error_message());
            return false;
        }
        
        // Store core metadata
        update_post_meta($sync_post_id, '_source_type', 'lifterlms_course');
        update_post_meta($sync_post_id, '_source_course_id', $course_id);
        update_post_meta($sync_post_id, '_sync_timestamp', current_time('timestamp'));
        update_post_meta($sync_post_id, '_course_permalink', get_permalink($course_id));
        
        // Store basic course data
        $basic_data = array(
            'difficulty'          => $course->get('difficulty'),
            'length'              => $course->get('length'),
            'capacity'            => $course->get('capacity'),
            'capacity_enabled'    => $course->get('enable_capacity'),
            'audio_embed'         => $course->get('audio_embed'),
            'video_embed'         => $course->get('video_embed'),
            'has_prerequisite'    => $course->has_prerequisite('course'),
            'prerequisite_id'     => $course->get('prerequisite'),
            'featured_image_url'  => get_the_post_thumbnail_url($course_id, 'full')
        );
        
        update_post_meta($sync_post_id, '_course_basic_data', $basic_data);
        
        // Get lesson/section counts (lightweight - IDs only)
        $lesson_ids = $course->get_lessons('ids');
        $section_count = count($course->get_sections('sections'));
        
        update_post_meta($sync_post_id, '_lesson_count', count($lesson_ids));
        update_post_meta($sync_post_id, '_section_count', $section_count);
        
        // Store lesson IDs for detail population
        update_post_meta($sync_post_id, '_lesson_ids', $lesson_ids);
        
        // Get taxonomies
        $categories = wp_get_post_terms($course_id, 'course_cat', array('fields' => 'ids'));
        $tags = wp_get_post_terms($course_id, 'course_tag', array('fields' => 'ids'));
        
        if (!empty($categories)) {
            wp_set_object_terms($sync_post_id, $categories, 'course_cat');
        }
        
        if (!empty($tags)) {
            wp_set_object_terms($sync_post_id, $tags, 'course_tag');
        }
        
        // CRITICAL: Fire completion action for detail population
        // This allows Snippet 2 to hook in and populate heavy data
        do_action('llms_content_sync_complete', $course_id, $sync_post_id, $is_update);
        
        return $sync_post_id;
        
    } catch (Exception $e) {
        error_log('LifterLMS Sync Exception: ' . $e->getMessage());
        return false;
    }
}
// ============================================
// RATE LIMITING FOR EXTERNAL API CALLS
// ============================================
/**
 * Throttled external request wrapper
 * Use this if you need to notify external systems
 */
function llms_sync_throttled_request($url, $data = array()) {
    $throttle_key = 'llms_api_throttle_' . md5($url);
    $request_count = get_transient($throttle_key);
    
    // Allow max 10 requests per minute
    if ($request_count && $request_count >= 10) {
        error_log('LifterLMS Sync: Rate limit exceeded for ' . $url);
        return false;
    }
    
    // Increment counter
    set_transient($throttle_key, ($request_count + 1), 60);
    
    // Make request
    return wp_remote_post($url, array(
        'body' => $data,
        'timeout' => 15
    ));
}
// ============================================
// UTILITY FUNCTIONS
// ============================================
/**
 * Get sync status for a course
 */
function llms_get_sync_status($course_id) {
    $synced = get_posts(array(
        'post_type' => 'llms_content_sync',
        'posts_per_page' => 1,
        'meta_query' => array(
            array(
                'key' => '_source_course_id',
                'value' => $course_id
            )
        ),
        'fields' => 'ids'
    ));
    
    return !empty($synced);
}
/**
 * Clear all sync locks (emergency use only)
 */
function llms_clear_all_sync_locks() {
    global $wpdb;
    $wpdb->query("DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_llms_sync_lock_%'");
    $wpdb->query("DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_timeout_llms_sync_lock_%'");
}

Comments

Add a Comment