enrolled_students_layout();
$content = ob_get_clean();
echo AdminTemplate::html_on_wp_admin_screen(
array(
'content' => $content,
'title' => __( 'Students', 'learnpress' ),
'id' => 'lp-enrolled-students',
)
);
}
/**
* Allow callback for AJAX.
*
* @param array $callbacks
*
* @return array
*/
public function allow_callback( array $callbacks ): array {
$callbacks[] = get_class( $this ) . ':render_enrolled_students';
return $callbacks;
}
/**
* Render initial layout with TemplateAJAX::load_content_via_ajax().
*/
public function enrolled_students_layout() {
try {
$page_current = '';
if ( function_exists( 'get_current_screen' ) ) {
$wp_screen = get_current_screen();
if ( $wp_screen ) {
// Page on the Admin screen.
if ( $wp_screen->id === 'learnpress_page_learn-press-students-enrolled' ) {
$page_current = 'learnpress_page_learn-press-students-enrolled';
}
}
}
$args = array(
'id_url' => 'lp-enrolled-students',
'course_id' => LP_Request::get_param( 'course_id', 0 ),
'course_name' => LP_Request::get_param( 'course_name' ),
'paged' => 1,
'search' => lp_request::get_param( 'search' ),
'start_date' => LP_Request::get_param( 'start_date' ),
'end_date' => LP_Request::get_param( 'end_date' ),
'enableUpdateParamsUrl' => false,
);
$call_back = array(
'class' => self::class,
'method' => 'render_enrolled_students',
);
$section = [
'wrap' => sprintf(
'
',
$page_current
),
'toolbar' => $this->html_toolbar(),
'table' => TemplateAJAX::load_content_via_ajax( $args, $call_back ),
'wrap-end' => '
',
];
echo Template::combine_components( $section );
} catch ( Throwable $e ) {
Template::print_message( $e->getMessage(), 'error' );
}
}
/**
* Static render method called by AJAX — returns stdClass{ content }.
*
* Permission check: Admin=full, Instructor=own courses only.
*
* @param array $data
*
* @return stdClass
*/
public static function render_enrolled_students( array $data ): stdClass {
$content = new stdClass();
$content->content = '';
try {
// Check permission
if ( ! current_user_can( UserModel::ROLE_ADMINISTRATOR )
&& ! current_user_can( UserModel::ROLE_INSTRUCTOR ) ) {
throw new Exception( esc_html__( 'You do not have permission to view enrolled students.', 'learnpress' ) );
}
$course_id = abs( LP_Helper::sanitize_params_submitted( $data['course_id'] ?? 0, 'int' ) );
$paged = max( 1, abs( LP_Helper::sanitize_params_submitted( $data['paged'] ?? 1, 'int' ) ) );
$course_name = LP_Helper::sanitize_params_submitted( $data['course_name'] ?? '' );
$search = LP_Helper::sanitize_params_submitted( $data['search'] ?? '' );
$start_date = lp_helper::sanitize_params_submitted( $data['start_date'] ?? '' );
$end_date = lp_helper::sanitize_params_submitted( $data['end_date'] ?? '' );
$per_page = self::PER_PAGE;
// Normalize date range if request is reversed.
if ( $start_date && $end_date && strtotime( $start_date ) > strtotime( $end_date ) ) {
$tmp = $start_date;
$start_date = $end_date;
$end_date = $tmp;
}
$lp_db_user_items = UserItemsDB::getInstance();
$filter = new UserItemsFilter();
$filter->item_type = LP_COURSE_CPT;
$filter->statues = array( 'enrolled', 'finished' );
$filter->limit = $per_page;
$filter->page = $paged;
$filter->order_by = 'ui.start_time';
$filter->order = 'DESC';
$filter->field_count = 'ui.user_item_id';
$filter->only_fields = array(
'ui.user_item_id',
'ui.user_id',
'ui.item_id',
'ui.start_time',
'ui.status',
'ui.graduation',
'u.display_name',
'u.user_email',
'p.post_title AS course_title',
);
$filter->join[] = "JOIN {$lp_db_user_items->wpdb->posts} p ON ui.item_id = p.ID";
$filter->join[] = "JOIN {$lp_db_user_items->wpdb->users} u ON ui.user_id = u.ID";
$instructor_id = get_current_user_id();
if ( $instructor_id > 0 && current_user_can( UserModel::ROLE_INSTRUCTOR ) ) {
$filter->where[] = $lp_db_user_items->wpdb->prepare( 'AND p.post_author = %d', $instructor_id );
}
if ( $course_id > 0 ) {
$filter->item_id = $course_id;
} elseif ( ! empty( $course_name ) ) {
$course_name_like = '%' . $lp_db_user_items->wpdb->esc_like( $course_name ) . '%';
$filter->where[] = $lp_db_user_items->wpdb->prepare(
'AND p.post_title LIKE %s',
$course_name_like
);
}
if ( ! empty( $search ) ) {
$search_like = '%' . $lp_db_user_items->wpdb->esc_like( $search ) . '%';
$filter->where[] = $lp_db_user_items->wpdb->prepare(
'AND ( u.display_name LIKE %s OR u.user_email LIKE %s )',
$search_like,
$search_like
);
}
if ( ! empty( $start_date ) ) {
$filter->where[] = $lp_db_user_items->wpdb->prepare(
'AND ui.start_time >= %s',
$start_date . ' 00:00:00'
);
}
if ( ! empty( $end_date ) ) {
$filter->where[] = $lp_db_user_items->wpdb->prepare(
'AND ui.start_time <= %s',
$end_date . ' 23:59:59'
);
}
$filter = apply_filters(
'learn-press/filter-enrolled-students',
$filter
);
$total_rows = 0;
$rows = $lp_db_user_items->get_user_items( $filter, $total_rows );
if ( ! is_array( $rows ) ) {
$rows = array();
}
// Build HTML.
$html = self::instance()->html_table(
$rows,
array(
'total' => $total_rows,
'paged' => $paged,
'per_page' => $per_page,
'students-of-course' => $course_id,
)
);
$content->content = $html;
} catch ( Throwable $e ) {
$content->content = Template::print_message( $e->getMessage(), 'error', false );
LP_Debug::error_log( $e );
}
return $content;
}
/**
* HTML builder: toolbar (course filter, search).
*
* @return string
*/
public function html_toolbar(): string {
$courses = array();
$data_get = LP_Helper::sanitize_params_submitted( $_GET );
$selected_course = abs( LP_Helper::sanitize_params_submitted( $data_get['course_id'] ?? 0, 'int' ) );
$search_course = LP_Helper::sanitize_params_submitted( $data_get['course_name'] ?? '' );
$search_student = LP_Helper::sanitize_params_submitted( $data_get['search'] ?? '' );
$search_start = lp_helper::sanitize_params_submitted( $data_get['start_date'] ?? '' );
$search_end = lp_helper::sanitize_params_submitted( $data_get['end_date'] ?? '' );
$html_fields = sprintf(
'
',
esc_html__( 'Course Filter', 'learnpress' ),
esc_attr( $search_course ),
esc_attr__( 'Search course...', 'learnpress' ),
esc_html__( 'Student', 'learnpress' ),
esc_attr( $search_student ),
esc_attr__( 'Enter student name or email', 'learnpress' ),
esc_html__( 'Start Date', 'learnpress' ),
esc_attr( $search_start ),
esc_html__( 'End date', 'learnpress' ),
esc_attr( $search_end ),
''
);
$html_btn_actions = sprintf(
'
',
esc_html__( 'Search', 'learnpress' ),
esc_html__( 'Clear Filter', 'learnpress' )
);
$html_toolbar = AdminTemplate::html_form_filter(
array(
'classes' => 'lp-enrolled-students-form',
'fields' => $html_fields,
'btn_actions' => $html_btn_actions,
)
);
return apply_filters(
'learn-press/admin/enrolled-students/toolbar/section',
$html_toolbar,
$courses,
$selected_course
);
}
/**
* Render toolbar used inside View Students modal.
*
* @return string
*/
public function html_toolbar_modal(): string {
$html_fields = sprintf(
'
',
esc_html__( 'Student', 'learnpress' ),
esc_attr__( 'Enter student name or email', 'learnpress' ),
esc_html__( 'Start Date', 'learnpress' ),
esc_html__( 'End Date', 'learnpress' )
);
$html_btn_actions = sprintf(
'
',
esc_html__( 'Search', 'learnpress' ),
esc_html__( 'Clear Filter', 'learnpress' )
);
return AdminTemplate::html_form_filter(
array(
'id' => 'lp-modal-enrolled-form',
'classes' => 'lp-enrolled-students-form lp-enrolled-students-form--modal lp-enrolled-students-table-toolbar--modal',
'fields' => $html_fields,
'btn_actions' => $html_btn_actions,
)
);
}
/**
* Render lp-target layout used in View Students modal.
*
* @return string
*/
public function html_modal_students_target(): string {
$args = array(
'id_url' => 'lp-modal-enrolled-students',
'course_id' => 0,
'paged' => 1,
'enableScrollToView' => false,
'enableUpdateParamsUrl' => false,
'html_no_load_ajax_first' => sprintf(
'%s
',
'width: 95%;',
esc_html__( 'Loading', 'learnpress' )
),
);
$call_back = array(
'class' => self::class,
'method' => 'render_enrolled_students',
);
$section = array(
'wrap' => '',
'target' => TemplateAJAX::load_content_via_ajax( $args, $call_back ),
'wrap-end' => '
',
);
return Template::combine_components( $section );
}
/**
* Print HTML template for modal toolbar.
*
* @return void
*/
public function print_modal_toolbar_template() {
$should_print = false;
if ( is_admin() ) {
$screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null;
if ( $screen && isset( $screen->id ) && $screen->id === 'edit-' . LP_COURSE_CPT ) {
$should_print = true;
}
} elseif ( class_exists( 'LP_Page_Controller' ) && defined( 'LP_PAGE_PROFILE' ) ) {
$should_print = LP_Page_Controller::page_current() === LP_PAGE_PROFILE;
}
if ( ! $should_print ) {
return;
}
echo '';
echo '';
}
/**
* HTML builder: table wrapper.
*
* @param array $rows DB result rows.
* @param array $meta [ total, paged, per_page ].
*
* @return string
*/
private function html_table( array $rows, array $meta ): string {
if ( empty( $rows ) ) {
$section_empty = array(
'wrap' => '',
'empty' => sprintf(
'
',
esc_html__( 'No students found.', 'learnpress' )
),
'wrap-end' => '
',
);
$section_empty = apply_filters(
'learn-press/admin/enrolled-students/table/empty/section',
$section_empty,
$rows,
$meta
);
return Template::combine_components( $section_empty );
}
$rows_html = array();
$results_map = array();
foreach ( $rows as $item ) {
$userCourseModel = new UserCourseModel( $item );
$rows_html[] = $this->html_student_row( $userCourseModel, $meta );
}
$rows_html = apply_filters(
'learn-press/admin/enrolled-students/table/rows-html',
$rows_html,
$rows,
$results_map,
$meta
);
$table_args = array(
'class_table' => 'lp-enrolled-students-table',
'header' => array(
'student' => array(
'class' => 'lp-col-student',
'title' => esc_html__( 'Student', 'learnpress' ),
),
'course' => array(
'class' => 'lp-col-course',
'title' => esc_html__( 'Course', 'learnpress' ),
),
'date' => array(
'class' => 'lp-col-date',
'title' => esc_html__( 'Enrolled Date', 'learnpress' ),
),
'progress' => array(
'class' => 'lp-col-progress',
'title' => esc_html__( 'Progress', 'learnpress' ),
),
'status' => array(
'class' => 'lp-col-status',
'title' => esc_html__( 'Status', 'learnpress' ),
),
),
'body' => array(
'rows_html' => Template::combine_components( $rows_html ),
),
);
$section_footer = array(
'wrap' => '',
);
$section_footer = apply_filters(
'learn-press/admin/enrolled-students/table/footer/section',
$section_footer,
$rows,
$results_map,
$meta
);
$table_args['footer'] = Template::combine_components( $section_footer );
$table_args = apply_filters(
'learn-press/admin/enrolled-students/table/args',
$table_args,
$rows,
$results_map,
$meta
);
if ( ! empty( $meta['students-of-course'] ) ) {
unset( $table_args['header']['course'] ); // Remove Course column if filtering by course, as it's redundant.
}
$section = array(
'wrap' => '',
'table' => TableListTemplate::instance()->html_table( $table_args ),
'wrap-end' => '
',
);
$section = apply_filters(
'learn-press/admin/enrolled-students/table/section',
$section,
$rows,
$results_map,
$meta
);
return Template::combine_components( $section );
}
/**
* HTML builder: single student row.
*
* @param UserCourseModel $userCourseModel
*
* @return string
*/
private function html_student_row( UserCourseModel $userCourseModel, array $data = [] ): string {
// Avatar initials.
$userModel = $userCourseModel->get_user_model();
if ( ! $userModel instanceof UserModel ) {
return '';
}
$courseModel = $userCourseModel->get_course_model();
if ( ! $courseModel instanceof CourseModel ) {
return '';
}
// Progress.
$course_result = $userCourseModel->calculate_course_results();
$progress = (float) ( $course_result['result'] ?? 0 );
// Status badge.
$graduation = $userCourseModel->get_graduation();
$status_raw = $graduation !== UserItemModel::GRADUATION_IN_PROGRESS
? $userCourseModel->get_graduation()
: $userCourseModel->get_status();
$status_label = ucfirst( str_replace( array( '-', '_' ), ' ', $status_raw ) );
$badge_class = 'lp-badge--' . sanitize_html_class( $status_raw );
// Date.
$date = UserCourseTemplate::instance()->html_start_date_time( $userCourseModel, false );
$user_display_name = $userModel->get_display_name();
if ( empty( $user_display_name ) ) {
$user_display_name = $userModel->get_username();
}
$section = array(
'row' => '',
'student-cell-open' => '| ',
'avatar' => SingleInstructorTemplate::instance()->html_avatar( $userModel ),
'meta-open' => ' ',
'name' => sprintf(
'%s',
esc_html( $user_display_name )
),
'email' => sprintf(
'%s',
esc_html( $userModel->get_email() )
),
'meta-close' => ' ',
'student-cell-close' => ' | ',
'course-cell' => sprintf(
'%s | ',
esc_url_raw( $courseModel->get_permalink() ),
$courseModel->get_title()
),
'date-cell' => sprintf(
'%s | ',
wp_kses_post( $date )
),
'progress-cell-open' => '',
'progress-bar' => sprintf(
' ',
$progress
),
'progress-text' => sprintf(
'%d%%',
$progress
),
'progress-cell-close' => ' | ',
'status-cell-open' => '',
'status-badge' => sprintf(
'%s',
esc_attr( $badge_class ),
esc_html( $status_label )
),
'status-cell-close' => ' | ',
'row-end' => '
',
);
if ( ! empty( $data['students-of-course'] ) ) {
unset( $section['course-cell'] ); // Remove Course cell if filtering by course, as it's redundant.
}
$section = apply_filters(
'learn-press/admin/enrolled-students/row/section',
$section,
$userCourseModel
);
return Template::combine_components( $section );
}
/**
* HTML builder: pagination.
*
* Uses .page-numbers class for loadAJAX.js compatibility.
*
* @param int $total
* @param int $paged
* @param int $per_page
*
* @return string
*/
private function html_pagination( int $total, int $paged, int $per_page ): string {
$total_pages = max( 1, ceil( $total / $per_page ) );
$pagination_items = Template::instance()->html_pagination(
array(
'total_pages' => $total_pages,
'paged' => $paged,
'wrapper' => array(
'',
),
)
);
$pagination_items = apply_filters(
'learn-press/admin/enrolled-students/pagination/items',
$pagination_items,
$total,
$paged,
$per_page,
$total_pages
);
$section = array(
'wrap' => '',
);
$section = apply_filters(
'learn-press/admin/enrolled-students/pagination/section',
$section,
$total,
$paged,
$per_page,
$total_pages
);
return Template::combine_components( $section );
}
}