html_content( $user );
} catch ( Throwable $e ) {
error_log( __METHOD__ . ': ' . $e->getMessage() );
}
echo Template::combine_components(
[
'wrapper' => '
',
'header' => $this->html_header(),
'content' => $html,
'wrapper_end' => '
',
]
);
}
private function html_header(): string {
return Template::combine_components(
[
'wrapper' => '',
]
);
}
/**
* Render dashboard content.
*
* @param UserModel $user
* @return string
*/
private function html_content( UserModel $user ): string {
$is_admin = user_can( $user->get_id(), 'administrator' );
if ( $is_admin ) {
$statistic = $this->get_admin_statistic();
} else {
$statistic = $user->get_instructor_statistic();
}
$stats_html = $this->html_statistics_cards( $statistic, $is_admin );
$charts_html = $this->html_charts_section( $is_admin, $user->get_id() );
$top_instructors_html = $is_admin ? $this->html_top_instructors_section() : '';
$charts_row_class = $is_admin
? 'lp-cb-dashboard__charts-row lp-cb-dashboard__charts-row--admin'
: 'lp-cb-dashboard__charts-row lp-cb-dashboard__charts-row--instructor';
$top_courses_html = $this->html_top_courses_section( $is_admin ? 0 : $user->get_id() );
$recent_courses_html = $this->html_recent_courses_section( $user );
$quick_actions_html = $this->html_quick_actions();
return Template::combine_components(
[
'stats' => $stats_html,
'charts_row' => sprintf(
'%s%s
',
esc_attr( $charts_row_class ),
$charts_html,
$top_instructors_html
),
'top_courses' => $top_courses_html,
'quick_actions' => $quick_actions_html,
'recent_courses' => $recent_courses_html,
]
);
}
/**
* Get global statistics for admin.
*
* @return array
*/
private function get_admin_statistic(): array {
$statistic = array(
'total_course' => 0,
'published_course' => 0,
'pending_course' => 0,
'total_student' => 0,
'total_instructor' => 0,
);
try {
global $wpdb;
// Course counts via wp_count_posts
$course_counts = wp_count_posts( LP_COURSE_CPT );
$statistic['total_course'] = intval( $course_counts->publish ?? 0 )
+ intval( $course_counts->pending ?? 0 )
+ intval( $course_counts->draft ?? 0 )
+ intval( $course_counts->private ?? 0 );
$statistic['published_course'] = intval( $course_counts->publish ?? 0 );
$statistic['pending_course'] = intval( $course_counts->pending ?? 0 );
// Total unique students enrolled in any course
$tb_user_items = $wpdb->prefix . 'learnpress_user_items';
$total_students = $wpdb->get_var(
$wpdb->prepare(
"SELECT COUNT(DISTINCT user_id) FROM {$tb_user_items} WHERE item_type = %s",
LP_COURSE_CPT
)
);
$statistic['total_student'] = intval( $total_students );
// Total instructors
$statistic['total_instructor'] = $this->count_total_instructors();
} catch ( Throwable $e ) {
error_log( __METHOD__ . ': ' . $e->getMessage() );
}
return $statistic;
}
/**
* Count total instructors (admin + lp_teacher).
*
* @return int
*/
private function count_total_instructors(): int {
global $wpdb;
$result = $wpdb->get_var(
$wpdb->prepare(
"SELECT COUNT(DISTINCT u.ID)
FROM {$wpdb->users} AS u
INNER JOIN {$wpdb->usermeta} AS um ON um.user_id = u.ID
WHERE um.meta_key = %s
AND (um.meta_value LIKE %s OR um.meta_value LIKE %s)",
$wpdb->prefix . 'capabilities',
'%administrator%',
'%' . LP_TEACHER_ROLE . '%'
)
);
return intval( $result );
}
/**
* Render statistics cards.
*
* @param array $statistic
* @param bool $is_admin
* @return string
*/
private function html_statistics_cards( array $statistic, bool $is_admin ): string {
$cards = [
[
'key' => 'total_course',
'label' => __( 'Total Courses', 'learnpress' ),
'color' => '#ef4444',
'icon' => 'ico-cb-total-courses.svg',
],
[
'key' => 'published_course',
'label' => __( 'Published Courses', 'learnpress' ),
'color' => '#2E91FA',
'icon' => 'ico-cb-published-courses.svg',
],
[
'key' => 'pending_course',
'label' => __( 'Pending Courses', 'learnpress' ),
'color' => '#F8A100',
'icon' => 'ico-cb-pending-courses.svg',
],
[
'key' => 'total_student',
'label' => __( 'Total Students', 'learnpress' ),
'color' => '#28A746',
'icon' => 'ico-cb-total-students.svg',
],
];
// Role-based summary card
if ( $is_admin ) {
$cards[] = [
'key' => 'total_instructor',
'label' => __( 'Total Instructors', 'learnpress' ),
'color' => '#06AED4',
'icon' => 'ico-cb-total-instructors.svg',
];
} else {
$cards[] = [
'key' => 'student_in_progress',
'label' => __( 'Total In-progress Students', 'learnpress' ),
'color' => '#06AED4',
'icon' => 'ico-cb-students-progress.svg',
];
}
$cards_html = '';
foreach ( $cards as $card ) {
$value = $statistic[ $card['key'] ] ?? 0;
$cards_html .= sprintf(
'',
esc_attr( $card['color'] ),
esc_attr( $card['color'] ),
LP_WP_Filesystem::get_icon_svg( $card['icon'] ?? '' ),
esc_html( $card['label'] ),
esc_html( number_format_i18n( $value ) )
);
}
return Template::combine_components(
[
'wrapper' => '',
'content' => $cards_html,
'wrapper_end' => '
',
]
);
}
/**
* Render charts section with Net Sales and Students charts.
*
* @param bool $is_admin
* @param int $user_id
* @return string
*/
private function html_charts_section( bool $is_admin, int $user_id ): string {
$instructor_id = $is_admin ? 0 : $user_id;
$nonce = wp_create_nonce( 'lp_cb_dashboard_nonce' );
// Get initial data - use 'year' for sales and 'previous_days' for students so data appears immediately
try {
$lp_statistic_db = LP_Statistics_DB::getInstance();
$current_date = current_time( 'Y-m-d' );
$sales_data = $lp_statistic_db->get_net_sales_data_scoped( 'year', $current_date, $instructor_id );
$students_data = $lp_statistic_db->get_enrollment_chart_data( 'previous_days', 6, $instructor_id );
// Process chart data using the REST controller's logic
require_once LP_PLUGIN_PATH . 'inc/rest-api/v1/admin/class-lp-admin-rest-statistics-controller.php';
$stats_ctrl = new \LP_REST_Admin_Statistics_Controller();
$sales_chart = $stats_ctrl->process_chart_data(
[
'filter_type' => 'year',
'time' => $current_date,
],
$sales_data
);
$students_chart = $stats_ctrl->process_chart_data(
[
'filter_type' => 'previous_days',
'time' => 6,
],
$students_data
);
} catch ( Throwable $e ) {
$sales_chart = [
'labels' => [],
'data' => [],
];
$students_chart = [
'labels' => [],
'data' => [],
];
error_log( __METHOD__ . ': ' . $e->getMessage() );
}
$filter_options = '
';
$html = sprintf(
'
',
esc_html__( 'Net sales', 'learnpress' ),
esc_attr( $nonce ),
$filter_options,
esc_html__( 'Students', 'learnpress' ),
esc_attr( $nonce ),
esc_html__( 'This week', 'learnpress' ),
esc_html__( 'This month', 'learnpress' ),
esc_html__( 'This year', 'learnpress' ),
wp_json_encode(
[
'sales' => [
'labels' => $sales_chart['labels'] ?? [],
'data' => $sales_chart['data'] ?? [],
],
'students' => [
'labels' => $students_chart['labels'] ?? [],
'data' => $students_chart['data'] ?? [],
],
'ajaxUrl' => admin_url( 'admin-ajax.php' ),
'nonce' => $nonce,
]
)
);
return $html;
}
/**
* Render top instructors section (admin only).
*
* @return string
*/
private function html_top_instructors_section(): string {
try {
$lp_statistic_db = LP_Statistics_DB::getInstance();
$instructors = $lp_statistic_db->get_top_instructors( 4 );
} catch ( Throwable $e ) {
$instructors = [];
error_log( __METHOD__ . ': ' . $e->getMessage() );
}
$items_html = '';
if ( empty( $instructors ) ) {
$items_html = '' . esc_html__( 'No instructors found', 'learnpress' ) . '
';
} else {
foreach ( $instructors as $instructor ) {
$avatar = get_avatar( $instructor->instructor_id, 40 );
$items_html .= sprintf(
'',
$avatar,
esc_html( $instructor->instructor_name ),
sprintf(
/* translators: %s: number of courses */
esc_html( _n( '%s course', '%s courses', $instructor->course_count, 'learnpress' ) ),
number_format_i18n( $instructor->course_count )
),
sprintf(
/* translators: %s: number of students */
esc_html( _n( '%s student', '%s students', $instructor->student_count, 'learnpress' ) ),
number_format_i18n( $instructor->student_count )
)
);
}
}
return sprintf(
'',
esc_html__( 'Top Instructors', 'learnpress' ),
$items_html
);
}
/**
* Render top courses section.
*
* @param int $user_id
* @return string
*/
private function html_top_courses_section( int $user_id ): string {
$top_enrolled_html = $this->html_top_enrolled_courses( $user_id );
$top_selling_html = $this->html_top_selling_courses( $user_id );
return Template::combine_components(
[
'wrapper' => '',
'top_enrolled' => $top_enrolled_html,
'top_selling' => $top_selling_html,
'wrapper_end' => '
',
]
);
}
/**
* Render top enrolled courses with rich card layout.
*
* @param int $user_id
* @return string
*/
private function html_top_enrolled_courses( int $user_id ): string {
try {
$lp_statistic_db = LP_Statistics_DB::getInstance();
$top_courses = $lp_statistic_db->get_top_enrolled_courses_by_instructor( $user_id, 3 );
} catch ( Throwable $e ) {
$top_courses = [];
error_log( __METHOD__ . ': ' . $e->getMessage() );
}
$total_enrolled = 0;
$items_html = '';
if ( empty( $top_courses ) ) {
$items_html = '' . esc_html__( 'No enrollment data available', 'learnpress' ) . '
';
} else {
foreach ( $top_courses as $course ) {
$total_enrolled += intval( $course->enrollment_count );
$thumbnail = get_the_post_thumbnail( $course->course_id, 'thumbnail' );
if ( empty( $thumbnail ) ) {
$thumbnail = sprintf(
'%s
',
LP_WP_Filesystem::get_icon_svg( 'ico-cb-dashboard-course-placeholder.svg' )
);
}
$categories = wp_get_post_terms( $course->course_id, 'course_category', array( 'fields' => 'names' ) );
$category_text = '';
if ( ! empty( $categories ) && ! is_wp_error( $categories ) ) {
$category_text = sprintf( ' %s %s', esc_html__( 'in', 'learnpress' ), esc_html( implode( ', ', $categories ) ) );
}
$items_html .= sprintf(
'',
$thumbnail,
esc_url( get_permalink( $course->course_id ) ),
esc_html( $course->course_name ),
esc_html__( 'by', 'learnpress' ),
esc_html( $course->instructor_name ?? '' ),
$category_text,
sprintf(
/* translators: %s: number of students */
esc_html( _n( '%s student', '%s students', $course->enrollment_count, 'learnpress' ) ),
number_format_i18n( $course->enrollment_count )
)
);
}
}
return sprintf(
'',
esc_html__( 'Top Enrolled Courses', 'learnpress' ),
esc_html__( 'Total:', 'learnpress' ),
esc_html( number_format_i18n( $total_enrolled ) ),
esc_html__( 'enrolled students', 'learnpress' ),
$items_html
);
}
/**
* Render top selling courses with rich card layout.
*
* @param int $user_id
* @return string
*/
private function html_top_selling_courses( int $user_id ): string {
try {
$lp_statistic_db = LP_Statistics_DB::getInstance();
$top_courses = $lp_statistic_db->get_top_sold_courses_by_instructor( $user_id, 3 );
} catch ( Throwable $e ) {
$top_courses = [];
error_log( __METHOD__ . ': ' . $e->getMessage() );
}
$total_revenue = 0;
$items_html = '';
if ( empty( $top_courses ) ) {
$items_html = '' . esc_html__( 'No sales data available', 'learnpress' ) . '
';
} else {
foreach ( $top_courses as $course ) {
$total_revenue += floatval( $course->total_revenue ?? 0 );
$thumbnail = get_the_post_thumbnail( $course->course_id, 'thumbnail' );
if ( empty( $thumbnail ) ) {
$thumbnail = sprintf(
'%s
',
LP_WP_Filesystem::get_icon_svg( 'ico-cb-dashboard-course-placeholder.svg' )
);
}
$currency_symbol = function_exists( 'learn_press_get_currency_symbol' ) ? learn_press_get_currency_symbol() : '$';
$lp_course = function_exists( 'learn_press_get_course' ) ? learn_press_get_course( $course->course_id ) : false;
$price_html = '';
if ( $lp_course ) {
$price = $lp_course->get_price();
if ( $price > 0 ) {
$price_html = learn_press_format_price( $price, true );
} else {
$price_html = esc_html__( 'Free', 'learnpress' );
}
}
$items_html .= sprintf(
'',
$thumbnail,
esc_url( get_permalink( $course->course_id ) ),
esc_html( $course->course_name ),
esc_html__( 'Instructor:', 'learnpress' ),
esc_html( $course->instructor_name ?? '' ),
sprintf(
/* translators: %s: number of students */
esc_html( _n( '%s student', '%s students', $course->course_count, 'learnpress' ) ),
number_format_i18n( $course->course_count )
),
$price_html,
esc_html__( 'Revenue', 'learnpress' ),
esc_html( $currency_symbol ),
esc_html( number_format_i18n( $course->total_revenue ?? 0, 2 ) ),
esc_html( number_format_i18n( $course->course_count ) ),
esc_html__( 'sold', 'learnpress' )
);
}
}
$currency_symbol = function_exists( 'learn_press_get_currency_symbol' ) ? learn_press_get_currency_symbol() : '$';
return sprintf(
'',
esc_html__( 'Top Selling Courses', 'learnpress' ),
esc_html__( 'Total:', 'learnpress' ),
esc_html( $currency_symbol ),
esc_html( number_format_i18n( $total_revenue, 2 ) ),
esc_html__( 'revenue', 'learnpress' ),
$items_html
);
}
/**
* Render quick action buttons.
*
* @return string
*/
private function html_quick_actions(): string {
$create_lesson_template_id = 'lp-tmpl-builder-popup-lesson-dashboard-new';
$create_lesson_template = sprintf(
'',
esc_attr( $create_lesson_template_id ),
TemplateAJAX::load_content_via_ajax(
[
'id_url' => 'builder-popup-lesson-dashboard-new',
'lesson_id' => 0,
'html_no_load_ajax_first' => sprintf(
'',
esc_html__( 'Loading...', 'learnpress' )
),
],
[
'class' => BuilderPopupTemplate::class,
'method' => 'render_lesson_popup',
]
)
);
$actions = [
[
'label' => __( 'Create Course', 'learnpress' ),
'url' => CourseBuilder::get_link_add_new( 'courses' ),
'color' => '#ef4444',
'svg' => LP_WP_Filesystem::get_icon_svg( 'ico-courses-2.svg' ),
],
[
'label' => __( 'Create Lesson', 'learnpress' ),
'attr' => 'data-add-new-lesson',
'popup_type' => 'lesson',
'popup_id' => 0,
'template_id' => $create_lesson_template_id,
'template_html' => $create_lesson_template,
'color' => '#7067ED',
'icon' => 'lp-icon-file-text-o',
],
[
'label' => __( 'Create Quiz', 'learnpress' ),
'url' => CourseBuilder::get_link_add_new( 'quizzes' ),
'color' => '#f59e0b',
'icon' => 'lp-icon-puzzle-piece',
],
[
'label' => __( 'Create Question', 'learnpress' ),
'url' => CourseBuilder::get_link_add_new( 'questions', CourseBuilder::POST_NEW, 'overview' ),
'color' => '#6b7280',
'icon' => 'lp-icon-question-circle-o',
],
];
$actions = apply_filters( 'learn-press/course-builder/dashboard/quick-actions', $actions );
$buttons_html = '';
foreach ( $actions as $action ) {
if ( ! empty( $action['attr'] ) || ! empty( $action['template_id'] ) ) {
$button_attrs = [];
$template_html = $action['template_html'] ?? '';
if ( ! empty( $action['attr'] ) ) {
$button_attrs[] = $action['attr'];
}
if ( ! empty( $action['template_id'] ) ) {
$button_attrs[] = sprintf( 'data-template="#%s"', esc_attr( $action['template_id'] ) );
}
if ( ! empty( $action['popup_type'] ) ) {
$button_attrs[] = sprintf( 'data-popup-type="%s"', esc_attr( $action['popup_type'] ) );
}
if ( isset( $action['popup_id'] ) ) {
$button_attrs[] = sprintf( 'data-popup-id="%d"', absint( $action['popup_id'] ) );
}
$button_attrs = ! empty( $button_attrs ) ? ' ' . implode( ' ', $button_attrs ) : '';
// Render as button with data attribute (opens popup)
$buttons_html .= sprintf(
'%s',
$button_attrs,
esc_attr( $action['color'] ),
esc_attr( $action['color'] ),
esc_attr( $action['icon'] ?? '' ),
$action['svg'] ?? '',
esc_html( $action['label'] ),
$template_html
);
} else {
// Render as link
$buttons_html .= sprintf(
'
%s
%s
',
esc_url( $action['url'] ),
esc_attr( $action['color'] ),
esc_attr( $action['color'] ),
$action['icon'] ?? '',
$action['svg'] ?? '',
esc_html( $action['label'] )
);
}
}
return sprintf(
'',
esc_html__( 'Quick Action', 'learnpress' ),
$buttons_html
);
}
/**
* Render recent courses section using course items from Courses tab.
*
* @param UserModel $user
* @return string
*/
private function html_recent_courses_section( UserModel $user ): string {
$content = '';
try {
$filter = new LP_Course_Filter();
$filter->limit = 3;
$filter->order_by = 'post_date';
$filter->order = 'DESC';
$filter->post_status = [ 'publish', 'pending', 'draft', 'private' ];
if ( ! user_can( $user->get_id(), 'administrator' ) ) {
$filter->post_author = $user->get_id();
}
$total_courses = 0;
$courses = \LearnPress\Models\Courses::get_courses( $filter, $total_courses );
$list_html = '';
if ( ! empty( $courses ) ) {
$html_list_course = '';
foreach ( $courses as $course_obj ) {
$course = \LearnPress\Models\CourseModel::find( $course_obj->ID, true );
if ( $course ) {
// Reuse course item from Courses tab
$html_list_course .= BuilderListCoursesTemplate::render_course( $course );
}
}
if ( ! empty( $html_list_course ) ) {
$list_html = Template::combine_components(
[
'wrapper' => '',
'list_course' => $html_list_course,
'wrapper_end' => '
',
]
);
}
}
if ( empty( $list_html ) ) {
$list_html = '' . esc_html__( 'No recent courses found', 'learnpress' ) . '
';
}
$content = sprintf(
'',
esc_html__( 'Recent Courses', 'learnpress' ),
$list_html
);
} catch ( Throwable $e ) {
error_log( __METHOD__ . ': ' . $e->getMessage() );
}
return $content;
}
}