← All changes
|
inc/TemplateHooks/Admin/AdminListStudentsEnrolled.php
+714
-749
4.3.9
→
4.4.9
View file →
| @@ -1,749 +1,714 @@ | ||
| 1 | -<?php | |
| 2 | - | |
| 3 | -namespace LearnPress\TemplateHooks\Admin; | |
| 4 | - | |
| 5 | -use Exception; | |
| 6 | -use LearnPress\Databases\PostDB; | |
| 7 | -use LearnPress\Databases\UserItemsDB; | |
| 8 | -use LearnPress\Filters\PostFilter; | |
| 9 | -use LearnPress\Filters\UserItemsFilter; | |
| 10 | -use LearnPress\Helpers\Singleton; | |
| 11 | -use LearnPress\Helpers\Template; | |
| 12 | -use LearnPress\Models\CourseModel; | |
| 13 | -use LearnPress\Models\UserItems\UserCourseModel; | |
| 14 | -use LearnPress\Models\UserItems\UserItemModel; | |
| 15 | -use LearnPress\Models\UserModel; | |
| 16 | -use LearnPress\TemplateHooks\Instructor\SingleInstructorTemplate; | |
| 17 | -use LearnPress\TemplateHooks\Table\TableListTemplate; | |
| 18 | -use LearnPress\TemplateHooks\TemplateAJAX; | |
| 19 | -use LearnPress\TemplateHooks\UserItem\UserCourseTemplate; | |
| 20 | -use LP_Debug; | |
| 21 | -use LP_Helper; | |
| 22 | -use LP_Page_Controller; | |
| 23 | -use stdClass; | |
| 24 | -use Throwable; | |
| 25 | - | |
| 26 | -/** | |
| 27 | - * Template Admin List Students Enrolled. | |
| 28 | - * | |
| 29 | - * Displays enrolled students for Admin (all courses) and Instructor (own courses only). | |
| 30 | - * Provides WP Admin submenu + Frontend profile tab. | |
| 31 | - * | |
| 32 | - * @since 4.3.3 | |
| 33 | - * @version 1.0.0 | |
| 34 | - */ | |
| 35 | -class AdminListStudentsEnrolled { | |
| 36 | - use Singleton; | |
| 37 | - | |
| 38 | - const PER_PAGE = 10; | |
| 39 | - | |
| 40 | - public function init() { | |
| 41 | - | |
| 42 | - // 1. Register render hook for both admin + frontend profile. | |
| 43 | - add_action( 'learn-press/admin/enrolled-students/layout', array( $this, 'enrolled_students_layout' ) ); | |
| 44 | - // 2. Whitelist AJAX callback. | |
| 45 | - add_filter( 'lp/rest/ajax/allow_callback', array( $this, 'allow_callback' ) ); | |
| 46 | - // 4. Render a modal toolbar template from PHP (used by JS modal). | |
| 47 | - add_action( 'admin_footer', array( $this, 'print_modal_toolbar_template' ) ); | |
| 48 | - add_action( 'wp_footer', array( $this, 'print_modal_toolbar_template' ) ); | |
| 49 | - } | |
| 50 | - | |
| 51 | - /** | |
| 52 | - * Admin page output callback. | |
| 53 | - */ | |
| 54 | - public function admin_page_output() { | |
| 55 | - $instructor_id = self::resolve_instructor_id_for_request( array() ); | |
| 56 | - | |
| 57 | - echo '<div class="wrap" id="lp-enrolled-students">'; | |
| 58 | - echo '<h1 class="wp-heading-inline">' . esc_html__( 'Enrolled Students', 'learnpress' ) . '</h1>'; | |
| 59 | - do_action( 'learn-press/admin/enrolled-students/layout', $instructor_id ); | |
| 60 | - echo '</div>'; | |
| 61 | - } | |
| 62 | - | |
| 63 | - /** | |
| 64 | - * Allow callback for AJAX. | |
| 65 | - * | |
| 66 | - * @param array $callbacks | |
| 67 | - * | |
| 68 | - * @return array | |
| 69 | - */ | |
| 70 | - public function allow_callback( array $callbacks ): array { | |
| 71 | - $callbacks[] = get_class( $this ) . ':render_enrolled_students'; | |
| 72 | - | |
| 73 | - return $callbacks; | |
| 74 | - } | |
| 75 | - | |
| 76 | - /** | |
| 77 | - * Render initial layout with TemplateAJAX::load_content_via_ajax(). | |
| 78 | - * | |
| 79 | - * @param int $instructor_id 0 for Admin (all), user ID for Instructor. | |
| 80 | - */ | |
| 81 | - public function enrolled_students_layout( $instructor_id = 0 ) { | |
| 82 | - try { | |
| 83 | - $page_current = ''; | |
| 84 | - if ( function_exists( 'get_current_screen' ) ) { | |
| 85 | - $wp_screen = get_current_screen(); | |
| 86 | - if ( $wp_screen ) { | |
| 87 | - // Page on the Admin screen. | |
| 88 | - if ( $wp_screen->id === 'learnpress_page_learn-press-students-enrolled' ) { | |
| 89 | - $page_current = 'learnpress_page_learn-press-students-enrolled'; | |
| 90 | - } | |
| 91 | - } | |
| 92 | - } | |
| 93 | - | |
| 94 | - // Enqueue styles — lp-enrolled-students-table CSS is loaded via admin.css/frontend.css import. | |
| 95 | - // Build toolbar HTML (outside AJAX so it persists across reloads). | |
| 96 | - $instructor_id = self::resolve_instructor_id_for_request( | |
| 97 | - array( | |
| 98 | - 'instructor_id' => $instructor_id, | |
| 99 | - ) | |
| 100 | - ); | |
| 101 | - | |
| 102 | - $args = array( | |
| 103 | - 'id_url' => 'lp-enrolled-students', | |
| 104 | - 'instructor_id' => (int) $instructor_id, | |
| 105 | - 'course_id' => abs( LP_Helper::sanitize_params_submitted( $_GET['course_id'] ?? 0, 'int' ) ), | |
| 106 | - 'course_name' => LP_Helper::sanitize_params_submitted( $_GET['course_name'] ?? '' ), | |
| 107 | - 'paged' => 1, | |
| 108 | - 'search' => LP_Helper::sanitize_params_submitted( $_GET['search'] ?? '' ), | |
| 109 | - 'start_date' => self::sanitize_date_filter( $_GET['start_date'] ?? '' ), | |
| 110 | - 'end_date' => self::sanitize_date_filter( $_GET['end_date'] ?? '' ), | |
| 111 | - 'enableUpdateParamsUrl' => false, | |
| 112 | - ); | |
| 113 | - | |
| 114 | - $call_back = array( | |
| 115 | - 'class' => self::class, | |
| 116 | - 'method' => 'render_enrolled_students', | |
| 117 | - ); | |
| 118 | - | |
| 119 | - $section = [ | |
| 120 | - 'wrap' => sprintf( | |
| 121 | - '<div class="lp-students-enrolled-layout %s">', | |
| 122 | - $page_current | |
| 123 | - ), | |
| 124 | - 'toolbar' => $this->html_toolbar(), | |
| 125 | - 'table' => TemplateAJAX::load_content_via_ajax( $args, $call_back ), | |
| 126 | - 'wrap-end' => '</div>', | |
| 127 | - ]; | |
| 128 | - | |
| 129 | - echo Template::combine_components( $section ); | |
| 130 | - } catch ( Throwable $e ) { | |
| 131 | - LP_Debug::error_log( $e ); | |
| 132 | - Template::print_message( $e->getMessage(), 'error' ); | |
| 133 | - } | |
| 134 | - } | |
| 135 | - | |
| 136 | - /** | |
| 137 | - * Static render method called by AJAX — returns stdClass{ content }. | |
| 138 | - * | |
| 139 | - * Permission check: Admin=full, Instructor=own courses only. | |
| 140 | - * | |
| 141 | - * @param array $data | |
| 142 | - * | |
| 143 | - * @return stdClass | |
| 144 | - */ | |
| 145 | - public static function render_enrolled_students( array $data ): stdClass { | |
| 146 | - $content = new stdClass(); | |
| 147 | - $content->content = ''; | |
| 148 | - | |
| 149 | - try { | |
| 150 | - // Check permission | |
| 151 | - if ( ! current_user_can( UserModel::ROLE_ADMINISTRATOR ) | |
| 152 | - && ! current_user_can( UserModel::ROLE_INSTRUCTOR ) ) { | |
| 153 | - throw new Exception( esc_html__( 'You do not have permission to view enrolled students.', 'learnpress' ) ); | |
| 154 | - } | |
| 155 | - | |
| 156 | - $instructor_id = self::resolve_instructor_id_for_request( $data ); | |
| 157 | - | |
| 158 | - $course_id = abs( LP_Helper::sanitize_params_submitted( $data['course_id'] ?? 0, 'int' ) ); | |
| 159 | - $paged = max( 1, abs( LP_Helper::sanitize_params_submitted( $data['paged'] ?? 1, 'int' ) ) ); | |
| 160 | - $course_name = LP_Helper::sanitize_params_submitted( $data['course_name'] ?? '' ); | |
| 161 | - $search = LP_Helper::sanitize_params_submitted( $data['search'] ?? '' ); | |
| 162 | - $start_date = self::sanitize_date_filter( $data['start_date'] ?? '' ); | |
| 163 | - $end_date = self::sanitize_date_filter( $data['end_date'] ?? '' ); | |
| 164 | - $per_page = self::PER_PAGE; | |
| 165 | - | |
| 166 | - // Normalize date range if request is reversed. | |
| 167 | - if ( $start_date && $end_date && strtotime( $start_date ) > strtotime( $end_date ) ) { | |
| 168 | - $tmp = $start_date; | |
| 169 | - $start_date = $end_date; | |
| 170 | - $end_date = $tmp; | |
| 171 | - } | |
| 172 | - | |
| 173 | - $lp_db_user_items = UserItemsDB::getInstance(); | |
| 174 | - $filter = new UserItemsFilter(); | |
| 175 | - $filter->item_type = LP_COURSE_CPT; | |
| 176 | - $filter->statues = array( 'enrolled', 'finished' ); | |
| 177 | - $filter->limit = $per_page; | |
| 178 | - $filter->page = $paged; | |
| 179 | - $filter->order_by = 'ui.start_time'; | |
| 180 | - $filter->order = 'DESC'; | |
| 181 | - $filter->field_count = 'ui.user_item_id'; | |
| 182 | - $filter->only_fields = array( | |
| 183 | - 'ui.user_item_id', | |
| 184 | - 'ui.user_id', | |
| 185 | - 'ui.item_id', | |
| 186 | - 'ui.start_time', | |
| 187 | - 'ui.status', | |
| 188 | - 'ui.graduation', | |
| 189 | - 'u.display_name', | |
| 190 | - 'u.user_email', | |
| 191 | - 'p.post_title AS course_title', | |
| 192 | - ); | |
| 193 | - $filter->join[] = "JOIN {$lp_db_user_items->wpdb->posts} p ON ui.item_id = p.ID"; | |
| 194 | - $filter->join[] = "JOIN {$lp_db_user_items->wpdb->users} u ON ui.user_id = u.ID"; | |
| 195 | - | |
| 196 | - if ( $instructor_id > 0 ) { | |
| 197 | - $filter->where[] = $lp_db_user_items->wpdb->prepare( 'AND p.post_author = %d', $instructor_id ); | |
| 198 | - } | |
| 199 | - | |
| 200 | - if ( $course_id > 0 ) { | |
| 201 | - $filter->item_id = $course_id; | |
| 202 | - } elseif ( ! empty( $course_name ) ) { | |
| 203 | - $course_name_like = '%' . $lp_db_user_items->wpdb->esc_like( $course_name ) . '%'; | |
| 204 | - $filter->where[] = $lp_db_user_items->wpdb->prepare( | |
| 205 | - 'AND p.post_title LIKE %s', | |
| 206 | - $course_name_like | |
| 207 | - ); | |
| 208 | - } | |
| 209 | - | |
| 210 | - if ( ! empty( $search ) ) { | |
| 211 | - $search_like = '%' . $lp_db_user_items->wpdb->esc_like( $search ) . '%'; | |
| 212 | - $filter->where[] = $lp_db_user_items->wpdb->prepare( | |
| 213 | - 'AND ( u.display_name LIKE %s OR u.user_email LIKE %s )', | |
| 214 | - $search_like, | |
| 215 | - $search_like | |
| 216 | - ); | |
| 217 | - } | |
| 218 | - | |
| 219 | - if ( ! empty( $start_date ) ) { | |
| 220 | - $filter->where[] = $lp_db_user_items->wpdb->prepare( | |
| 221 | - 'AND ui.start_time >= %s', | |
| 222 | - $start_date . ' 00:00:00' | |
| 223 | - ); | |
| 224 | - } | |
| 225 | - | |
| 226 | - if ( ! empty( $end_date ) ) { | |
| 227 | - $filter->where[] = $lp_db_user_items->wpdb->prepare( | |
| 228 | - 'AND ui.start_time <= %s', | |
| 229 | - $end_date . ' 23:59:59' | |
| 230 | - ); | |
| 231 | - } | |
| 232 | - | |
| 233 | - $total_rows = 0; | |
| 234 | - $rows = $lp_db_user_items->get_user_items( $filter, $total_rows ); | |
| 235 | - if ( ! is_array( $rows ) ) { | |
| 236 | - $rows = array(); | |
| 237 | - } | |
| 238 | - | |
| 239 | - // Build HTML. | |
| 240 | - $html = self::instance()->html_table( | |
| 241 | - $rows, | |
| 242 | - array( | |
| 243 | - 'total' => $total_rows, | |
| 244 | - 'paged' => $paged, | |
| 245 | - 'per_page' => $per_page, | |
| 246 | - 'students-of-course' => $course_id, | |
| 247 | - ) | |
| 248 | - ); | |
| 249 | - | |
| 250 | - $content->content = $html; | |
| 251 | - } catch ( Throwable $e ) { | |
| 252 | - $content->content = Template::print_message( $e->getMessage(), 'error', false ); | |
| 253 | - LP_Debug::error_log( $e ); | |
| 254 | - } | |
| 255 | - | |
| 256 | - return $content; | |
| 257 | - } | |
| 258 | - | |
| 259 | - /** | |
| 260 | - * Check whether user can view enrolled students content. | |
| 261 | - * | |
| 262 | - * @param int $user_id | |
| 263 | - * | |
| 264 | - * @return bool | |
| 265 | - */ | |
| 266 | - private static function can_view_enrolled_students( int $user_id = 0 ): bool { | |
| 267 | - if ( $user_id < 1 ) { | |
| 268 | - $user_id = get_current_user_id(); | |
| 269 | - } | |
| 270 | - | |
| 271 | - if ( $user_id < 1 ) { | |
| 272 | - return false; | |
| 273 | - } | |
| 274 | - | |
| 275 | - $user_model = UserModel::find( $user_id, true ); | |
| 276 | - if ( ! $user_model instanceof UserModel ) { | |
| 277 | - return false; | |
| 278 | - } | |
| 279 | - | |
| 280 | - return $user_model->is_instructor(); | |
| 281 | - } | |
| 282 | - | |
| 283 | - /** | |
| 284 | - * Resolve instructor_id from request by permission. | |
| 285 | - * Admin can keep requested instructor_id; instructor is forced to self. | |
| 286 | - * | |
| 287 | - * @param array $data | |
| 288 | - * | |
| 289 | - * @return int | |
| 290 | - */ | |
| 291 | - private static function resolve_instructor_id_for_request( array $data ): int { | |
| 292 | - $current_user_id = get_current_user_id(); | |
| 293 | - if ( ! self::can_view_enrolled_students( $current_user_id ) ) { | |
| 294 | - return 0; | |
| 295 | - } | |
| 296 | - | |
| 297 | - $is_admin = user_can( $current_user_id, UserModel::ROLE_ADMINISTRATOR ); | |
| 298 | - if ( $is_admin ) { | |
| 299 | - return max( 0, abs( LP_Helper::sanitize_params_submitted( $data['instructor_id'] ?? 0, 'int' ) ) ); | |
| 300 | - } | |
| 301 | - | |
| 302 | - return $current_user_id; | |
| 303 | - } | |
| 304 | - | |
| 305 | - /** | |
| 306 | - * HTML builder: toolbar (course filter, search). | |
| 307 | - * | |
| 308 | - * @return string | |
| 309 | - */ | |
| 310 | - public function html_toolbar(): string { | |
| 311 | - $courses = array(); | |
| 312 | - | |
| 313 | - $data_get = LP_Helper::sanitize_params_submitted( $_GET ); | |
| 314 | - $selected_course = abs( LP_Helper::sanitize_params_submitted( $data_get['course_id'] ?? 0, 'int' ) ); | |
| 315 | - $search_course = LP_Helper::sanitize_params_submitted( $data_get['course_name'] ?? '' ); | |
| 316 | - $search_student = LP_Helper::sanitize_params_submitted( $data_get['search'] ?? '' ); | |
| 317 | - $search_start = self::sanitize_date_filter( $data_get['start_date'] ?? '' ); | |
| 318 | - $search_end = self::sanitize_date_filter( $data_get['end_date'] ?? '' ); | |
| 319 | - | |
| 320 | - $section = array( | |
| 321 | - 'wrap' => '<form class="lp-enrolled-students-table-toolbar lp-enrolled-students-form" onsubmit="return false;">', | |
| 322 | - 'filter-row-open' => '<div class="lp-enrolled-students-table-toolbar__row lp-enrolled-students-table-toolbar__row--filters">', | |
| 323 | - 'course-field-open' => '<div class="lp-enrolled-students-table-toolbar__field">', | |
| 324 | - 'course-label' => '<label class="lp-enrolled-students-table-toolbar__label" for="lp-enrolled-filter-course-name">' . esc_html__( 'Course Filter', 'learnpress' ) . '</label>', | |
| 325 | - 'course-input' => '<input id="lp-enrolled-filter-course-name" class="lp-enrolled-filter-course-name lp-enrolled-students-table-toolbar__input" type="text" name="course_name" list="lp-enrolled-course-list" value="' . esc_attr( $search_course ) . '" placeholder="' . esc_attr__( 'Search course...', 'learnpress' ) . '">', | |
| 326 | - 'course-field-close' => '</div>', | |
| 327 | - 'student-field-open' => '<div class="lp-enrolled-students-table-toolbar__field lp-enrolled-students-table-toolbar__field--student">', | |
| 328 | - 'student-label' => '<label class="lp-enrolled-students-table-toolbar__label" for="lp-enrolled-search-input">' . esc_html__( 'Student', 'learnpress' ) . '</label>', | |
| 329 | - 'student-input' => '<input id="lp-enrolled-search-input" class="lp-enrolled-search-input lp-enrolled-students-table-toolbar__input" type="text" name="search" value="' . esc_attr( $search_student ) . '" placeholder="' . esc_attr__( 'Enter student name or email', 'learnpress' ) . '">', | |
| 330 | - 'student-field-close' => '</div>', | |
| 331 | - 'start-field-open' => '<div class="lp-enrolled-students-table-toolbar__field lp-enrolled-students-table-toolbar__field--date">', | |
| 332 | - 'start-label' => '<label class="lp-enrolled-students-table-toolbar__label" for="lp-enrolled-filter-start-date">' . esc_html__( 'Start Date', 'learnpress' ) . '</label>', | |
| 333 | - 'start-input' => '<input id="lp-enrolled-filter-start-date" class="lp-enrolled-filter-start-date lp-enrolled-students-table-toolbar__input" type="date" name="start_date" value="' . esc_attr( $search_start ) . '" placeholder="mm/dd/yyyy">', | |
| 334 | - 'start-field-close' => '</div>', | |
| 335 | - 'end-field-open' => '<div class="lp-enrolled-students-table-toolbar__field lp-enrolled-students-table-toolbar__field--date">', | |
| 336 | - 'end-label' => '<label class="lp-enrolled-students-table-toolbar__label" for="lp-enrolled-filter-end-date">' . esc_html__( 'End date', 'learnpress' ) . '</label>', | |
| 337 | - 'end-input' => '<input id="lp-enrolled-filter-end-date" class="lp-enrolled-filter-end-date lp-enrolled-students-table-toolbar__input" type="date" name="end_date" value="' . esc_attr( $search_end ) . '" placeholder="mm/dd/yyyy">', | |
| 338 | - 'end-field-close' => '</div>', | |
| 339 | - 'filter-row-close' => '</div>', | |
| 340 | - 'actions-open' => '<div class="lp-enrolled-students-table-toolbar__actions">', | |
| 341 | - 'search-btn' => sprintf( | |
| 342 | - '<button type="button" class="lp-button lp-enrolled-btn-search">%s</button>', | |
| 343 | - esc_html__( 'Search', 'learnpress' ) | |
| 344 | - ), | |
| 345 | - 'clear-btn' => sprintf( | |
| 346 | - '<button type="button" class="lp-button lp-enrolled-btn-clear">%s</button>', | |
| 347 | - esc_html__( 'Clear Filter', 'learnpress' ) | |
| 348 | - ), | |
| 349 | - 'actions-close' => '</div>', | |
| 350 | - 'wrap-end' => '</form>', | |
| 351 | - ); | |
| 352 | - $section = apply_filters( | |
| 353 | - 'learn-press/admin/enrolled-students/toolbar/section', | |
| 354 | - $section, | |
| 355 | - $courses, | |
| 356 | - $selected_course, | |
| 357 | - ); | |
| 358 | - | |
| 359 | - return Template::combine_components( $section ); | |
| 360 | - } | |
| 361 | - | |
| 362 | - /** | |
| 363 | - * Render toolbar used inside View Students modal. | |
| 364 | - * | |
| 365 | - * @return string | |
| 366 | - */ | |
| 367 | - public function html_toolbar_modal(): string { | |
| 368 | - | |
| 369 | - $section = array( | |
| 370 | - 'wrap' => '<form class="lp-enrolled-students-table-toolbar lp-enrolled-students-table-toolbar--modal lp-enrolled-students-form lp-enrolled-students-form--modal" id="lp-modal-enrolled-form" onsubmit="return false;">', | |
| 371 | - 'filter-row-open' => '<div class="lp-enrolled-students-table-toolbar__row lp-enrolled-students-table-toolbar__row--filters">', | |
| 372 | - 'student-field-open' => '<div class="lp-enrolled-students-table-toolbar__field lp-enrolled-students-table-toolbar__field--student">', | |
| 373 | - 'student-label' => '<label class="lp-enrolled-students-table-toolbar__label" for="lp-modal-enrolled-search-input">' . esc_html__( 'Student', 'learnpress' ) . '</label>', | |
| 374 | - 'student-input' => '<input id="lp-modal-enrolled-search-input" class="lp-enrolled-search-input lp-enrolled-students-table-toolbar__input" type="text" name="search" placeholder="' . esc_attr__( 'Enter student name or email', 'learnpress' ) . '">', | |
| 375 | - 'student-field-close' => '</div>', | |
| 376 | - 'start-field-open' => '<div class="lp-enrolled-students-table-toolbar__field lp-enrolled-students-table-toolbar__field--date">', | |
| 377 | - 'start-label' => '<label class="lp-enrolled-students-table-toolbar__label" for="lp-modal-enrolled-filter-start-date">' . esc_html__( 'Start Date', 'learnpress' ) . '</label>', | |
| 378 | - 'start-input' => '<input id="lp-modal-enrolled-filter-start-date" class="lp-enrolled-filter-start-date lp-enrolled-students-table-toolbar__input" type="date" name="start_date" placeholder="mm/dd/yyyy">', | |
| 379 | - 'start-field-close' => '</div>', | |
| 380 | - 'end-field-open' => '<div class="lp-enrolled-students-table-toolbar__field lp-enrolled-students-table-toolbar__field--date">', | |
| 381 | - 'end-label' => '<label class="lp-enrolled-students-table-toolbar__label" for="lp-modal-enrolled-filter-end-date">' . esc_html__( 'End Date', 'learnpress' ) . '</label>', | |
| 382 | - 'end-input' => '<input id="lp-modal-enrolled-filter-end-date" class="lp-enrolled-filter-end-date lp-enrolled-students-table-toolbar__input" type="date" name="end_date" placeholder="mm/dd/yyyy">', | |
| 383 | - 'end-field-close' => '</div>', | |
| 384 | - 'filter-row-close' => '</div>', | |
| 385 | - 'actions-open' => '<div class="lp-enrolled-students-table-toolbar__actions">', | |
| 386 | - 'search-btn' => sprintf( | |
| 387 | - '<button type="button" class="lp-button lp-enrolled-btn-search-modal">%s</button>', | |
| 388 | - esc_html__( 'Search', 'learnpress' ) | |
| 389 | - ), | |
| 390 | - 'clear-btn' => sprintf( | |
| 391 | - '<button type="button" class="lp-button lp-enrolled-btn-clear-modal">%s</button>', | |
| 392 | - esc_html__( 'Clear Filter', 'learnpress' ) | |
| 393 | - ), | |
| 394 | - 'actions-close' => '</div>', | |
| 395 | - 'wrap-end' => '</form>', | |
| 396 | - ); | |
| 397 | - | |
| 398 | - return Template::combine_components( $section ); | |
| 399 | - } | |
| 400 | - | |
| 401 | - /** | |
| 402 | - * Render lp-target layout used in View Students modal. | |
| 403 | - * | |
| 404 | - * @return string | |
| 405 | - */ | |
| 406 | - public function html_modal_students_target(): string { | |
| 407 | - $args = array( | |
| 408 | - 'id_url' => 'lp-modal-enrolled-students', | |
| 409 | - 'course_id' => 0, | |
| 410 | - 'paged' => 1, | |
| 411 | - 'enableScrollToView' => false, | |
| 412 | - 'enableUpdateParamsUrl' => false, | |
| 413 | - 'html_no_load_ajax_first' => sprintf( | |
| 414 | - '<div class="learn-press-message" style="%s">%s</div>', | |
| 415 | - 'width: 95%;', | |
| 416 | - esc_html__( 'Loading', 'learnpress' ) | |
| 417 | - ), | |
| 418 | - ); | |
| 419 | - | |
| 420 | - $call_back = array( | |
| 421 | - 'class' => self::class, | |
| 422 | - 'method' => 'render_enrolled_students', | |
| 423 | - ); | |
| 424 | - | |
| 425 | - $section = array( | |
| 426 | - 'wrap' => '<div id="lp-modal-enrolled-wrap">', | |
| 427 | - 'target' => TemplateAJAX::load_content_via_ajax( $args, $call_back ), | |
| 428 | - 'wrap-end' => '</div>', | |
| 429 | - ); | |
| 430 | - | |
| 431 | - return Template::combine_components( $section ); | |
| 432 | - } | |
| 433 | - | |
| 434 | - /** | |
| 435 | - * Print HTML template for modal toolbar. | |
| 436 | - * | |
| 437 | - * @return void | |
| 438 | - */ | |
| 439 | - public function print_modal_toolbar_template() { | |
| 440 | - | |
| 441 | - $should_print = false; | |
| 442 | - | |
| 443 | - if ( is_admin() ) { | |
| 444 | - $screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null; | |
| 445 | - if ( $screen && isset( $screen->id ) && $screen->id === 'edit-' . LP_COURSE_CPT ) { | |
| 446 | - $should_print = true; | |
| 447 | - } | |
| 448 | - } elseif ( class_exists( 'LP_Page_Controller' ) && defined( 'LP_PAGE_PROFILE' ) ) { | |
| 449 | - $should_print = LP_Page_Controller::page_current() === LP_PAGE_PROFILE; | |
| 450 | - } | |
| 451 | - | |
| 452 | - if ( ! $should_print ) { | |
| 453 | - return; | |
| 454 | - } | |
| 455 | - | |
| 456 | - echo '<script type="text/html" id="lp-tmpl-enrolled-students-toolbar-modal">'; | |
| 457 | - echo $this->html_toolbar_modal(); | |
| 458 | - echo '</script>'; | |
| 459 | - | |
| 460 | - echo '<script type="text/html" id="lp-tmpl-enrolled-students-target-modal">'; | |
| 461 | - echo $this->html_modal_students_target(); | |
| 462 | - echo '</script>'; | |
| 463 | - } | |
| 464 | - | |
| 465 | - /** | |
| 466 | - * HTML builder: table wrapper. | |
| 467 | - * | |
| 468 | - * @param array $rows DB result rows. | |
| 469 | - * @param array $meta [ total, paged, per_page ]. | |
| 470 | - * | |
| 471 | - * @return string | |
| 472 | - */ | |
| 473 | - private function html_table( array $rows, array $meta ): string { | |
| 474 | - if ( empty( $rows ) ) { | |
| 475 | - $section_empty = array( | |
| 476 | - 'wrap' => '<div class="lp-enrolled-students-table-wrap">', | |
| 477 | - 'empty' => '<div class="lp-enrolled-empty"><p>' . esc_html__( 'No students found.', 'learnpress' ) . '</p></div>', | |
| 478 | - 'wrap-end' => '</div>', | |
| 479 | - ); | |
| 480 | - $section_empty = apply_filters( | |
| 481 | - 'learn-press/admin/enrolled-students/table/empty/section', | |
| 482 | - $section_empty, | |
| 483 | - $rows, | |
| 484 | - $meta | |
| 485 | - ); | |
| 486 | - | |
| 487 | - return Template::combine_components( $section_empty ); | |
| 488 | - } | |
| 489 | - | |
| 490 | - $rows_html = array(); | |
| 491 | - | |
| 492 | - $results_map = array(); | |
| 493 | - foreach ( $rows as $item ) { | |
| 494 | - $userCourseModel = new UserCourseModel( $item ); | |
| 495 | - $rows_html[] = $this->html_student_row( $userCourseModel, $meta ); | |
| 496 | - } | |
| 497 | - $rows_html = apply_filters( | |
| 498 | - 'learn-press/admin/enrolled-students/table/rows-html', | |
| 499 | - $rows_html, | |
| 500 | - $rows, | |
| 501 | - $results_map, | |
| 502 | - $meta | |
| 503 | - ); | |
| 504 | - | |
| 505 | - $table_args = array( | |
| 506 | - 'class_table' => 'lp-enrolled-students-table', | |
| 507 | - 'header' => array( | |
| 508 | - 'student' => array( | |
| 509 | - 'class' => 'lp-col-student', | |
| 510 | - 'title' => esc_html__( 'Student', 'learnpress' ), | |
| 511 | - ), | |
| 512 | - 'course' => array( | |
| 513 | - 'class' => 'lp-col-course', | |
| 514 | - 'title' => esc_html__( 'Course', 'learnpress' ), | |
| 515 | - ), | |
| 516 | - 'date' => array( | |
| 517 | - 'class' => 'lp-col-date', | |
| 518 | - 'title' => esc_html__( 'Enrolled Date', 'learnpress' ), | |
| 519 | - ), | |
| 520 | - 'progress' => array( | |
| 521 | - 'class' => 'lp-col-progress', | |
| 522 | - 'title' => esc_html__( 'Progress', 'learnpress' ), | |
| 523 | - ), | |
| 524 | - 'status' => array( | |
| 525 | - 'class' => 'lp-col-status', | |
| 526 | - 'title' => esc_html__( 'Status', 'learnpress' ), | |
| 527 | - ), | |
| 528 | - ), | |
| 529 | - 'body' => array( | |
| 530 | - 'rows_html' => Template::combine_components( $rows_html ), | |
| 531 | - ), | |
| 532 | - ); | |
| 533 | - $section_footer = array( | |
| 534 | - 'wrap' => '<div class="lp-enrolled-students-table-footer">', | |
| 535 | - 'page_result' => sprintf( | |
| 536 | - '<span class="lp-enrolled-students-table-footer__count">%s</span>', | |
| 537 | - TableListTemplate::instance()->html_page_result( | |
| 538 | - array( | |
| 539 | - 'paged' => $meta['paged'], | |
| 540 | - 'per_page' => $meta['per_page'], | |
| 541 | - 'total_rows' => $meta['total'], | |
| 542 | - 'item_name' => _n( 'student', 'students', $meta['total'], 'learnpress' ), | |
| 543 | - ) | |
| 544 | - ) | |
| 545 | - ), | |
| 546 | - 'pagination' => $this->html_pagination( $meta['total'], $meta['paged'], $meta['per_page'] ), | |
| 547 | - 'wrap-end' => '</div>', | |
| 548 | - ); | |
| 549 | - $section_footer = apply_filters( | |
| 550 | - 'learn-press/admin/enrolled-students/table/footer/section', | |
| 551 | - $section_footer, | |
| 552 | - $rows, | |
| 553 | - $results_map, | |
| 554 | - $meta | |
| 555 | - ); | |
| 556 | - $table_args['footer'] = Template::combine_components( $section_footer ); | |
| 557 | - $table_args = apply_filters( | |
| 558 | - 'learn-press/admin/enrolled-students/table/args', | |
| 559 | - $table_args, | |
| 560 | - $rows, | |
| 561 | - $results_map, | |
| 562 | - $meta | |
| 563 | - ); | |
| 564 | - | |
| 565 | - if ( ! empty( $meta['students-of-course'] ) ) { | |
| 566 | - unset( $table_args['header']['course'] ); // Remove Course column if filtering by course, as it's redundant. | |
| 567 | - } | |
| 568 | - | |
| 569 | - $section = array( | |
| 570 | - 'wrap' => '<div class="lp-enrolled-students-table-wrap">', | |
| 571 | - 'table' => TableListTemplate::instance()->html_table( $table_args ), | |
| 572 | - 'wrap-end' => '</div>', | |
| 573 | - ); | |
| 574 | - $section = apply_filters( | |
| 575 | - 'learn-press/admin/enrolled-students/table/section', | |
| 576 | - $section, | |
| 577 | - $rows, | |
| 578 | - $results_map, | |
| 579 | - $meta | |
| 580 | - ); | |
| 581 | - | |
| 582 | - return Template::combine_components( $section ); | |
| 583 | - } | |
| 584 | - | |
| 585 | - /** | |
| 586 | - * HTML builder: single student row. | |
| 587 | - * | |
| 588 | - * @param UserCourseModel $userCourseModel | |
| 589 | - * | |
| 590 | - * @return string | |
| 591 | - */ | |
| 592 | - private function html_student_row( UserCourseModel $userCourseModel, array $data = [] ): string { | |
| 593 | - // Avatar initials. | |
| 594 | - $userModel = $userCourseModel->get_user_model(); | |
| 595 | - if ( ! $userModel instanceof UserModel ) { | |
| 596 | - return ''; | |
| 597 | - } | |
| 598 | - | |
| 599 | - $courseModel = $userCourseModel->get_course_model(); | |
| 600 | - if ( ! $courseModel instanceof CourseModel ) { | |
| 601 | - return ''; | |
| 602 | - } | |
| 603 | - | |
| 604 | - // Progress. | |
| 605 | - $course_result = $userCourseModel->calculate_course_results(); | |
| 606 | - $progress = (float) ( $course_result['result'] ?? 0 ); | |
| 607 | - | |
| 608 | - // Status badge. | |
| 609 | - $graduation = $userCourseModel->get_graduation(); | |
| 610 | - $status_raw = $graduation !== UserItemModel::GRADUATION_IN_PROGRESS | |
| 611 | - ? $userCourseModel->get_graduation() | |
| 612 | - : $userCourseModel->get_status(); | |
| 613 | - $status_label = ucfirst( str_replace( array( '-', '_' ), ' ', $status_raw ) ); | |
| 614 | - $badge_class = 'lp-badge--' . sanitize_html_class( $status_raw ); | |
| 615 | - | |
| 616 | - // Date. | |
| 617 | - $date = UserCourseTemplate::instance()->html_start_date_time( $userCourseModel, false ); | |
| 618 | - | |
| 619 | - $user_display_name = $userModel->get_display_name(); | |
| 620 | - if ( empty( $user_display_name ) ) { | |
| 621 | - $user_display_name = $userModel->get_username(); | |
| 622 | - } | |
| 623 | - | |
| 624 | - $section = array( | |
| 625 | - 'row' => '<tr>', | |
| 626 | - 'student-cell-open' => '<td class="lp-cell-student">', | |
| 627 | - 'avatar' => SingleInstructorTemplate::instance()->html_avatar( $userModel ), | |
| 628 | - 'meta-open' => '<div class="lp-meta">', | |
| 629 | - 'name' => sprintf( | |
| 630 | - '<span class="lp-name">%s</span>', | |
| 631 | - esc_html( $user_display_name ) | |
| 632 | - ), | |
| 633 | - 'email' => sprintf( | |
| 634 | - '<span class="lp-email">%s</span>', | |
| 635 | - esc_html( $userModel->get_email() ) | |
| 636 | - ), | |
| 637 | - 'meta-close' => '</div>', | |
| 638 | - 'student-cell-close' => '</td>', | |
| 639 | - 'course-cell' => sprintf( | |
| 640 | - '<td class="lp-cell-course"><a href="%s">%s</a></td>', | |
| 641 | - esc_url_raw( $courseModel->get_permalink() ), | |
| 642 | - $courseModel->get_title() | |
| 643 | - ), | |
| 644 | - 'date-cell' => sprintf( | |
| 645 | - '<td class="lp-cell-date">%s</td>', | |
| 646 | - wp_kses_post( $date ) | |
| 647 | - ), | |
| 648 | - 'progress-cell-open' => '<td class="lp-cell-progress">', | |
| 649 | - 'progress-bar' => sprintf( | |
| 650 | - '<div class="lp-progress-bar"><span class="" style="width: %d%%;"></span></div>', | |
| 651 | - $progress | |
| 652 | - ), | |
| 653 | - 'progress-text' => sprintf( | |
| 654 | - '<span class="lp-progress-text">%d%%</span>', | |
| 655 | - $progress | |
| 656 | - ), | |
| 657 | - 'progress-cell-close' => '</td>', | |
| 658 | - 'status-cell-open' => '<td class="lp-cell-status">', | |
| 659 | - 'status-badge' => sprintf( | |
| 660 | - '<span class="lp-badge %s">%s</span>', | |
| 661 | - esc_attr( $badge_class ), | |
| 662 | - esc_html( $status_label ) | |
| 663 | - ), | |
| 664 | - 'status-cell-close' => '</td>', | |
| 665 | - 'row-end' => '</tr>', | |
| 666 | - ); | |
| 667 | - | |
| 668 | - if ( ! empty( $data['students-of-course'] ) ) { | |
| 669 | - unset( $section['course-cell'] ); // Remove Course cell if filtering by course, as it's redundant. | |
| 670 | - } | |
| 671 | - | |
| 672 | - $section = apply_filters( | |
| 673 | - 'learn-press/admin/enrolled-students/row/section', | |
| 674 | - $section, | |
| 675 | - $userCourseModel | |
| 676 | - ); | |
| 677 | - | |
| 678 | - return Template::combine_components( $section ); | |
| 679 | - } | |
| 680 | - | |
| 681 | - /** | |
| 682 | - * HTML builder: pagination. | |
| 683 | - * | |
| 684 | - * Uses .page-numbers class for loadAJAX.js compatibility. | |
| 685 | - * | |
| 686 | - * @param int $total | |
| 687 | - * @param int $paged | |
| 688 | - * @param int $per_page | |
| 689 | - * | |
| 690 | - * @return string | |
| 691 | - */ | |
| 692 | - private function html_pagination( int $total, int $paged, int $per_page ): string { | |
| 693 | - $total_pages = max( 1, ceil( $total / $per_page ) ); | |
| 694 | - | |
| 695 | - $pagination_items = Template::instance()->html_pagination( | |
| 696 | - array( | |
| 697 | - 'total_pages' => $total_pages, | |
| 698 | - 'paged' => $paged, | |
| 699 | - 'wrapper' => array( | |
| 700 | - '<nav class="learn-press-pagination navigation pagination lp-pagination">' => '</nav>', | |
| 701 | - ), | |
| 702 | - ) | |
| 703 | - ); | |
| 704 | - $pagination_items = apply_filters( | |
| 705 | - 'learn-press/admin/enrolled-students/pagination/items', | |
| 706 | - $pagination_items, | |
| 707 | - $total, | |
| 708 | - $paged, | |
| 709 | - $per_page, | |
| 710 | - $total_pages | |
| 711 | - ); | |
| 712 | - | |
| 713 | - $section = array( | |
| 714 | - 'wrap' => '<div class="lp-enrolled-students-table-footer__pagination">', | |
| 715 | - 'pagination' => $pagination_items, | |
| 716 | - 'wrap-end' => '</div>', | |
| 717 | - ); | |
| 718 | - $section = apply_filters( | |
| 719 | - 'learn-press/admin/enrolled-students/pagination/section', | |
| 720 | - $section, | |
| 721 | - $total, | |
| 722 | - $paged, | |
| 723 | - $per_page, | |
| 724 | - $total_pages | |
| 725 | - ); | |
| 726 | - | |
| 727 | - return Template::combine_components( $section ); | |
| 728 | - } | |
| 729 | - | |
| 730 | - /** | |
| 731 | - * Sanitize date filter value in Y-m-d format. | |
| 732 | - * | |
| 733 | - * @param string $date | |
| 734 | - * | |
| 735 | - * @return string | |
| 736 | - */ | |
| 737 | - private static function sanitize_date_filter( $date ): string { | |
| 738 | - if ( ! is_scalar( $date ) ) { | |
| 739 | - return ''; | |
| 740 | - } | |
| 741 | - | |
| 742 | - $date = LP_Helper::sanitize_params_submitted( (string) $date ); | |
| 743 | - if ( preg_match( '/^\d{4}-\d{2}-\d{2}$/', $date ) ) { | |
| 744 | - return $date; | |
| 745 | - } | |
| 746 | - | |
| 747 | - return ''; | |
| 748 | - } | |
| 749 | -} | |
| 1 | +<?php | |
| 2 | + | |
| 3 | +namespace LearnPress\TemplateHooks\Admin; | |
| 4 | + | |
| 5 | +use Exception; | |
| 6 | +use LearnPress\Databases\UserItemsDB; | |
| 7 | +use LearnPress\Filters\UserItemsFilter; | |
| 8 | +use LearnPress\Helpers\Singleton; | |
| 9 | +use LearnPress\Helpers\Template; | |
| 10 | +use LearnPress\Models\CourseModel; | |
| 11 | +use LearnPress\Models\UserItems\UserCourseModel; | |
| 12 | +use LearnPress\Models\UserItems\UserItemModel; | |
| 13 | +use LearnPress\Models\UserModel; | |
| 14 | +use LearnPress\TemplateHooks\Instructor\SingleInstructorTemplate; | |
| 15 | +use LearnPress\TemplateHooks\Table\TableListTemplate; | |
| 16 | +use LearnPress\TemplateHooks\TemplateAJAX; | |
| 17 | +use LearnPress\TemplateHooks\UserItem\UserCourseTemplate; | |
| 18 | +use LP_Debug; | |
| 19 | +use LP_Helper; | |
| 20 | +use LP_Page_Controller; | |
| 21 | +use LP_Request; | |
| 22 | +use stdClass; | |
| 23 | +use Throwable; | |
| 24 | + | |
| 25 | +/** | |
| 26 | + * Template Admin List Students Enrolled. | |
| 27 | + * | |
| 28 | + * Displays enrolled students for Admin (all courses) and Instructor (own courses only). | |
| 29 | + * Provides WP Admin submenu + Frontend profile tab. | |
| 30 | + * | |
| 31 | + * @since 4.3.3 | |
| 32 | + * @version 1.0.1 | |
| 33 | + */ | |
| 34 | +class AdminListStudentsEnrolled { | |
| 35 | + use Singleton; | |
| 36 | + | |
| 37 | + const PER_PAGE = 10; | |
| 38 | + | |
| 39 | + public function init(): void { | |
| 40 | + // 2. Whitelist AJAX callback. | |
| 41 | + add_filter( 'lp/rest/ajax/allow_callback', array( $this, 'allow_callback' ) ); | |
| 42 | + // 4. Render a modal toolbar template from PHP (used by JS modal). | |
| 43 | + add_action( 'admin_footer', array( $this, 'print_modal_toolbar_template' ) ); | |
| 44 | + add_action( 'wp_footer', array( $this, 'print_modal_toolbar_template' ) ); | |
| 45 | + } | |
| 46 | + | |
| 47 | + /** | |
| 48 | + * Admin page output callback. | |
| 49 | + */ | |
| 50 | + public function admin_page_output() { | |
| 51 | + ob_start(); | |
| 52 | + $this->enrolled_students_layout(); | |
| 53 | + $content = ob_get_clean(); | |
| 54 | + | |
| 55 | + echo AdminTemplate::html_on_wp_admin_screen( | |
| 56 | + array( | |
| 57 | + 'content' => $content, | |
| 58 | + 'title' => __( 'Students', 'learnpress' ), | |
| 59 | + 'id' => 'lp-enrolled-students', | |
| 60 | + ) | |
| 61 | + ); | |
| 62 | + } | |
| 63 | + | |
| 64 | + /** | |
| 65 | + * Allow callback for AJAX. | |
| 66 | + * | |
| 67 | + * @param array $callbacks | |
| 68 | + * | |
| 69 | + * @return array | |
| 70 | + */ | |
| 71 | + public function allow_callback( array $callbacks ): array { | |
| 72 | + $callbacks[] = get_class( $this ) . ':render_enrolled_students'; | |
| 73 | + | |
| 74 | + return $callbacks; | |
| 75 | + } | |
| 76 | + | |
| 77 | + /** | |
| 78 | + * Render initial layout with TemplateAJAX::load_content_via_ajax(). | |
| 79 | + */ | |
| 80 | + public function enrolled_students_layout() { | |
| 81 | + try { | |
| 82 | + $page_current = ''; | |
| 83 | + if ( function_exists( 'get_current_screen' ) ) { | |
| 84 | + $wp_screen = get_current_screen(); | |
| 85 | + if ( $wp_screen ) { | |
| 86 | + // Page on the Admin screen. | |
| 87 | + if ( $wp_screen->id === 'learnpress_page_learn-press-students-enrolled' ) { | |
| 88 | + $page_current = 'learnpress_page_learn-press-students-enrolled'; | |
| 89 | + } | |
| 90 | + } | |
| 91 | + } | |
| 92 | + | |
| 93 | + $args = array( | |
| 94 | + 'id_url' => 'lp-enrolled-students', | |
| 95 | + 'course_id' => LP_Request::get_param( 'course_id', 0 ), | |
| 96 | + 'course_name' => LP_Request::get_param( 'course_name' ), | |
| 97 | + 'paged' => 1, | |
| 98 | + 'search' => lp_request::get_param( 'search' ), | |
| 99 | + 'start_date' => LP_Request::get_param( 'start_date' ), | |
| 100 | + 'end_date' => LP_Request::get_param( 'end_date' ), | |
| 101 | + 'enableUpdateParamsUrl' => false, | |
| 102 | + ); | |
| 103 | + | |
| 104 | + $call_back = array( | |
| 105 | + 'class' => self::class, | |
| 106 | + 'method' => 'render_enrolled_students', | |
| 107 | + ); | |
| 108 | + | |
| 109 | + $section = [ | |
| 110 | + 'wrap' => sprintf( | |
| 111 | + '<div class="lp-students-enrolled-layout %s">', | |
| 112 | + $page_current | |
| 113 | + ), | |
| 114 | + 'toolbar' => $this->html_toolbar(), | |
| 115 | + 'table' => TemplateAJAX::load_content_via_ajax( $args, $call_back ), | |
| 116 | + 'wrap-end' => '</div>', | |
| 117 | + ]; | |
| 118 | + | |
| 119 | + echo Template::combine_components( $section ); | |
| 120 | + } catch ( Throwable $e ) { | |
| 121 | + Template::print_message( $e->getMessage(), 'error' ); | |
| 122 | + } | |
| 123 | + } | |
| 124 | + | |
| 125 | + /** | |
| 126 | + * Static render method called by AJAX — returns stdClass{ content }. | |
| 127 | + * | |
| 128 | + * Permission check: Admin=full, Instructor=own courses only. | |
| 129 | + * | |
| 130 | + * @param array $data | |
| 131 | + * | |
| 132 | + * @return stdClass | |
| 133 | + */ | |
| 134 | + public static function render_enrolled_students( array $data ): stdClass { | |
| 135 | + $content = new stdClass(); | |
| 136 | + $content->content = ''; | |
| 137 | + | |
| 138 | + try { | |
| 139 | + // Check permission | |
| 140 | + if ( ! current_user_can( UserModel::ROLE_ADMINISTRATOR ) | |
| 141 | + && ! current_user_can( UserModel::ROLE_INSTRUCTOR ) ) { | |
| 142 | + throw new Exception( esc_html__( 'You do not have permission to view enrolled students.', 'learnpress' ) ); | |
| 143 | + } | |
| 144 | + | |
| 145 | + $course_id = abs( LP_Helper::sanitize_params_submitted( $data['course_id'] ?? 0, 'int' ) ); | |
| 146 | + $paged = max( 1, abs( LP_Helper::sanitize_params_submitted( $data['paged'] ?? 1, 'int' ) ) ); | |
| 147 | + $course_name = LP_Helper::sanitize_params_submitted( $data['course_name'] ?? '' ); | |
| 148 | + $search = LP_Helper::sanitize_params_submitted( $data['search'] ?? '' ); | |
| 149 | + $start_date = lp_helper::sanitize_params_submitted( $data['start_date'] ?? '' ); | |
| 150 | + $end_date = lp_helper::sanitize_params_submitted( $data['end_date'] ?? '' ); | |
| 151 | + $per_page = self::PER_PAGE; | |
| 152 | + | |
| 153 | + // Normalize date range if request is reversed. | |
| 154 | + if ( $start_date && $end_date && strtotime( $start_date ) > strtotime( $end_date ) ) { | |
| 155 | + $tmp = $start_date; | |
| 156 | + $start_date = $end_date; | |
| 157 | + $end_date = $tmp; | |
| 158 | + } | |
| 159 | + | |
| 160 | + $lp_db_user_items = UserItemsDB::getInstance(); | |
| 161 | + $filter = new UserItemsFilter(); | |
| 162 | + $filter->item_type = LP_COURSE_CPT; | |
| 163 | + $filter->statues = array( 'enrolled', 'finished' ); | |
| 164 | + $filter->limit = $per_page; | |
| 165 | + $filter->page = $paged; | |
| 166 | + $filter->order_by = 'ui.start_time'; | |
| 167 | + $filter->order = 'DESC'; | |
| 168 | + $filter->field_count = 'ui.user_item_id'; | |
| 169 | + $filter->only_fields = array( | |
| 170 | + 'ui.user_item_id', | |
| 171 | + 'ui.user_id', | |
| 172 | + 'ui.item_id', | |
| 173 | + 'ui.start_time', | |
| 174 | + 'ui.status', | |
| 175 | + 'ui.graduation', | |
| 176 | + 'u.display_name', | |
| 177 | + 'u.user_email', | |
| 178 | + 'p.post_title AS course_title', | |
| 179 | + ); | |
| 180 | + $filter->join[] = "JOIN {$lp_db_user_items->wpdb->posts} p ON ui.item_id = p.ID"; | |
| 181 | + $filter->join[] = "JOIN {$lp_db_user_items->wpdb->users} u ON ui.user_id = u.ID"; | |
| 182 | + | |
| 183 | + $instructor_id = get_current_user_id(); | |
| 184 | + if ( $instructor_id > 0 && current_user_can( UserModel::ROLE_INSTRUCTOR ) ) { | |
| 185 | + $filter->where[] = $lp_db_user_items->wpdb->prepare( 'AND p.post_author = %d', $instructor_id ); | |
| 186 | + } | |
| 187 | + | |
| 188 | + if ( $course_id > 0 ) { | |
| 189 | + $filter->item_id = $course_id; | |
| 190 | + } elseif ( ! empty( $course_name ) ) { | |
| 191 | + $course_name_like = '%' . $lp_db_user_items->wpdb->esc_like( $course_name ) . '%'; | |
| 192 | + $filter->where[] = $lp_db_user_items->wpdb->prepare( | |
| 193 | + 'AND p.post_title LIKE %s', | |
| 194 | + $course_name_like | |
| 195 | + ); | |
| 196 | + } | |
| 197 | + | |
| 198 | + if ( ! empty( $search ) ) { | |
| 199 | + $search_like = '%' . $lp_db_user_items->wpdb->esc_like( $search ) . '%'; | |
| 200 | + $filter->where[] = $lp_db_user_items->wpdb->prepare( | |
| 201 | + 'AND ( u.display_name LIKE %s OR u.user_email LIKE %s )', | |
| 202 | + $search_like, | |
| 203 | + $search_like | |
| 204 | + ); | |
| 205 | + } | |
| 206 | + | |
| 207 | + if ( ! empty( $start_date ) ) { | |
| 208 | + $filter->where[] = $lp_db_user_items->wpdb->prepare( | |
| 209 | + 'AND ui.start_time >= %s', | |
| 210 | + $start_date . ' 00:00:00' | |
| 211 | + ); | |
| 212 | + } | |
| 213 | + | |
| 214 | + if ( ! empty( $end_date ) ) { | |
| 215 | + $filter->where[] = $lp_db_user_items->wpdb->prepare( | |
| 216 | + 'AND ui.start_time <= %s', | |
| 217 | + $end_date . ' 23:59:59' | |
| 218 | + ); | |
| 219 | + } | |
| 220 | + | |
| 221 | + $filter = apply_filters( | |
| 222 | + 'learn-press/filter-enrolled-students', | |
| 223 | + $filter | |
| 224 | + ); | |
| 225 | + $total_rows = 0; | |
| 226 | + $rows = $lp_db_user_items->get_user_items( $filter, $total_rows ); | |
| 227 | + if ( ! is_array( $rows ) ) { | |
| 228 | + $rows = array(); | |
| 229 | + } | |
| 230 | + | |
| 231 | + // Build HTML. | |
| 232 | + $html = self::instance()->html_table( | |
| 233 | + $rows, | |
| 234 | + array( | |
| 235 | + 'total' => $total_rows, | |
| 236 | + 'paged' => $paged, | |
| 237 | + 'per_page' => $per_page, | |
| 238 | + 'students-of-course' => $course_id, | |
| 239 | + ) | |
| 240 | + ); | |
| 241 | + | |
| 242 | + $content->content = $html; | |
| 243 | + } catch ( Throwable $e ) { | |
| 244 | + $content->content = Template::print_message( $e->getMessage(), 'error', false ); | |
| 245 | + LP_Debug::error_log( $e ); | |
| 246 | + } | |
| 247 | + | |
| 248 | + return $content; | |
| 249 | + } | |
| 250 | + | |
| 251 | + /** | |
| 252 | + * HTML builder: toolbar (course filter, search). | |
| 253 | + * | |
| 254 | + * @return string | |
| 255 | + */ | |
| 256 | + public function html_toolbar(): string { | |
| 257 | + $courses = array(); | |
| 258 | + | |
| 259 | + $data_get = LP_Helper::sanitize_params_submitted( $_GET ); | |
| 260 | + $selected_course = abs( LP_Helper::sanitize_params_submitted( $data_get['course_id'] ?? 0, 'int' ) ); | |
| 261 | + $search_course = LP_Helper::sanitize_params_submitted( $data_get['course_name'] ?? '' ); | |
| 262 | + $search_student = LP_Helper::sanitize_params_submitted( $data_get['search'] ?? '' ); | |
| 263 | + $search_start = lp_helper::sanitize_params_submitted( $data_get['start_date'] ?? '' ); | |
| 264 | + $search_end = lp_helper::sanitize_params_submitted( $data_get['end_date'] ?? '' ); | |
| 265 | + | |
| 266 | + $html_fields = sprintf( | |
| 267 | + '<div class="filter-field"> | |
| 268 | + <label for="lp-enrolled-filter-course-name">%s</label> | |
| 269 | + <input id="lp-enrolled-filter-course-name" | |
| 270 | + class="lp-enrolled-filter-course-name" | |
| 271 | + type="text" | |
| 272 | + name="course_name" | |
| 273 | + list="lp-enrolled-course-list" | |
| 274 | + value="%s" | |
| 275 | + placeholder="%s"> | |
| 276 | + </div> | |
| 277 | + <div class="filter-field"> | |
| 278 | + <label for="lp-enrolled-search-input">%s</label> | |
| 279 | + <input id="lp-enrolled-search-input" | |
| 280 | + class="lp-enrolled-search-input" | |
| 281 | + type="text" | |
| 282 | + name="search" value="%s" placeholder="%s"> | |
| 283 | + </div> | |
| 284 | + <div class="filter-field"> | |
| 285 | + <label for="lp-enrolled-filter-start-date">%s</label> | |
| 286 | + <input id="lp-enrolled-filter-start-date" | |
| 287 | + class="lp-enrolled-filter-start-date" | |
| 288 | + type="date" name="start_date" | |
| 289 | + value="%s" | |
| 290 | + placeholder="mm/dd/yyyy"> | |
| 291 | + </div> | |
| 292 | + <div class="filter-field"> | |
| 293 | + <label for="lp-enrolled-filter-end-date">%s</label> | |
| 294 | + <input id="lp-enrolled-filter-end-date" class="lp-enrolled-filter-end-date" type="date" name="end_date" value="%s" placeholder="mm/dd/yyyy"> | |
| 295 | + </div> | |
| 296 | + <datalist id="lp-enrolled-course-list">%s</datalist>', | |
| 297 | + esc_html__( 'Course Filter', 'learnpress' ), | |
| 298 | + esc_attr( $search_course ), | |
| 299 | + esc_attr__( 'Search course...', 'learnpress' ), | |
| 300 | + esc_html__( 'Student', 'learnpress' ), | |
| 301 | + esc_attr( $search_student ), | |
| 302 | + esc_attr__( 'Enter student name or email', 'learnpress' ), | |
| 303 | + esc_html__( 'Start Date', 'learnpress' ), | |
| 304 | + esc_attr( $search_start ), | |
| 305 | + esc_html__( 'End date', 'learnpress' ), | |
| 306 | + esc_attr( $search_end ), | |
| 307 | + '' | |
| 308 | + ); | |
| 309 | + | |
| 310 | + $html_btn_actions = sprintf( | |
| 311 | + '<button type="button" class="lp-button lp-enrolled-btn-search">%s</button> | |
| 312 | + <button type="button" class="lp-button lp-enrolled-btn-clear">%s</button>', | |
| 313 | + esc_html__( 'Search', 'learnpress' ), | |
| 314 | + esc_html__( 'Clear Filter', 'learnpress' ) | |
| 315 | + ); | |
| 316 | + | |
| 317 | + $html_toolbar = AdminTemplate::html_form_filter( | |
| 318 | + array( | |
| 319 | + 'form_classes' => 'lp-enrolled-students-form', | |
| 320 | + 'fields' => $html_fields, | |
| 321 | + 'btn_actions' => $html_btn_actions, | |
| 322 | + ) | |
| 323 | + ); | |
| 324 | + | |
| 325 | + return apply_filters( | |
| 326 | + 'learn-press/admin/enrolled-students/toolbar/section', | |
| 327 | + $html_toolbar, | |
| 328 | + $courses, | |
| 329 | + $selected_course | |
| 330 | + ); | |
| 331 | + } | |
| 332 | + | |
| 333 | + /** | |
| 334 | + * Render toolbar used inside View Students modal. | |
| 335 | + * | |
| 336 | + * @return string | |
| 337 | + */ | |
| 338 | + public function html_toolbar_modal(): string { | |
| 339 | + $html_fields = sprintf( | |
| 340 | + '<div class="filter-field"> | |
| 341 | + <label for="lp-modal-enrolled-search-input">%s</label> | |
| 342 | + <input id="lp-modal-enrolled-search-input" | |
| 343 | + class="lp-enrolled-search-input" | |
| 344 | + type="text" name="search" placeholder="%s"> | |
| 345 | + </div> | |
| 346 | + <div class="filter-field"> | |
| 347 | + <label for="lp-modal-enrolled-filter-start-date">%s</label> | |
| 348 | + <input id="lp-modal-enrolled-filter-start-date" | |
| 349 | + class="lp-enrolled-filter-start-date" | |
| 350 | + type="date" name="start_date" placeholder="mm/dd/yyyy"> | |
| 351 | + </div> | |
| 352 | + <div class="filter-field"> | |
| 353 | + <label for="lp-modal-enrolled-filter-end-date">%s</label> | |
| 354 | + <input id="lp-modal-enrolled-filter-end-date" | |
| 355 | + class="lp-enrolled-filter-end-date" | |
| 356 | + type="date" name="end_date" placeholder="mm/dd/yyyy"> | |
| 357 | + </div>', | |
| 358 | + esc_html__( 'Student', 'learnpress' ), | |
| 359 | + esc_attr__( 'Enter student name or email', 'learnpress' ), | |
| 360 | + esc_html__( 'Start Date', 'learnpress' ), | |
| 361 | + esc_html__( 'End Date', 'learnpress' ) | |
| 362 | + ); | |
| 363 | + | |
| 364 | + $html_btn_actions = sprintf( | |
| 365 | + '<button type="button" class="lp-button lp-enrolled-btn-search-modal">%s</button> | |
| 366 | + <button type="button" class="lp-button lp-enrolled-btn-clear-modal">%s</button>', | |
| 367 | + esc_html__( 'Search', 'learnpress' ), | |
| 368 | + esc_html__( 'Clear Filter', 'learnpress' ) | |
| 369 | + ); | |
| 370 | + | |
| 371 | + return AdminTemplate::html_form_filter( | |
| 372 | + array( | |
| 373 | + 'id' => 'lp-modal-enrolled-form', | |
| 374 | + 'form_classes' => 'lp-enrolled-students-form lp-enrolled-students-form--modal lp-enrolled-students-table-toolbar--modal', | |
| 375 | + 'fields' => $html_fields, | |
| 376 | + 'btn_actions' => $html_btn_actions, | |
| 377 | + ) | |
| 378 | + ); | |
| 379 | + } | |
| 380 | + | |
| 381 | + /** | |
| 382 | + * Render lp-target layout used in View Students modal. | |
| 383 | + * | |
| 384 | + * @return string | |
| 385 | + */ | |
| 386 | + public function html_modal_students_target(): string { | |
| 387 | + $args = array( | |
| 388 | + 'id_url' => 'lp-modal-enrolled-students', | |
| 389 | + 'course_id' => 0, | |
| 390 | + 'paged' => 1, | |
| 391 | + 'enableScrollToView' => false, | |
| 392 | + 'enableUpdateParamsUrl' => false, | |
| 393 | + 'html_no_load_ajax_first' => sprintf( | |
| 394 | + '<div class="learn-press-message" style="%s">%s</div>', | |
| 395 | + 'width: 95%;', | |
| 396 | + esc_html__( 'Loading', 'learnpress' ) | |
| 397 | + ), | |
| 398 | + ); | |
| 399 | + | |
| 400 | + $call_back = array( | |
| 401 | + 'class' => self::class, | |
| 402 | + 'method' => 'render_enrolled_students', | |
| 403 | + ); | |
| 404 | + | |
| 405 | + $section = array( | |
| 406 | + 'wrap' => '<div id="lp-modal-enrolled-wrap">', | |
| 407 | + 'target' => TemplateAJAX::load_content_via_ajax( $args, $call_back ), | |
| 408 | + 'wrap-end' => '</div>', | |
| 409 | + ); | |
| 410 | + | |
| 411 | + return Template::combine_components( $section ); | |
| 412 | + } | |
| 413 | + | |
| 414 | + /** | |
| 415 | + * Print HTML template for modal toolbar. | |
| 416 | + * | |
| 417 | + * @return void | |
| 418 | + */ | |
| 419 | + public function print_modal_toolbar_template() { | |
| 420 | + | |
| 421 | + $should_print = false; | |
| 422 | + | |
| 423 | + if ( is_admin() ) { | |
| 424 | + $screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null; | |
| 425 | + if ( $screen && isset( $screen->id ) && $screen->id === 'edit-' . LP_COURSE_CPT ) { | |
| 426 | + $should_print = true; | |
| 427 | + } | |
| 428 | + } elseif ( class_exists( 'LP_Page_Controller' ) && defined( 'LP_PAGE_PROFILE' ) ) { | |
| 429 | + $should_print = LP_Page_Controller::page_current() === LP_PAGE_PROFILE; | |
| 430 | + } | |
| 431 | + | |
| 432 | + if ( ! $should_print ) { | |
| 433 | + return; | |
| 434 | + } | |
| 435 | + | |
| 436 | + echo '<script type="text/html" id="lp-tmpl-enrolled-students-toolbar-modal">'; | |
| 437 | + echo $this->html_toolbar_modal(); | |
| 438 | + echo '</script>'; | |
| 439 | + | |
| 440 | + echo '<script type="text/html" id="lp-tmpl-enrolled-students-target-modal">'; | |
| 441 | + echo $this->html_modal_students_target(); | |
| 442 | + echo '</script>'; | |
| 443 | + } | |
| 444 | + | |
| 445 | + /** | |
| 446 | + * HTML builder: table wrapper. | |
| 447 | + * | |
| 448 | + * @param array $rows DB result rows. | |
| 449 | + * @param array $meta [ total, paged, per_page ]. | |
| 450 | + * | |
| 451 | + * @return string | |
| 452 | + */ | |
| 453 | + private function html_table( array $rows, array $meta ): string { | |
| 454 | + if ( empty( $rows ) ) { | |
| 455 | + $section_empty = array( | |
| 456 | + 'wrap' => '<div class="lp-enrolled-students-table-wrap">', | |
| 457 | + 'empty' => sprintf( | |
| 458 | + '<div class="lp-enrolled-empty"><p>%s</p></div>', | |
| 459 | + esc_html__( 'No students found.', 'learnpress' ) | |
| 460 | + ), | |
| 461 | + 'wrap-end' => '</div>', | |
| 462 | + ); | |
| 463 | + $section_empty = apply_filters( | |
| 464 | + 'learn-press/admin/enrolled-students/table/empty/section', | |
| 465 | + $section_empty, | |
| 466 | + $rows, | |
| 467 | + $meta | |
| 468 | + ); | |
| 469 | + | |
| 470 | + return Template::combine_components( $section_empty ); | |
| 471 | + } | |
| 472 | + | |
| 473 | + $rows_html = array(); | |
| 474 | + | |
| 475 | + $results_map = array(); | |
| 476 | + foreach ( $rows as $item ) { | |
| 477 | + $userCourseModel = new UserCourseModel( $item ); | |
| 478 | + $rows_html[] = $this->html_student_row( $userCourseModel, $meta ); | |
| 479 | + } | |
| 480 | + $rows_html = apply_filters( | |
| 481 | + 'learn-press/admin/enrolled-students/table/rows-html', | |
| 482 | + $rows_html, | |
| 483 | + $rows, | |
| 484 | + $results_map, | |
| 485 | + $meta | |
| 486 | + ); | |
| 487 | + | |
| 488 | + $table_args = array( | |
| 489 | + 'class_table' => 'lp-enrolled-students-table', | |
| 490 | + 'header' => array( | |
| 491 | + 'student' => array( | |
| 492 | + 'class' => 'lp-col-student', | |
| 493 | + 'title' => esc_html__( 'Student', 'learnpress' ), | |
| 494 | + ), | |
| 495 | + 'course' => array( | |
| 496 | + 'class' => 'lp-col-course', | |
| 497 | + 'title' => esc_html__( 'Course', 'learnpress' ), | |
| 498 | + ), | |
| 499 | + 'date' => array( | |
| 500 | + 'class' => 'lp-col-date', | |
| 501 | + 'title' => esc_html__( 'Enrolled Date', 'learnpress' ), | |
| 502 | + ), | |
| 503 | + 'progress' => array( | |
| 504 | + 'class' => 'lp-col-progress', | |
| 505 | + 'title' => esc_html__( 'Progress', 'learnpress' ), | |
| 506 | + ), | |
| 507 | + 'status' => array( | |
| 508 | + 'class' => 'lp-col-status', | |
| 509 | + 'title' => esc_html__( 'Status', 'learnpress' ), | |
| 510 | + ), | |
| 511 | + ), | |
| 512 | + 'body' => array( | |
| 513 | + 'rows_html' => Template::combine_components( $rows_html ), | |
| 514 | + ), | |
| 515 | + ); | |
| 516 | + $section_footer = array( | |
| 517 | + 'wrap' => '<div class="lp-enrolled-students-table-footer">', | |
| 518 | + 'page_result' => sprintf( | |
| 519 | + '<span class="lp-enrolled-students-table-footer__count">%s</span>', | |
| 520 | + TableListTemplate::instance()->html_page_result( | |
| 521 | + array( | |
| 522 | + 'paged' => $meta['paged'], | |
| 523 | + 'per_page' => $meta['per_page'], | |
| 524 | + 'total_rows' => $meta['total'], | |
| 525 | + 'item_name' => _n( 'student', 'students', $meta['total'], 'learnpress' ), | |
| 526 | + ) | |
| 527 | + ) | |
| 528 | + ), | |
| 529 | + 'pagination' => $this->html_pagination( $meta['total'], $meta['paged'], $meta['per_page'] ), | |
| 530 | + 'wrap-end' => '</div>', | |
| 531 | + ); | |
| 532 | + $section_footer = apply_filters( | |
| 533 | + 'learn-press/admin/enrolled-students/table/footer/section', | |
| 534 | + $section_footer, | |
| 535 | + $rows, | |
| 536 | + $results_map, | |
| 537 | + $meta | |
| 538 | + ); | |
| 539 | + $table_args['footer'] = Template::combine_components( $section_footer ); | |
| 540 | + $table_args = apply_filters( | |
| 541 | + 'learn-press/admin/enrolled-students/table/args', | |
| 542 | + $table_args, | |
| 543 | + $rows, | |
| 544 | + $results_map, | |
| 545 | + $meta | |
| 546 | + ); | |
| 547 | + | |
| 548 | + if ( ! empty( $meta['students-of-course'] ) ) { | |
| 549 | + unset( $table_args['header']['course'] ); // Remove Course column if filtering by course, as it's redundant. | |
| 550 | + } | |
| 551 | + | |
| 552 | + $section = array( | |
| 553 | + 'wrap' => '<div class="lp-enrolled-students-table-wrap">', | |
| 554 | + 'table' => TableListTemplate::instance()->html_table( $table_args ), | |
| 555 | + 'wrap-end' => '</div>', | |
| 556 | + ); | |
| 557 | + $section = apply_filters( | |
| 558 | + 'learn-press/admin/enrolled-students/table/section', | |
| 559 | + $section, | |
| 560 | + $rows, | |
| 561 | + $results_map, | |
| 562 | + $meta | |
| 563 | + ); | |
| 564 | + | |
| 565 | + return Template::combine_components( $section ); | |
| 566 | + } | |
| 567 | + | |
| 568 | + /** | |
| 569 | + * HTML builder: single student row. | |
| 570 | + * | |
| 571 | + * @param UserCourseModel $userCourseModel | |
| 572 | + * | |
| 573 | + * @return string | |
| 574 | + */ | |
| 575 | + private function html_student_row( UserCourseModel $userCourseModel, array $data = [] ): string { | |
| 576 | + // Avatar initials. | |
| 577 | + $userModel = $userCourseModel->get_user_model(); | |
| 578 | + if ( ! $userModel instanceof UserModel ) { | |
| 579 | + return ''; | |
| 580 | + } | |
| 581 | + | |
| 582 | + $courseModel = $userCourseModel->get_course_model(); | |
| 583 | + if ( ! $courseModel instanceof CourseModel ) { | |
| 584 | + return ''; | |
| 585 | + } | |
| 586 | + | |
| 587 | + // Progress. | |
| 588 | + $course_result = $userCourseModel->calculate_course_results(); | |
| 589 | + $progress = (float) ( $course_result['result'] ?? 0 ); | |
| 590 | + | |
| 591 | + // Status badge. | |
| 592 | + $graduation = $userCourseModel->get_graduation(); | |
| 593 | + $status_raw = $graduation !== UserItemModel::GRADUATION_IN_PROGRESS | |
| 594 | + ? $userCourseModel->get_graduation() | |
| 595 | + : $userCourseModel->get_status(); | |
| 596 | + $status_label = $graduation !== UserItemModel::GRADUATION_IN_PROGRESS | |
| 597 | + ? $userCourseModel->get_graduation_label() | |
| 598 | + : $userCourseModel->get_status_label(); | |
| 599 | + $badge_class = 'lp-badge--' . sanitize_html_class( $status_raw ); | |
| 600 | + | |
| 601 | + // Date. | |
| 602 | + $date = UserCourseTemplate::instance()->html_start_date_time( $userCourseModel, false ); | |
| 603 | + | |
| 604 | + $user_display_name = $userModel->get_display_name(); | |
| 605 | + if ( empty( $user_display_name ) ) { | |
| 606 | + $user_display_name = $userModel->get_username(); | |
| 607 | + } | |
| 608 | + | |
| 609 | + $section = array( | |
| 610 | + 'row' => '<tr>', | |
| 611 | + 'student-cell-open' => '<td><div class="lp-cell-student">', | |
| 612 | + 'avatar' => SingleInstructorTemplate::instance()->html_avatar( $userModel ), | |
| 613 | + 'meta-open' => '<div class="lp-meta">', | |
| 614 | + 'name' => sprintf( | |
| 615 | + '<span class="lp-name">%s</span>', | |
| 616 | + esc_html( $user_display_name ) | |
| 617 | + ), | |
| 618 | + 'email' => sprintf( | |
| 619 | + '<span class="lp-email">%s</span>', | |
| 620 | + esc_html( $userModel->get_email() ) | |
| 621 | + ), | |
| 622 | + 'meta-close' => '</div>', | |
| 623 | + 'student-cell-close' => '</div></td>', | |
| 624 | + 'course-cell' => sprintf( | |
| 625 | + '<td class="lp-cell-course"><a href="%s">%s</a></td>', | |
| 626 | + esc_url_raw( $courseModel->get_permalink() ), | |
| 627 | + $courseModel->get_title() | |
| 628 | + ), | |
| 629 | + 'date-cell' => sprintf( | |
| 630 | + '<td class="lp-cell-date">%s</td>', | |
| 631 | + wp_kses_post( $date ) | |
| 632 | + ), | |
| 633 | + 'progress-cell-open' => '<td class="lp-cell-progress">', | |
| 634 | + 'progress-bar' => sprintf( | |
| 635 | + '<div class="lp-progress-bar"><span class="" style="width: %d%%;"></span></div>', | |
| 636 | + $progress | |
| 637 | + ), | |
| 638 | + 'progress-text' => sprintf( | |
| 639 | + '<span class="lp-progress-text">%d%%</span>', | |
| 640 | + $progress | |
| 641 | + ), | |
| 642 | + 'progress-cell-close' => '</td>', | |
| 643 | + 'status-cell-open' => '<td class="lp-cell-status">', | |
| 644 | + 'status-badge' => sprintf( | |
| 645 | + '<span class="lp-badge %s">%s</span>', | |
| 646 | + esc_attr( $badge_class ), | |
| 647 | + esc_html( $status_label ) | |
| 648 | + ), | |
| 649 | + 'status-cell-close' => '</td>', | |
| 650 | + 'row-end' => '</tr>', | |
| 651 | + ); | |
| 652 | + | |
| 653 | + if ( ! empty( $data['students-of-course'] ) ) { | |
| 654 | + unset( $section['course-cell'] ); // Remove Course cell if filtering by course, as it's redundant. | |
| 655 | + } | |
| 656 | + | |
| 657 | + $section = apply_filters( | |
| 658 | + 'learn-press/admin/enrolled-students/row/section', | |
| 659 | + $section, | |
| 660 | + $userCourseModel | |
| 661 | + ); | |
| 662 | + | |
| 663 | + return Template::combine_components( $section ); | |
| 664 | + } | |
| 665 | + | |
| 666 | + /** | |
| 667 | + * HTML builder: pagination. | |
| 668 | + * | |
| 669 | + * Uses .page-numbers class for loadAJAX.js compatibility. | |
| 670 | + * | |
| 671 | + * @param int $total | |
| 672 | + * @param int $paged | |
| 673 | + * @param int $per_page | |
| 674 | + * | |
| 675 | + * @return string | |
| 676 | + */ | |
| 677 | + private function html_pagination( int $total, int $paged, int $per_page ): string { | |
| 678 | + $total_pages = max( 1, ceil( $total / $per_page ) ); | |
| 679 | + | |
| 680 | + $pagination_items = Template::instance()->html_pagination( | |
| 681 | + array( | |
| 682 | + 'total_pages' => $total_pages, | |
| 683 | + 'paged' => $paged, | |
| 684 | + 'wrapper' => array( | |
| 685 | + '<nav class="learn-press-pagination navigation pagination lp-pagination">' => '</nav>', | |
| 686 | + ), | |
| 687 | + ) | |
| 688 | + ); | |
| 689 | + $pagination_items = apply_filters( | |
| 690 | + 'learn-press/admin/enrolled-students/pagination/items', | |
| 691 | + $pagination_items, | |
| 692 | + $total, | |
| 693 | + $paged, | |
| 694 | + $per_page, | |
| 695 | + $total_pages | |
| 696 | + ); | |
| 697 | + | |
| 698 | + $section = array( | |
| 699 | + 'wrap' => '<div class="lp-enrolled-students-table-footer__pagination">', | |
| 700 | + 'pagination' => $pagination_items, | |
| 701 | + 'wrap-end' => '</div>', | |
| 702 | + ); | |
| 703 | + $section = apply_filters( | |
| 704 | + 'learn-press/admin/enrolled-students/pagination/section', | |
| 705 | + $section, | |
| 706 | + $total, | |
| 707 | + $paged, | |
| 708 | + $per_page, | |
| 709 | + $total_pages | |
| 710 | + ); | |
| 711 | + | |
| 712 | + return Template::combine_components( $section ); | |
| 713 | + } | |
| 714 | +} | |