| |
| <?php
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| defined( 'ABSPATH' ) || exit;
|
|
|
|
|
|
|
|
|
| add_action( 'llms_content_sync_complete', 'llms_populate_course_details', 10, 3 );
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| function llms_populate_course_details( $course_id, $sync_post_id, $is_update = false ) {
|
|
|
|
|
| if ( ! class_exists( 'LifterLMS' ) ) {
|
| error_log( 'LLMS_DETAIL: LifterLMS not active' );
|
| return;
|
| }
|
|
|
|
|
| $course = llms_get_post( $course_id );
|
| if ( ! $course || ! is_a( $course, 'LLMS_Course' ) ) {
|
| error_log( 'LLMS_DETAIL: Invalid course ID ' . $course_id );
|
| return;
|
| }
|
|
|
|
|
| update_post_meta( $sync_post_id, '_source_course_id', $course_id );
|
|
|
|
|
| $start_time = microtime( true );
|
|
|
| try {
|
|
|
| llms_populate_section_data( $course, $sync_post_id );
|
|
|
|
|
| llms_populate_lesson_data( $course, $sync_post_id );
|
|
|
|
|
| llms_populate_quiz_data( $course, $sync_post_id );
|
|
|
|
|
| llms_populate_assignment_data( $course, $sync_post_id );
|
|
|
|
|
| llms_populate_certificate_data( $course, $sync_post_id );
|
|
|
|
|
| if ( function_exists( 'update_field' ) ) {
|
| llms_populate_acf_fields( $course, $sync_post_id );
|
| }
|
|
|
|
|
| if ( function_exists( 'llms_update_course_links' ) ) {
|
| llms_update_course_links( $sync_post_id, $course_id );
|
| }
|
|
|
|
|
| delete_transient( 'llms_detail_populate_' . $sync_post_id );
|
|
|
|
|
| $execution_time = microtime( true ) - $start_time;
|
| error_log( sprintf( 'LLMS_DETAIL: Populated course %d in %.2f seconds', $course_id, $execution_time ) );
|
|
|
| } catch ( Exception $e ) {
|
| error_log( 'LLMS_DETAIL: Error populating course ' . $course_id . ': ' . $e->getMessage() );
|
| }
|
| }
|
|
|
|
|
|
|
|
|
| function llms_populate_section_data( $course, $sync_post_id ) {
|
| $sections = $course->get_sections( 'sections' );
|
| $section_details = [];
|
|
|
| foreach ( $sections as $section ) {
|
| $lesson_count = count( $section->get_lessons( 'ids' ) );
|
|
|
| $section_details[] = sprintf(
|
| "Section %d: %s\n- %d lessons\n- %s",
|
| $section->get( 'order' ),
|
| $section->get( 'title' ),
|
| $lesson_count,
|
| $section->get( 'content' ) ? 'Has description' : 'No description'
|
| );
|
| }
|
|
|
| $sections_list = implode( "\n\n", $section_details );
|
| update_post_meta( $sync_post_id, 'sections_details_list', $sections_list );
|
|
|
|
|
| error_log( sprintf( 'LLMS_DETAIL: Processed %d sections for course %d',
|
| count( $sections ),
|
| $course->get( 'id' )
|
| ) );
|
| }
|
|
|
|
|
|
|
|
|
| function llms_populate_lesson_data( $course, $sync_post_id ) {
|
| $lessons = $course->get_lessons( 'lessons' );
|
| $lesson_details = [];
|
| $steps_details = [];
|
|
|
| foreach ( $lessons as $lesson ) {
|
|
|
| $lesson_info = sprintf(
|
| "Lesson: %s\n- Type: %s\n- Points: %d\n- Free: %s",
|
| $lesson->get( 'title' ),
|
| $lesson->get( 'video_embed' ) ? 'Video' : 'Standard',
|
| $lesson->get( 'points' ),
|
| $lesson->is_free() ? 'Yes' : 'No'
|
| );
|
|
|
|
|
| if ( $lesson->has_quiz() ) {
|
| $lesson_info .= "\n- Has Quiz: Yes";
|
| }
|
|
|
|
|
| $assignment_id = $lesson->get( 'assignment' );
|
| if ( $assignment_id ) {
|
| $lesson_info .= "\n- Has Assignment: Yes";
|
| }
|
|
|
|
|
| if ( $lesson->has_prerequisite() ) {
|
| $prereq_id = $lesson->get( 'prerequisite' );
|
| $prereq_post = get_post( $prereq_id );
|
| if ( $prereq_post ) {
|
| $lesson_info .= "\n- Prerequisite: " . $prereq_post->post_title;
|
| }
|
| }
|
|
|
|
|
| $drip_method = $lesson->get( 'drip_method' );
|
| if ( $drip_method && $drip_method !== '' ) {
|
| $lesson_info .= "\n- Drip: " . ucfirst( $drip_method );
|
| $days = $lesson->get( 'days_before_available' );
|
| if ( $days ) {
|
| $lesson_info .= " (after {$days} days)";
|
| }
|
| }
|
|
|
| $lesson_details[] = $lesson_info;
|
|
|
|
|
| $steps_details[] = sprintf(
|
| "Step %d: %s (%s)",
|
| count( $steps_details ) + 1,
|
| $lesson->get( 'title' ),
|
| $lesson->is_free() ? 'Free' : 'Locked'
|
| );
|
| }
|
|
|
|
|
| $lessons_list = implode( "\n\n", $lesson_details );
|
| update_post_meta( $sync_post_id, 'lessons_details_list', $lessons_list );
|
|
|
| $steps_list = implode( "\n", $steps_details );
|
| update_post_meta( $sync_post_id, 'steps_details_list', $steps_list );
|
|
|
| error_log( sprintf( 'LLMS_DETAIL: Processed %d lessons', count( $lessons ) ) );
|
| }
|
|
|
|
|
|
|
|
|
| function llms_populate_quiz_data( $course, $sync_post_id ) {
|
| $lessons = $course->get_lessons( 'lessons' );
|
| $quiz_details = [];
|
|
|
| foreach ( $lessons as $lesson ) {
|
| if ( ! $lesson->has_quiz() ) {
|
| continue;
|
| }
|
|
|
| $quiz = $lesson->get_quiz();
|
| if ( ! $quiz ) {
|
| continue;
|
| }
|
|
|
| $questions = $quiz->get_questions();
|
|
|
| $quiz_info = sprintf(
|
| "Quiz: %s\n- Lesson: %s\n- Questions: %d\n- Pass Rate: %d%%\n- Time Limit: %s\n- Attempts: %s",
|
| $quiz->get( 'title' ),
|
| $lesson->get( 'title' ),
|
| count( $questions ),
|
| $quiz->get( 'passing_percent' ),
|
| $quiz->get( 'time_limit' ) ? $quiz->get( 'time_limit' ) . ' minutes' : 'None',
|
| $quiz->get( 'allowed_attempts' ) ?: 'Unlimited'
|
| );
|
|
|
|
|
| $question_types = [];
|
| foreach ( $questions as $question ) {
|
| $type = get_post_meta( $question->get( 'id' ), '_llms_question_type', true );
|
| if ( ! isset( $question_types[ $type ] ) ) {
|
| $question_types[ $type ] = 0;
|
| }
|
| $question_types[ $type ]++;
|
| }
|
|
|
| if ( ! empty( $question_types ) ) {
|
| $quiz_info .= "\n- Question Types: ";
|
| $types = [];
|
| foreach ( $question_types as $type => $count ) {
|
| $types[] = ucfirst( $type ) . " ({$count})";
|
| }
|
| $quiz_info .= implode( ', ', $types );
|
| }
|
|
|
| $quiz_details[] = $quiz_info;
|
| }
|
|
|
| $quizzes_list = ! empty( $quiz_details )
|
| ? implode( "\n\n", $quiz_details )
|
| : 'No quizzes in this course';
|
|
|
| update_post_meta( $sync_post_id, 'quizzes_details_list', $quizzes_list );
|
|
|
| error_log( sprintf( 'LLMS_DETAIL: Found %d quizzes', count( $quiz_details ) ) );
|
| }
|
|
|
|
|
|
|
|
|
| function llms_populate_assignment_data( $course, $sync_post_id ) {
|
| $lessons = $course->get_lessons( 'lessons' );
|
| $assignment_details = [];
|
|
|
| foreach ( $lessons as $lesson ) {
|
| $assignment_id = $lesson->get( 'assignment' );
|
|
|
| if ( ! $assignment_id || get_post_type( $assignment_id ) !== 'llms_assignment' ) {
|
| continue;
|
| }
|
|
|
| $assignment = get_post( $assignment_id );
|
| if ( ! $assignment ) {
|
| continue;
|
| }
|
|
|
| $assignment_type = get_post_meta( $assignment_id, '_llms_assignment_type', true );
|
| $points_enabled = get_post_meta( $assignment_id, '_llms_points', true );
|
| $points = get_post_meta( $assignment_id, '_llms_points_value', true );
|
| $passing_grade = get_post_meta( $assignment_id, '_llms_assignment_passing_grade', true );
|
|
|
| $assignment_info = sprintf(
|
| "Assignment: %s\n- Lesson: %s\n- Type: %s\n- Points: %s\n- Pass Grade: %d%%",
|
| $assignment->post_title,
|
| $lesson->get( 'title' ),
|
| ucfirst( $assignment_type ?: 'upload' ),
|
| $points_enabled === 'yes' ? $points : 'Not graded',
|
| intval( $passing_grade )
|
| );
|
|
|
|
|
| if ( ! empty( $assignment->post_content ) ) {
|
| $content_preview = wp_trim_words( strip_tags( $assignment->post_content ), 20 );
|
| $assignment_info .= "\n- Instructions: " . $content_preview;
|
| }
|
|
|
| $assignment_details[] = $assignment_info;
|
| }
|
|
|
| $assignments_list = ! empty( $assignment_details )
|
| ? implode( "\n\n", $assignment_details )
|
| : 'No assignments in this course';
|
|
|
| update_post_meta( $sync_post_id, 'assignments_details_list', $assignments_list );
|
|
|
| error_log( sprintf( 'LLMS_DETAIL: Found %d assignments', count( $assignment_details ) ) );
|
| }
|
|
|
|
|
|
|
|
|
| function llms_populate_certificate_data( $course, $sync_post_id ) {
|
| global $wpdb;
|
|
|
|
|
| $engagements = $wpdb->get_results( $wpdb->prepare(
|
| "SELECT p.*, pm.meta_value as engagement_type, pm2.meta_value as engagement_id
|
| FROM {$wpdb->posts} p
|
| LEFT JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id AND pm.meta_key = '_llms_engagement_type'
|
| LEFT JOIN {$wpdb->postmeta} pm2 ON p.ID = pm2.post_id AND pm2.meta_key = '_llms_engagement'
|
| WHERE p.post_type = 'llms_engagement'
|
| AND p.post_status = 'publish'
|
| AND p.ID IN (
|
| SELECT post_id FROM {$wpdb->postmeta}
|
| WHERE meta_key = '_llms_engagement_trigger_post'
|
| AND meta_value = %d
|
| )",
|
| $course->get( 'id' )
|
| ) );
|
|
|
| $certificate_details = [];
|
| $engagement_details = [];
|
|
|
| foreach ( $engagements as $engagement ) {
|
| $type = $engagement->engagement_type;
|
| $template_id = $engagement->engagement_id;
|
|
|
| if ( ! $template_id ) {
|
| continue;
|
| }
|
|
|
| $template_post = get_post( $template_id );
|
| if ( ! $template_post ) {
|
| continue;
|
| }
|
|
|
|
|
| $trigger_type = get_post_meta( $engagement->ID, '_llms_trigger_type', true );
|
| $engagement_delay = get_post_meta( $engagement->ID, '_llms_engagement_delay', true );
|
|
|
| $trigger_desc = ucfirst( str_replace( '_', ' ', $trigger_type ) );
|
| if ( $engagement_delay > 0 ) {
|
| $trigger_desc .= " (+{$engagement_delay} days)";
|
| }
|
|
|
| switch ( $type ) {
|
| case 'certificate':
|
| $cert_info = sprintf(
|
| "Certificate: %s\n- Trigger: %s\n- Template ID: %d",
|
| $template_post->post_title,
|
| $trigger_desc,
|
| $template_id
|
| );
|
|
|
|
|
| if ( strpos( $template_post->post_content, '{{' ) !== false ) {
|
| $cert_info .= "\n- Uses merge codes: Yes";
|
| }
|
|
|
| $certificate_details[] = $cert_info;
|
| break;
|
|
|
| case 'email':
|
| $email_info = sprintf(
|
| "Email: %s\n- Trigger: %s",
|
| $template_post->post_title,
|
| $trigger_desc
|
| );
|
| $engagement_details[] = $email_info;
|
| break;
|
|
|
| case 'achievement':
|
| $achievement_info = sprintf(
|
| "Achievement: %s\n- Trigger: %s",
|
| $template_post->post_title,
|
| $trigger_desc
|
| );
|
| $engagement_details[] = $achievement_info;
|
| break;
|
| }
|
| }
|
|
|
|
|
| $certificates_list = ! empty( $certificate_details )
|
| ? implode( "\n\n", $certificate_details )
|
| : 'No certificates for this course';
|
|
|
| update_post_meta( $sync_post_id, 'certificates_details_list', $certificates_list );
|
|
|
|
|
| $all_engagements = array_merge( $certificate_details, $engagement_details );
|
| $engagements_list = ! empty( $all_engagements )
|
| ? implode( "\n\n", $all_engagements )
|
| : 'No engagements configured';
|
|
|
| update_post_meta( $sync_post_id, 'engagements_details_list', $engagements_list );
|
|
|
| error_log( sprintf( 'LLMS_DETAIL: Found %d certificates, %d total engagements',
|
| count( $certificate_details ),
|
| count( $all_engagements )
|
| ) );
|
| }
|
|
|
|
|
|
|
|
|
| function llms_populate_enrollment_data( $course, $sync_post_id ) {
|
|
|
|
|
| $course_id = $course->get( 'id' );
|
| $enrollment_details = [];
|
|
|
|
|
| $is_free_meta = get_post_meta( $course_id, '_llms_is_free', true );
|
| if ( $is_free_meta === 'yes' ) {
|
| $enrollment_details[] = "Course Type: FREE - Open enrollment";
|
| }
|
|
|
|
|
| $access_plans = new WP_Query( [
|
| 'post_type' => 'llms_access_plan',
|
| 'posts_per_page' => -1,
|
| 'meta_query' => [
|
| [
|
| 'key' => '_llms_product_id',
|
| 'value' => $course_id,
|
| 'compare' => '='
|
| ]
|
| ]
|
| ] );
|
|
|
|
|
| if ( $access_plans->have_posts() ) {
|
| foreach ( $access_plans->posts as $plan_post ) {
|
| $plan = new LLMS_Access_Plan( $plan_post->ID );
|
|
|
| $plan_info = sprintf(
|
| "Access Plan: %s\n- Price: %s\n- Period: %s\n- Trial: %s",
|
| $plan->get( 'title' ),
|
| $plan->is_free() ? 'Free' : '$' . $plan->get( 'price' ),
|
| $plan->get( 'access_period' ) ?: 'Lifetime',
|
| $plan->has_trial() ? 'Yes (' . $plan->get( 'trial_length' ) . ' days)' : 'No'
|
| );
|
|
|
|
|
| if ( ! $plan->is_free() ) {
|
| $frequency = $plan->get( 'frequency' );
|
| if ( $frequency > 0 ) {
|
| $plan_info .= sprintf( "\n- Payment: Every %d %s",
|
| $frequency,
|
| $plan->get( 'period' )
|
| );
|
| }
|
| }
|
|
|
|
|
| $enrollment_period = $plan->get( 'enrollment_period' );
|
| if ( $enrollment_period ) {
|
| $plan_info .= "\n- Enrollment: " . ucfirst( $enrollment_period );
|
| }
|
|
|
| $enrollment_details[] = $plan_info;
|
| }
|
| }
|
|
|
|
|
| $restriction_msg = get_post_meta( $course_id, '_llms_content_restricted_message', true );
|
| if ( $restriction_msg ) {
|
| $enrollment_details[] = "Restriction Message:\n" . wp_trim_words( $restriction_msg, 30 );
|
| }
|
|
|
|
|
| if ( $course->has_prerequisite( 'course' ) ) {
|
|
|
| $prereq_id = $course->get_prerequisite_id( 'course' );
|
| $prereq_course = get_post( $prereq_id );
|
| if ( $prereq_course ) {
|
| $enrollment_details[] = "Prerequisite Course: " . $prereq_course->post_title;
|
| }
|
| }
|
|
|
| $enrollment_list = ! empty( $enrollment_details )
|
| ? implode( "\n\n", $enrollment_details )
|
| : 'Open enrollment - no restrictions';
|
|
|
| update_post_meta( $sync_post_id, 'enrollment_details_list', $enrollment_list );
|
|
|
| error_log( sprintf( 'LLMS_DETAIL: Found %d access plans', $access_plans->post_count ) );
|
| }
|
|
|
|
|
|
|
|
|
| function llms_populate_acf_fields( $course, $sync_post_id ) {
|
|
|
|
|
| $lessons = $course->get_lessons( 'lessons' );
|
| $total_minutes = 0;
|
|
|
| foreach ( $lessons as $lesson ) {
|
|
|
| $content = $lesson->get( 'content' );
|
| $word_count = str_word_count( strip_tags( $content ) );
|
| $read_time = ceil( $word_count / 200 );
|
|
|
|
|
| $video_embed = $lesson->get( 'video_embed' );
|
| if ( $video_embed ) {
|
| $read_time += 10;
|
| }
|
|
|
| $total_minutes += $read_time;
|
| }
|
|
|
| $hours = floor( $total_minutes / 60 );
|
| $minutes = $total_minutes % 60;
|
| $duration_estimate = sprintf( '%d hours %d minutes', $hours, $minutes );
|
|
|
| update_field( 'estimated_duration', $duration_estimate, $sync_post_id );
|
|
|
|
|
| $complexity_score = llms_calculate_complexity_score( $course );
|
| update_field( 'complexity_score', $complexity_score, $sync_post_id );
|
|
|
|
|
| if ( function_exists( 'llms_get_enrolled_students' ) ) {
|
| $enrolled_students = llms_get_enrolled_students( $course->get( 'id' ), 'enrolled' );
|
| $enrollment_count = is_array( $enrolled_students ) ? count( $enrolled_students ) : 0;
|
| update_field( 'current_enrollments', $enrollment_count, $sync_post_id );
|
| }
|
|
|
|
|
| llms_populate_enrollment_data( $course, $sync_post_id );
|
|
|
| error_log( 'LLMS_DETAIL: ACF fields populated' );
|
| }
|
|
|
|
|
|
|
|
|
| function llms_calculate_complexity_score( $course ) {
|
| $score = 0;
|
|
|
|
|
| $sections = $course->get_sections( 'sections' );
|
| $score += count( $sections ) * 2;
|
|
|
|
|
| $lessons = $course->get_lessons( 'lessons' );
|
| $score += count( $lessons );
|
|
|
|
|
| foreach ( $lessons as $lesson ) {
|
| if ( $lesson->has_quiz() ) {
|
| $score += 3;
|
| }
|
| if ( $lesson->get( 'assignment' ) ) {
|
| $score += 2;
|
| }
|
| }
|
|
|
|
|
| if ( $course->has_prerequisite( 'course' ) ) {
|
| $score += 5;
|
| }
|
|
|
|
|
| foreach ( $lessons as $lesson ) {
|
| if ( $lesson->get( 'drip_method' ) ) {
|
| $score += 1;
|
| }
|
| }
|
|
|
|
|
| $normalized = min( 10, max( 1, round( $score / 10 ) ) );
|
|
|
| return $normalized;
|
| }
|
|
|
|
|
|
|
|
|
| add_action( 'admin_init', function() {
|
| if ( isset( $_GET['llms_detail_cleanup'] ) && current_user_can( 'manage_options' ) ) {
|
|
|
|
|
| global $wpdb;
|
| $wpdb->query( "DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_llms_detail_populate_%'" );
|
| $wpdb->query( "DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_timeout_llms_detail_populate_%'" );
|
|
|
| wp_die( 'Detail population transients cleared!' );
|
| }
|
| } );
|
| |
| |
Comments