| |
| <?php
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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() {
|
|
|
| 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() {
|
|
|
| @ini_set('memory_limit', '512M');
|
| set_time_limit(300);
|
|
|
|
|
| $courses = get_posts(array(
|
| 'post_type' => 'course',
|
| 'posts_per_page' => -1,
|
| 'post_status' => 'publish'
|
| ));
|
|
|
|
|
| $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++;
|
| }
|
|
|
|
|
| unset($course);
|
| }
|
|
|
|
|
| wp_suspend_cache_invalidation($cache_suspended);
|
|
|
| return $synced_count;
|
| }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| add_action('edit_post_course', 'llms_auto_sync_course', 10, 2);
|
|
|
| function llms_auto_sync_course($post_id, $post) {
|
|
|
|
|
|
|
| if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
|
| return;
|
| }
|
|
|
|
|
| if (wp_is_post_revision($post_id)) {
|
| return;
|
| }
|
|
|
|
|
| if (defined('REST_REQUEST') && REST_REQUEST) {
|
| return;
|
| }
|
|
|
|
|
| if (wp_doing_ajax() || wp_doing_cron()) {
|
| return;
|
| }
|
|
|
|
|
| if ($post->post_status !== 'publish') {
|
| return;
|
| }
|
|
|
|
|
| if (!current_user_can('edit_post', $post_id)) {
|
| return;
|
| }
|
|
|
|
|
|
|
| $transient_key = 'llms_sync_lock_' . $post_id;
|
| if (get_transient($transient_key)) {
|
| return;
|
| }
|
|
|
|
|
| set_transient($transient_key, 1, 60);
|
|
|
|
|
| llms_sync_course_to_cpt($post_id, $post, false);
|
|
|
|
|
| delete_transient($transient_key);
|
| }
|
|
|
|
|
|
|
|
|
| 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;
|
| }
|
|
|
|
|
| $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);
|
| }
|
| }
|
|
|
|
|
|
|
|
|
|
|
| function llms_sync_course_to_cpt($course_id, $post, $manual = false) {
|
|
|
| if (!function_exists('llms_get_post')) {
|
| error_log('LifterLMS Sync Error: LifterLMS not active');
|
| return false;
|
| }
|
|
|
| try {
|
|
|
| $course = new LLMS_Course($course_id);
|
|
|
| if (!$course || !$course->get('id')) {
|
| return false;
|
| }
|
|
|
|
|
| $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;
|
|
|
|
|
| $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;
|
| }
|
|
|
|
|
| if (is_wp_error($result)) {
|
| error_log('LifterLMS Sync Error: ' . $result->get_error_message());
|
| return false;
|
| }
|
|
|
|
|
| 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));
|
|
|
|
|
| $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);
|
|
|
|
|
| $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);
|
|
|
|
|
| update_post_meta($sync_post_id, '_lesson_ids', $lesson_ids);
|
|
|
|
|
| $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');
|
| }
|
|
|
|
|
|
|
| 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;
|
| }
|
| }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| function llms_sync_throttled_request($url, $data = array()) {
|
| $throttle_key = 'llms_api_throttle_' . md5($url);
|
| $request_count = get_transient($throttle_key);
|
|
|
|
|
| if ($request_count && $request_count >= 10) {
|
| error_log('LifterLMS Sync: Rate limit exceeded for ' . $url);
|
| return false;
|
| }
|
|
|
|
|
| set_transient($throttle_key, ($request_count + 1), 60);
|
|
|
|
|
| return wp_remote_post($url, array(
|
| 'body' => $data,
|
| 'timeout' => 15
|
| ));
|
| }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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);
|
| }
|
|
|
|
|
|
|
|
|
| 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