PluginProbe
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses / 4.4.4
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses v4.4.4
4.4.8 4.4.7 4.4.6 4.4.5 4.4.4 4.4.3 4.4.2 4.4.1 4.4.0 4.3.9.1 4.3.9 4.3.8 4.3.7 4.1.6.9 4.1.6.9.1 4.1.6.9.2 4.1.6.9.3 4.1.6.9.4 4.1.7 4.1.7.1 4.1.7.2 4.1.7.3 4.1.7.3.1 4.1.7.3.2 4.2.0 All 139 releases
learnpress / inc / TemplateHooks / CourseBuilder / Dashboard / BuilderDashboardTemplate.php

BuilderDashboardTemplate.php in LearnPress – WordPress LMS Plugin for Create and Sell Online Courses 4.4.4, at inc/TemplateHooks/CourseBuilder/Dashboard/BuilderDashboardTemplate.php

812 lines 24.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Template hooks Dashboard in Course Builder.
4 *
5 * @since 4.3.0
6 * @version 2.0.1
7 */
8
9 namespace LearnPress\TemplateHooks\CourseBuilder\Dashboard;
10
11 use LearnPress\TemplateHooks\CourseBuilder\BuilderPopupTemplate;
12 use LearnPress\CourseBuilder\CourseBuilder;
13 use LearnPress\Helpers\Singleton;
14 use LearnPress\Helpers\Template;
15 use LearnPress\Models\UserModel;
16 use LearnPress\TemplateHooks\CourseBuilder\Course\BuilderListCoursesTemplate;
17 use LearnPress\TemplateHooks\TemplateAJAX;
18 use LP_Course_Filter;
19 use LP_Statistics_DB;
20 use LP_WP_Filesystem;
21 use Throwable;
22
23 class BuilderDashboardTemplate {
24 use Singleton;
25
26 public function init() {
27 add_action( 'learn-press/course-builder/dashboard/layout', [ $this, 'layout' ] );
28 }
29
30 /**
31 * Render dashboard layout.
32 *
33 * @param array $data
34 *
35 * @return void
36 */
37 public function layout( array $data = [] ) {
38 $html = '';
39
40 try {
41 $user = $data['userModel'] ?? false;
42 if ( ! $user instanceof UserModel ) {
43 $user_id = get_current_user_id();
44 if ( ! $user_id ) {
45 return;
46 }
47
48 $user = UserModel::find( $user_id );
49 if ( ! $user ) {
50 return;
51 }
52 }
53
54 $html = $this->html_content( $user );
55 } catch ( Throwable $e ) {
56 error_log( __METHOD__ . ': ' . $e->getMessage() );
57 }
58
59 echo Template::combine_components(
60 [
61 'wrapper' => '<div class="lp-course-builder-dashboard">',
62 'header' => $this->html_header(),
63 'content' => $html,
64 'wrapper_end' => '</div>',
65 ]
66 );
67 }
68
69 private function html_header(): string {
70 return Template::combine_components(
71 [
72 'wrapper' => '<div class="cb-tab-header">',
73 'title' => sprintf( '<h2 class="lp-cb-tab__title">%s</h2>', __( 'Dashboard', 'learnpress' ) ),
74 'wrapper_end' => '</div>',
75 ]
76 );
77 }
78
79 /**
80 * Render dashboard content.
81 *
82 * @param UserModel $user
83 * @return string
84 */
85 private function html_content( UserModel $user ): string {
86 $is_admin = user_can( $user->get_id(), 'administrator' );
87
88 if ( $is_admin ) {
89 $statistic = $this->get_admin_statistic();
90 } else {
91 $statistic = $user->get_instructor_statistic();
92 }
93
94 $stats_html = $this->html_statistics_cards( $statistic, $is_admin );
95 $charts_html = $this->html_charts_section( $is_admin, $user->get_id() );
96 $top_instructors_html = $is_admin ? $this->html_top_instructors_section() : '';
97 $charts_row_class = $is_admin
98 ? 'lp-cb-dashboard__charts-row lp-cb-dashboard__charts-row--admin'
99 : 'lp-cb-dashboard__charts-row lp-cb-dashboard__charts-row--instructor';
100 $top_courses_html = $this->html_top_courses_section( $is_admin ? 0 : $user->get_id() );
101 $recent_courses_html = $this->html_recent_courses_section( $user );
102 $quick_actions_html = $this->html_quick_actions();
103
104 return Template::combine_components(
105 [
106 'stats' => $stats_html,
107 'charts_row' => sprintf(
108 '<div class="%s">%s%s</div>',
109 esc_attr( $charts_row_class ),
110 $charts_html,
111 $top_instructors_html
112 ),
113 'top_courses' => $top_courses_html,
114 'quick_actions' => $quick_actions_html,
115 'recent_courses' => $recent_courses_html,
116 ]
117 );
118 }
119
120 /**
121 * Get global statistics for admin.
122 *
123 * @return array
124 */
125 private function get_admin_statistic(): array {
126 $statistic = array(
127 'total_course' => 0,
128 'published_course' => 0,
129 'pending_course' => 0,
130 'total_student' => 0,
131 'total_instructor' => 0,
132 );
133
134 try {
135 global $wpdb;
136
137 // Course counts via wp_count_posts
138 $course_counts = wp_count_posts( LP_COURSE_CPT );
139
140 $statistic['total_course'] = intval( $course_counts->publish ?? 0 )
141 + intval( $course_counts->pending ?? 0 )
142 + intval( $course_counts->draft ?? 0 )
143 + intval( $course_counts->private ?? 0 );
144 $statistic['published_course'] = intval( $course_counts->publish ?? 0 );
145 $statistic['pending_course'] = intval( $course_counts->pending ?? 0 );
146
147 // Total unique students enrolled in any course
148 $tb_user_items = $wpdb->prefix . 'learnpress_user_items';
149 $total_students = $wpdb->get_var(
150 $wpdb->prepare(
151 "SELECT COUNT(DISTINCT user_id) FROM {$tb_user_items} WHERE item_type = %s",
152 LP_COURSE_CPT
153 )
154 );
155 $statistic['total_student'] = intval( $total_students );
156
157 // Total instructors
158 $statistic['total_instructor'] = $this->count_total_instructors();
159 } catch ( Throwable $e ) {
160 error_log( __METHOD__ . ': ' . $e->getMessage() );
161 }
162
163 return $statistic;
164 }
165
166 /**
167 * Count total instructors (admin + lp_teacher).
168 *
169 * @return int
170 */
171 private function count_total_instructors(): int {
172 global $wpdb;
173
174 $result = $wpdb->get_var(
175 $wpdb->prepare(
176 "SELECT COUNT(DISTINCT u.ID)
177 FROM {$wpdb->users} AS u
178 INNER JOIN {$wpdb->usermeta} AS um ON um.user_id = u.ID
179 WHERE um.meta_key = %s
180 AND (um.meta_value LIKE %s OR um.meta_value LIKE %s)",
181 $wpdb->prefix . 'capabilities',
182 '%administrator%',
183 '%' . LP_TEACHER_ROLE . '%'
184 )
185 );
186
187 return intval( $result );
188 }
189
190 /**
191 * Render statistics cards.
192 *
193 * @param array $statistic
194 * @param bool $is_admin
195 * @return string
196 */
197 private function html_statistics_cards( array $statistic, bool $is_admin ): string {
198 $cards = [
199 [
200 'key' => 'total_course',
201 'label' => __( 'Total Courses', 'learnpress' ),
202 'color' => '#ef4444',
203 'icon' => 'ico-cb-total-courses.svg',
204 ],
205 [
206 'key' => 'published_course',
207 'label' => __( 'Published Courses', 'learnpress' ),
208 'color' => '#2E91FA',
209 'icon' => 'ico-cb-published-courses.svg',
210 ],
211 [
212 'key' => 'pending_course',
213 'label' => __( 'Pending Courses', 'learnpress' ),
214 'color' => '#F8A100',
215 'icon' => 'ico-cb-pending-courses.svg',
216 ],
217 [
218 'key' => 'total_student',
219 'label' => __( 'Total Students', 'learnpress' ),
220 'color' => '#28A746',
221 'icon' => 'ico-cb-total-students.svg',
222 ],
223 ];
224
225 // Role-based summary card
226 if ( $is_admin ) {
227 $cards[] = [
228 'key' => 'total_instructor',
229 'label' => __( 'Total Instructors', 'learnpress' ),
230 'color' => '#06AED4',
231 'icon' => 'ico-cb-total-instructors.svg',
232 ];
233 } else {
234 $cards[] = [
235 'key' => 'student_in_progress',
236 'label' => __( 'Total In-progress Students', 'learnpress' ),
237 'color' => '#06AED4',
238 'icon' => 'ico-cb-students-progress.svg',
239 ];
240 }
241
242 $cards_html = '';
243 foreach ( $cards as $card ) {
244 $value = $statistic[ $card['key'] ] ?? 0;
245 $cards_html .= sprintf(
246 '<div class="lp-cb-dashboard__stat-card" style="--card-color: %s; --card-bg: %s15">
247 <div class="stat-card__icon">%s</div>
248 <span class="stat-card__label">%s</span>
249 <span class="stat-card__value">%s</span>
250 </div>',
251 esc_attr( $card['color'] ),
252 esc_attr( $card['color'] ),
253 LP_WP_Filesystem::get_icon_svg( $card['icon'] ?? '' ),
254 esc_html( $card['label'] ),
255 esc_html( number_format_i18n( $value ) )
256 );
257 }
258
259 return Template::combine_components(
260 [
261 'wrapper' => '<div class="lp-cb-dashboard__stats">',
262 'content' => $cards_html,
263 'wrapper_end' => '</div>',
264 ]
265 );
266 }
267
268 /**
269 * Render charts section with Net Sales and Students charts.
270 *
271 * @param bool $is_admin
272 * @param int $user_id
273 * @return string
274 */
275 private function html_charts_section( bool $is_admin, int $user_id ): string {
276 $instructor_id = $is_admin ? 0 : $user_id;
277 $nonce = wp_create_nonce( 'lp_cb_dashboard_nonce' );
278
279 // Get initial data - use 'year' for sales and 'previous_days' for students so data appears immediately
280 try {
281 $lp_statistic_db = LP_Statistics_DB::getInstance();
282 $current_date = current_time( 'Y-m-d' );
283
284 $sales_data = $lp_statistic_db->get_net_sales_data_scoped( 'year', $current_date, $instructor_id );
285 $students_data = $lp_statistic_db->get_enrollment_chart_data( 'previous_days', 6, $instructor_id );
286
287 // Process chart data using the REST controller's logic
288 require_once LP_PLUGIN_PATH . 'inc/rest-api/v1/admin/class-lp-admin-rest-statistics-controller.php';
289 $stats_ctrl = new \LP_REST_Admin_Statistics_Controller();
290
291 $sales_chart = $stats_ctrl->process_chart_data(
292 [
293 'filter_type' => 'year',
294 'time' => $current_date,
295 ],
296 $sales_data
297 );
298 $students_chart = $stats_ctrl->process_chart_data(
299 [
300 'filter_type' => 'previous_days',
301 'time' => 6,
302 ],
303 $students_data
304 );
305 } catch ( Throwable $e ) {
306 $sales_chart = [
307 'labels' => [],
308 'data' => [],
309 ];
310 $students_chart = [
311 'labels' => [],
312 'data' => [],
313 ];
314 error_log( __METHOD__ . ': ' . $e->getMessage() );
315 }
316
317 $filter_options = '
318 <option value="this_month">' . esc_html__( 'This month', 'learnpress' ) . '</option>
319 <option value="this_week">' . esc_html__( 'This week', 'learnpress' ) . '</option>
320 <option value="this_year" selected>' . esc_html__( 'This year', 'learnpress' ) . '</option>';
321
322 $html = sprintf(
323 '<div class="lp-cb-dashboard__chart-card">
324 <div class="chart-card__header">
325 <h3 class="chart-card__title">%s</h3>
326 <select class="chart-card__filter" data-chart="sales" data-nonce="%s">%s</select>
327 </div>
328 <div class="chart-card__body">
329 <canvas id="lp-cb-chart-sales"></canvas>
330 </div>
331 </div>
332 <div class="lp-cb-dashboard__chart-card">
333 <div class="chart-card__header">
334 <h3 class="chart-card__title">%s</h3>
335 <select class="chart-card__filter" data-chart="students" data-nonce="%s">
336 <option value="this_week" selected>%s</option>
337 <option value="this_month">%s</option>
338 <option value="this_year">%s</option>
339 </select>
340 </div>
341 <div class="chart-card__body">
342 <canvas id="lp-cb-chart-students"></canvas>
343 </div>
344 </div>
345 <script id="lp-cb-dashboard-chart-data" type="application/json">%s</script>',
346 esc_html__( 'Net sales', 'learnpress' ),
347 esc_attr( $nonce ),
348 $filter_options,
349 esc_html__( 'Students', 'learnpress' ),
350 esc_attr( $nonce ),
351 esc_html__( 'This week', 'learnpress' ),
352 esc_html__( 'This month', 'learnpress' ),
353 esc_html__( 'This year', 'learnpress' ),
354 wp_json_encode(
355 [
356 'sales' => [
357 'labels' => $sales_chart['labels'] ?? [],
358 'data' => $sales_chart['data'] ?? [],
359 ],
360 'students' => [
361 'labels' => $students_chart['labels'] ?? [],
362 'data' => $students_chart['data'] ?? [],
363 ],
364 'ajaxUrl' => admin_url( 'admin-ajax.php' ),
365 'nonce' => $nonce,
366 ]
367 )
368 );
369
370 return $html;
371 }
372
373 /**
374 * Render top instructors section (admin only).
375 *
376 * @return string
377 */
378 private function html_top_instructors_section(): string {
379 try {
380 $lp_statistic_db = LP_Statistics_DB::getInstance();
381 $instructors = $lp_statistic_db->get_top_instructors( 4 );
382 } catch ( Throwable $e ) {
383 $instructors = [];
384 error_log( __METHOD__ . ': ' . $e->getMessage() );
385 }
386
387 $items_html = '';
388 if ( empty( $instructors ) ) {
389 $items_html = '<div class="no-data">' . esc_html__( 'No instructors found', 'learnpress' ) . '</div>';
390 } else {
391 foreach ( $instructors as $instructor ) {
392 $avatar = get_avatar( $instructor->instructor_id, 40 );
393 $items_html .= sprintf(
394 '<div class="instructor-item">
395 <div class="instructor-item__avatar">%s</div>
396 <div class="instructor-item__info">
397 <span class="instructor-item__name">%s</span>
398 <span class="instructor-item__meta">%s &middot; %s</span>
399 </div>
400 </div>',
401 $avatar,
402 esc_html( $instructor->instructor_name ),
403 sprintf(
404 /* translators: %s: number of courses */
405 esc_html( _n( '%s course', '%s courses', $instructor->course_count, 'learnpress' ) ),
406 number_format_i18n( $instructor->course_count )
407 ),
408 sprintf(
409 /* translators: %s: number of students */
410 esc_html( _n( '%s student', '%s students', $instructor->student_count, 'learnpress' ) ),
411 number_format_i18n( $instructor->student_count )
412 )
413 );
414 }
415 }
416
417 return sprintf(
418 '<div class="lp-cb-dashboard__top-instructors">
419 <div class="top-instructors__header">
420 <h3 class="top-instructors__title">
421 %s
422 </h3>
423 </div>
424 <div class="top-instructors__list">%s</div>
425 </div>',
426 esc_html__( 'Top Instructors', 'learnpress' ),
427 $items_html
428 );
429 }
430
431 /**
432 * Render top courses section.
433 *
434 * @param int $user_id
435 * @return string
436 */
437 private function html_top_courses_section( int $user_id ): string {
438 $top_enrolled_html = $this->html_top_enrolled_courses( $user_id );
439 $top_selling_html = $this->html_top_selling_courses( $user_id );
440
441 return Template::combine_components(
442 [
443 'wrapper' => '<div class="lp-cb-dashboard__top-courses-wrapper">',
444 'top_enrolled' => $top_enrolled_html,
445 'top_selling' => $top_selling_html,
446 'wrapper_end' => '</div>',
447 ]
448 );
449 }
450
451 /**
452 * Render top enrolled courses with rich card layout.
453 *
454 * @param int $user_id
455 * @return string
456 */
457 private function html_top_enrolled_courses( int $user_id ): string {
458 try {
459 $lp_statistic_db = LP_Statistics_DB::getInstance();
460 $top_courses = $lp_statistic_db->get_top_enrolled_courses_by_instructor( $user_id, 3 );
461 } catch ( Throwable $e ) {
462 $top_courses = [];
463 error_log( __METHOD__ . ': ' . $e->getMessage() );
464 }
465
466 $total_enrolled = 0;
467 $items_html = '';
468
469 if ( empty( $top_courses ) ) {
470 $items_html = '<div class="no-data">' . esc_html__( 'No enrollment data available', 'learnpress' ) . '</div>';
471 } else {
472 foreach ( $top_courses as $course ) {
473 $total_enrolled += intval( $course->enrollment_count );
474 $thumbnail = get_the_post_thumbnail( $course->course_id, 'thumbnail' );
475 if ( empty( $thumbnail ) ) {
476 $thumbnail = sprintf(
477 '<div class="course-item__thumb-placeholder">%s</div>',
478 LP_WP_Filesystem::get_icon_svg( 'ico-cb-dashboard-course-placeholder.svg' )
479 );
480 }
481
482 $categories = wp_get_post_terms( $course->course_id, 'course_category', array( 'fields' => 'names' ) );
483 $category_text = '';
484 if ( ! empty( $categories ) && ! is_wp_error( $categories ) ) {
485 $category_text = sprintf( ' %s <span class="category">%s</span>', esc_html__( 'in', 'learnpress' ), esc_html( implode( ', ', $categories ) ) );
486 }
487
488 $items_html .= sprintf(
489 '<div class="course-item">
490 <div class="course-item__thumb">%s</div>
491 <div class="course-item__info">
492 <a href="%s" class="course-item__title">%s</a>
493 <span class="course-item__meta">%s <span class="author">%s</span>%s</span>
494 </div>
495 <div class="course-item__badge-wrapper">
496 <span class="course-item__badge">
497 %s
498 </span>
499 </div>
500 </div>',
501 $thumbnail,
502 esc_url( get_permalink( $course->course_id ) ),
503 esc_html( $course->course_name ),
504 esc_html__( 'by', 'learnpress' ),
505 esc_html( $course->instructor_name ?? '' ),
506 $category_text,
507 sprintf(
508 /* translators: %s: number of students */
509 esc_html( _n( '%s student', '%s students', $course->enrollment_count, 'learnpress' ) ),
510 number_format_i18n( $course->enrollment_count )
511 )
512 );
513 }
514 }
515
516 return sprintf(
517 '<div class="lp-cb-dashboard__top-courses lp-cb-dashboard__top-enrolled">
518 <div class="top-courses__header">
519 <h3 class="top-courses__title">%s</h3>
520 <span class="top-courses__total">%s <strong class="enrolled-students-total">%s</strong> %s</span>
521 </div>
522 <div class="top-courses__list">%s</div>
523 </div>',
524 esc_html__( 'Top Enrolled Courses', 'learnpress' ),
525 esc_html__( 'Total:', 'learnpress' ),
526 esc_html( number_format_i18n( $total_enrolled ) ),
527 esc_html__( 'students', 'learnpress' ),
528 $items_html
529 );
530 }
531
532 /**
533 * Render top selling courses with rich card layout.
534 *
535 * @param int $user_id
536 * @return string
537 */
538 private function html_top_selling_courses( int $user_id ): string {
539 try {
540 $lp_statistic_db = LP_Statistics_DB::getInstance();
541 $top_courses = $lp_statistic_db->get_top_sold_courses_by_instructor( $user_id, 3 );
542 } catch ( Throwable $e ) {
543 $top_courses = [];
544 error_log( __METHOD__ . ': ' . $e->getMessage() );
545 }
546
547 $total_revenue = 0;
548 $items_html = '';
549
550 if ( empty( $top_courses ) ) {
551 $items_html = '<div class="no-data">' . esc_html__( 'No sales data available', 'learnpress' ) . '</div>';
552 } else {
553 foreach ( $top_courses as $course ) {
554 $total_revenue += floatval( $course->total_revenue ?? 0 );
555 $thumbnail = get_the_post_thumbnail( $course->course_id, 'thumbnail' );
556 if ( empty( $thumbnail ) ) {
557 $thumbnail = sprintf(
558 '<div class="course-item__thumb-placeholder">%s</div>',
559 LP_WP_Filesystem::get_icon_svg( 'ico-cb-dashboard-course-placeholder.svg' )
560 );
561 }
562
563 $currency_symbol = function_exists( 'learn_press_get_currency_symbol' ) ? learn_press_get_currency_symbol() : '$';
564
565 $lp_course = function_exists( 'learn_press_get_course' ) ? learn_press_get_course( $course->course_id ) : false;
566 $price_html = '';
567 if ( $lp_course ) {
568 $price = $lp_course->get_price();
569 if ( $price > 0 ) {
570 $price_html = learn_press_format_price( $price, true );
571 } else {
572 $price_html = esc_html__( 'Free', 'learnpress' );
573 }
574 }
575
576 $items_html .= sprintf(
577 '<div class="course-item">
578 <div class="course-item__thumb">%s</div>
579 <div class="course-item__info">
580 <a href="%s" class="course-item__title">%s</a>
581 <span class="course-item__meta">%s <span class="author">%s</span> &bull; %s</span>
582 <span class="course-item__price">%s</span>
583 </div>
584 <div class="course-item__stats">
585 <div class="course-item__revenue">%s: <strong class="revenue-amount">%s%s</strong></div>
586 <div class="course-item__sold">%s %s</div>
587 </div>
588 </div>',
589 $thumbnail,
590 esc_url( get_permalink( $course->course_id ) ),
591 esc_html( $course->course_name ),
592 esc_html__( 'Instructor:', 'learnpress' ),
593 esc_html( $course->instructor_name ?? '' ),
594 sprintf(
595 /* translators: %s: number of students */
596 esc_html( _n( '%s student', '%s students', $course->course_count, 'learnpress' ) ),
597 number_format_i18n( $course->course_count )
598 ),
599 $price_html,
600 esc_html__( 'Revenue', 'learnpress' ),
601 esc_html( $currency_symbol ),
602 esc_html( number_format_i18n( $course->total_revenue ?? 0, 2 ) ),
603 esc_html( number_format_i18n( $course->course_count ) ),
604 esc_html__( 'sold', 'learnpress' )
605 );
606 }
607 }
608
609 $currency_symbol = function_exists( 'learn_press_get_currency_symbol' ) ? learn_press_get_currency_symbol() : '$';
610
611 return sprintf(
612 '<div class="lp-cb-dashboard__top-courses lp-cb-dashboard__top-selling">
613 <div class="top-courses__header">
614 <h3 class="top-courses__title">%s</h3>
615 <span class="top-courses__total">%s <strong class="revenue-total">%s%s</strong> %s</span>
616 </div>
617 <div class="top-courses__list">%s</div>
618 </div>',
619 esc_html__( 'Top Selling Courses', 'learnpress' ),
620 esc_html__( 'Total:', 'learnpress' ),
621 esc_html( $currency_symbol ),
622 esc_html( number_format_i18n( $total_revenue, 2 ) ),
623 esc_html__( 'revenue', 'learnpress' ),
624 $items_html
625 );
626 }
627
628 /**
629 * Render quick action buttons.
630 *
631 * @return string
632 */
633 private function html_quick_actions(): string {
634 $create_lesson_template_id = 'lp-tmpl-builder-popup-lesson-dashboard-new';
635 $create_lesson_template = sprintf(
636 '<script type="text/template" id="%1$s"><div class="lp-builder-popup-overlay"></div><div class="lp-builder-popup lp-builder-popup--loading">%2$s</div></script>',
637 esc_attr( $create_lesson_template_id ),
638 TemplateAJAX::load_content_via_ajax(
639 [
640 'id_url' => 'builder-popup-lesson-dashboard-new',
641 'lesson_id' => 0,
642 'html_no_load_ajax_first' => sprintf(
643 '<div class="lp-builder-popup__loader"><div class="lp-loading-circle"></div><span>%s</span></div>',
644 esc_html__( 'Loading...', 'learnpress' )
645 ),
646 ],
647 [
648 'class' => BuilderPopupTemplate::class,
649 'method' => 'render_lesson_popup',
650 ]
651 )
652 );
653
654 $actions = [
655 [
656 'label' => __( 'Create Course', 'learnpress' ),
657 'url' => CourseBuilder::get_link_add_new( 'courses' ),
658 'color' => '#ef4444',
659 'svg' => LP_WP_Filesystem::get_icon_svg( 'ico-courses-2.svg' ),
660 ],
661 [
662 'label' => __( 'Create Lesson', 'learnpress' ),
663 'attr' => 'data-add-new-lesson',
664 'popup_type' => 'lesson',
665 'popup_id' => 0,
666 'template_id' => $create_lesson_template_id,
667 'template_html' => $create_lesson_template,
668 'color' => '#7067ED',
669 'icon' => 'lp-icon-file-text-o',
670 ],
671 [
672 'label' => __( 'Create Quiz', 'learnpress' ),
673 'url' => CourseBuilder::get_link_add_new( 'quizzes' ),
674 'color' => '#f59e0b',
675 'icon' => 'lp-icon-puzzle-piece',
676 ],
677 [
678 'label' => __( 'Create Question', 'learnpress' ),
679 'url' => CourseBuilder::get_link_add_new( 'questions', CourseBuilder::POST_NEW, 'overview' ),
680 'color' => '#6b7280',
681 'icon' => 'lp-icon-question-circle-o',
682 ],
683 ];
684 $actions = apply_filters( 'learn-press/course-builder/dashboard/quick-actions', $actions );
685
686 $buttons_html = '';
687 foreach ( $actions as $action ) {
688 if ( ! empty( $action['attr'] ) || ! empty( $action['template_id'] ) ) {
689 $button_attrs = [];
690 $template_html = $action['template_html'] ?? '';
691 if ( ! empty( $action['attr'] ) ) {
692 $button_attrs[] = $action['attr'];
693 }
694 if ( ! empty( $action['template_id'] ) ) {
695 $button_attrs[] = sprintf( 'data-template="#%s"', esc_attr( $action['template_id'] ) );
696 }
697 if ( ! empty( $action['popup_type'] ) ) {
698 $button_attrs[] = sprintf( 'data-popup-type="%s"', esc_attr( $action['popup_type'] ) );
699 }
700 if ( isset( $action['popup_id'] ) ) {
701 $button_attrs[] = sprintf( 'data-popup-id="%d"', absint( $action['popup_id'] ) );
702 }
703 $button_attrs = ! empty( $button_attrs ) ? ' ' . implode( ' ', $button_attrs ) : '';
704
705 // Render as button with data attribute (opens popup)
706 $buttons_html .= sprintf(
707 '<button type="button"%s class="quick-action__btn" style="--action-color: %s; --action-bg: %s10">
708 <span class="quick-action__icon %s">%s</span>
709 <span class="quick-action__label">%s</span>
710 </button>%s',
711 $button_attrs,
712 esc_attr( $action['color'] ),
713 esc_attr( $action['color'] ),
714 esc_attr( $action['icon'] ?? '' ),
715 $action['svg'] ?? '',
716 esc_html( $action['label'] ),
717 $template_html
718 );
719 } else {
720 // Render as link
721 $buttons_html .= sprintf(
722 '<a href="%s" class="quick-action__btn" style="--action-color: %s; --action-bg: %s10">
723 <span class="quick-action__icon %s">%s</span>
724 <span class="quick-action__label">%s</span>
725 </a>',
726 esc_url( $action['url'] ),
727 esc_attr( $action['color'] ),
728 esc_attr( $action['color'] ),
729 $action['icon'] ?? '',
730 $action['svg'] ?? '',
731 esc_html( $action['label'] )
732 );
733 }
734 }
735
736 return sprintf(
737 '<div class="lp-cb-dashboard__quick-actions">
738 <h3 class="quick-actions__title">%s</h3>
739 <div class="quick-actions__grid">%s</div>
740 </div>',
741 esc_html__( 'Quick Action', 'learnpress' ),
742 $buttons_html
743 );
744 }
745
746 /**
747 * Render recent courses section using course items from Courses tab.
748 *
749 * @param UserModel $user
750 * @return string
751 */
752 private function html_recent_courses_section( UserModel $user ): string {
753 $content = '';
754
755 try {
756 $filter = new LP_Course_Filter();
757 $filter->limit = 3;
758 $filter->order_by = 'post_date';
759 $filter->order = 'DESC';
760 $filter->post_status = [ 'publish', 'pending', 'draft', 'private' ];
761
762 if ( ! user_can( $user->get_id(), 'administrator' ) ) {
763 $filter->post_author = $user->get_id();
764 }
765
766 $total_courses = 0;
767 $courses = \LearnPress\Models\Courses::get_courses( $filter, $total_courses );
768
769 $list_html = '';
770 if ( ! empty( $courses ) ) {
771 $html_list_course = '';
772 foreach ( $courses as $course_obj ) {
773 $course = \LearnPress\Models\CourseModel::find( $course_obj->ID, true );
774 if ( $course ) {
775 // Reuse course item from Courses tab
776 $html_list_course .= BuilderListCoursesTemplate::render_course( $course );
777 }
778 }
779
780 if ( ! empty( $html_list_course ) ) {
781 $list_html = Template::combine_components(
782 [
783 'wrapper' => '<div class="courses-builder__course-tab learn-press-courses"><ul class="cb-list-course">',
784 'list_course' => $html_list_course,
785 'wrapper_end' => '</ul></div>',
786 ]
787 );
788 }
789 }
790
791 if ( empty( $list_html ) ) {
792 $list_html = '<div class="no-data">' . esc_html__( 'No recent courses found', 'learnpress' ) . '</div>';
793 }
794
795 $content = sprintf(
796 '<div class="lp-cb-dashboard__recent-courses" style="margin-top: 30px;">
797 <div class="recent-courses__header">
798 <h3 class="recent-courses__title">%s</h3>
799 </div>
800 <div class="recent-courses__list">%s</div>
801 </div>',
802 esc_html__( 'Recent Courses', 'learnpress' ),
803 $list_html
804 );
805 } catch ( Throwable $e ) {
806 error_log( __METHOD__ . ': ' . $e->getMessage() );
807 }
808
809 return $content;
810 }
811 }
812