PluginProbe
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses / 4.4.8
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses v4.4.8
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
← All changes | inc/TemplateHooks/Course/ListCoursesTemplate.php +934 -921 4.3.94.4.8 View file →
@@ -1,921 +1,934 @@
1 -<?php
2 -/**
3 - * Template hooks List Courses.
4 - *
5 - * @since 4.2.3.2
6 - * @version 1.0.3
7 - */
8 -
9 -namespace LearnPress\TemplateHooks\Course;
10 -
11 -use LearnPress\Helpers\Singleton;
12 -use LearnPress\Helpers\Template;
13 -use LearnPress\Models\CourseModel;
14 -use LearnPress\Models\Courses;
15 -use LearnPress\Models\UserItems\UserCourseModel;
16 -use LearnPress\TemplateHooks\TemplateAJAX;
17 -use LP_Course;
18 -use LP_Course_Filter;
19 -use LP_Database;
20 -use LP_Settings;
21 -use LP_Settings_Courses;
22 -use LP_User_Items_DB;
23 -use LP_User_Items_Filter;
24 -use LP_WP_Filesystem;
25 -use stdClass;
26 -use Throwable;
27 -use WP_Term;
28 -
29 -class ListCoursesTemplate {
30 - use Singleton;
31 -
32 - public function init() {
33 - add_action( 'learn-press/list-courses/layout', [ $this, 'layout_courses' ] );
34 - add_action( 'learn-press/rest-api/courses/suggest/layout', [ $this, 'sections_course_suggest' ] );
35 - add_action( 'learn-press/archive-course/sidebar', [ $this, 'sidebar' ] );
36 - add_filter( 'lp/rest/ajax/allow_callback', [ $this, 'allow_callback' ] );
37 - }
38 -
39 - public function allow_callback( $callbacks ) {
40 - /**
41 - * @uses self::render_courses()
42 - */
43 - $callbacks[] = get_class( $this ) . ':render_courses';
44 -
45 - return $callbacks;
46 - }
47 -
48 - /**
49 - * Layout default list courses.
50 - *
51 - * @return void
52 - * @since 4.2.5.8
53 - * @version 1.0.1
54 - */
55 - public function layout_courses() {
56 - $html_wrapper = [
57 - '<div class="lp-list-courses-default">' => '</div>',
58 - ];
59 -
60 - $callback = [
61 - 'class' => get_class( $this ),
62 - 'method' => 'render_courses',
63 - ];
64 -
65 - $args = lp_archive_skeleton_get_args();
66 - $args['id_url'] = 'list-courses-default';
67 - $args['courses_load_ajax'] = LP_Settings_Courses::is_ajax_load_courses() ? 1 : 0;
68 - $args['courses_first_no_ajax'] = LP_Settings_Courses::is_no_load_ajax_first_courses() ? 1 : 0;
69 -
70 - // Load list courses via AJAX.
71 - if ( LP_Settings_Courses::is_ajax_load_courses() && ! LP_Settings_Courses::is_no_load_ajax_first_courses() ) {
72 - $content = TemplateAJAX::load_content_via_ajax( $args, $callback );
73 - } else { // Load courses first not AJAX, or for filter.
74 - $content_obj = static::render_courses( $args );
75 - $args['html_no_load_ajax_first'] = $content_obj->content;
76 - $content = TemplateAJAX::load_content_via_ajax( $args, $callback );
77 - }
78 -
79 - echo Template::instance()->nest_elements( $html_wrapper, $content );
80 - }
81 -
82 - /**
83 - * Render template list courses with settings param.
84 - *
85 - * @param array $settings
86 - *
87 - * @return stdClass { content: string_html }
88 - * @since 4.2.5.7
89 - * @version 1.0.5
90 - */
91 - public static function render_courses( array $settings = [] ): stdClass {
92 - $filter = new LP_Course_Filter();
93 - Courses::handle_params_for_query_courses( $filter, $settings );
94 -
95 - $total_rows = 0;
96 - $courses = Courses::get_courses( $filter, $total_rows );
97 - $total_pages = LP_Database::get_total_pages( $filter->limit, $total_rows );
98 - $settings['total_pages'] = $total_pages;
99 - $settings['total_rows'] = $total_rows;
100 - $settings['courses_per_page'] = $filter->limit;
101 - $skin = $settings['skin'] ?? ( wp_is_mobile() ? 'grid' : learn_press_get_courses_layout() );
102 - $paged = $settings['paged'] ?? 1;
103 - $settings['paged'] = $paged;
104 - $listCoursesTemplate = self::instance();
105 -
106 - // HTML section courses.
107 - $html_courses = '';
108 - if ( empty( $courses ) ) {
109 - $html_courses = Template::print_message( __( 'No courses found', 'learnpress' ), 'info', false );
110 - } else {
111 - foreach ( $courses as $courseObj ) {
112 - $course = CourseModel::find( $courseObj->ID, true );
113 - $html_courses .= static::render_course( $course, $settings );
114 - }
115 - }
116 -
117 - $section_courses = apply_filters(
118 - 'learn-press/layout/list-courses/section/courses',
119 - [
120 - 'wrap' => sprintf(
121 - '<ul class="learn-press-courses lp-list-courses-no-css %1$s" data-layout="%1$s">',
122 - $skin
123 - ),
124 - 'courses' => $html_courses,
125 - 'wrap_end' => '</ul>',
126 - ],
127 - $courses,
128 - $settings
129 - );
130 -
131 - $section_top = apply_filters(
132 - 'learn-press/layout/list-courses/section/top',
133 - [
134 - 'wrapper' => '<div class="lp-courses-bar">',
135 - 'search' => $listCoursesTemplate->html_search_form( $settings ),
136 - 'order_by' => $listCoursesTemplate->html_order_by( $settings['order_by'] ?? 'post_date' ),
137 - 'switch_layout' => $listCoursesTemplate->switch_layout(),
138 - 'btn_filter_courses_mobile' => FilterCourseTemplate::instance()->html_btn_filter_mobile( $settings ),
139 - 'wrapper_end' => '</div>',
140 - ],
141 - $courses,
142 - $settings
143 - );
144 -
145 - // For old themes use old hook.
146 - $section_top = self::fix_theme_cb_hook_courses( $section_top, $courses, $settings );
147 - //$section= self::fix_theme_el_hook_old_render_courses( $section, $courses, $settings );
148 - // End.
149 -
150 - // Pagination html
151 - $data_pagination_type = LP_Settings::get_option( 'course_pagination_type', 'number' );
152 - if ( empty( $settings['courses_load_ajax'] ) ) {
153 - $data_pagination_type = 'number';
154 - }
155 - $data_pagination = [
156 - 'total_pages' => $total_pages,
157 - 'type' => $data_pagination_type,
158 - 'base' => add_query_arg( 'paged', '%#%', $settings['url_current'] ?? '' ),
159 - 'paged' => $settings['paged'] ?? 1,
160 - ];
161 - $html_pagination = static::instance()->html_pagination( $data_pagination );
162 -
163 - $section = apply_filters(
164 - 'learn-press/layout/list-courses/section',
165 - [
166 - 'top' => Template::combine_components( $section_top ),
167 - 'courses' => Template::combine_components( $section_courses ),
168 - 'pagination' => $html_pagination,
169 - ],
170 - $courses,
171 - $settings
172 - );
173 -
174 - $content = new stdClass();
175 - $content->content = Template::combine_components( $section );
176 - $content->total_pages = $total_pages;
177 - $content->paged = $paged;
178 -
179 - return $content;
180 - }
181 -
182 - /**
183 - * Render single item course
184 - *
185 - * @param CourseModel|LP_Course $course
186 - * @param array $settings
187 - *
188 - * @return string
189 - * @since 4.2.5.8
190 - * @version 1.0.4
191 - */
192 - public static function render_course( $course, array $settings = [] ): string {
193 - if ( ! $course instanceof CourseModel ) {
194 - $course = CourseModel::find( $course->get_id(), true );
195 - }
196 - $singleCourseTemplate = SingleCourseTemplate::instance();
197 - $show_view_students = ! empty( $settings['show_view_students'] );
198 -
199 - try {
200 - // New layout course item.
201 -
202 - // HTML top section, show image.
203 - $section_top = apply_filters(
204 - 'learn-press/layout/list-courses/item/section-top',
205 - [
206 - 'wrapper' => '<div class="course-thumbnail">',
207 - 'img' => sprintf(
208 - '<a href="%s">%s</a>',
209 - $course->get_permalink(),
210 - $singleCourseTemplate->html_image( $course )
211 - ),
212 - 'wrapper_end' => '</div>',
213 - ],
214 - $course,
215 - $settings
216 - );
217 -
218 - // HTML meta section.
219 - $meta_data = apply_filters(
220 - 'learn-press/layout/list-courses/item/meta-data',
221 - [
222 - 'duration' => $singleCourseTemplate->html_duration( $course ),
223 - 'level' => $singleCourseTemplate->html_level( $course ),
224 - 'lesson' => $singleCourseTemplate->html_count_item( $course, LP_LESSON_CPT ),
225 - 'quiz' => $singleCourseTemplate->html_count_item( $course, LP_QUIZ_CPT ),
226 - 'student' => $singleCourseTemplate->html_count_student( $course ),
227 - ],
228 - $course,
229 - $settings
230 - );
231 -
232 - if ( $course->is_offline() ) {
233 - $singleCourseOfflineTemplate = SingleCourseOfflineTemplate::instance();
234 - unset( $meta_data['quiz'] );
235 - unset( $meta_data['student'] );
236 - if ( ! empty( $meta_data['lesson'] ) ) {
237 - $meta_data['lesson'] = $singleCourseOfflineTemplate->html_lesson_info( $course, true );
238 - }
239 -
240 - // Add address for offline course.
241 - $html_address = $singleCourseOfflineTemplate->html_address( $course );
242 - if ( ! empty( $html_address ) ) {
243 - $meta_data['address'] = $singleCourseOfflineTemplate->html_address( $course );
244 - }
245 - }
246 -
247 - $html_meta_data = '';
248 - if ( ! empty( $meta_data ) ) {
249 - foreach ( $meta_data as $k => $v ) {
250 - $html_meta_data .= sprintf( '<div class="meta-item meta-item-%s">%s</div>', $k, $v );
251 - }
252 -
253 - $html_meta_data = sprintf( '<div class="course-wrap-meta">%s</div>', $html_meta_data );
254 - }
255 -
256 - $btn_read_more_text = SingleCourseTemplate::text_button_course( $course );
257 -
258 - // HTML bottom section end.
259 - $section_bottom_end = apply_filters(
260 - 'learn-press/layout/list-courses/item/section/bottom/end',
261 - [
262 - 'short_des' => $singleCourseTemplate->html_short_description( $course ),
263 - 'wrapper' => '<div class="course-info">',
264 - //'clearfix' => '<div class="clearfix"></div>',
265 - //'course_footer' => '<div class="course-footer">',
266 - 'price' => $singleCourseTemplate->html_price( $course ),
267 - 'btn_read_more' => sprintf(
268 - '<div class="course-readmore"><a href="%s">%s</a></div>',
269 - $course->get_permalink(),
270 - $btn_read_more_text
271 - ),
272 - 'btn_view_list' => $show_view_students ? sprintf(
273 - '<div class="lp-wrap-btn-view-course-students">
274 - <button type="button" class="lp-button lp-btn-view-students" data-course-id="%d" data-course-title="%s">%s</button>
275 - </div>',
276 - $course->get_id(),
277 - esc_attr( $course->get_title() ),
278 - esc_html__( 'View List Students', 'learnpress' )
279 - ) : '',
280 - //'course_footer_end' => '</div>',
281 - 'wrapper_end' => '</div>',
282 - ],
283 - $course,
284 - $settings
285 - );
286 -
287 - // HTML bottom section.
288 - $html_categories = $singleCourseTemplate->html_categories( $course );
289 - if ( ! empty( $html_categories ) ) {
290 - $html_categories = sprintf(
291 - '<div>%s %s</div>',
292 - sprintf( '<label>%s</label>', __( 'in', 'learnpress' ) ),
293 - $html_categories
294 - );
295 - }
296 - $section_bottom = apply_filters(
297 - 'learn-press/layout/list-courses/item/section/bottom',
298 - [
299 - 'wrapper' => '<div class="course-content">',
300 - 'title' => sprintf(
301 - '<h3 class="wap-course-title"><a class="course-permalink" href="%s">%s</a></h3>',
302 - $course->get_permalink(),
303 - $singleCourseTemplate->html_title( $course )
304 - ),
305 - 'featured' => $singleCourseTemplate->html_featured( $course ),
306 - 'wrapper_instructor_cate' => '<div class="course-instructor-category">',
307 - 'instructor' => sprintf(
308 - '<div>%s %s</div>',
309 - sprintf( '<label>%s</label>', __( 'by', 'learnpress' ) ),
310 - $singleCourseTemplate->html_instructor( $course )
311 - ),
312 - 'category' => $html_categories,
313 - 'wrapper_instructor_cate_end' => '</div>',
314 - 'meta' => $html_meta_data,
315 - 'info' => Template::combine_components( $section_bottom_end ),
316 - 'wrapper_end' => '</div>',
317 - ],
318 - $course,
319 - $settings
320 - );
321 -
322 - $section = apply_filters(
323 - 'learn-press/layout/list-courses/item-li',
324 - [
325 - 'wrapper_li' => '<li class="course">',
326 - 'wrapper_div' => sprintf( '<div class="course-item" data-id="%s">', esc_attr( $course->get_id() ) ),
327 - 'top' => Template::combine_components( $section_top ),
328 - 'bottom' => Template::combine_components( $section_bottom ),
329 - 'wrapper_div_end' => '</div>',
330 - 'wrapper_li_end' => '</li>',
331 - ],
332 - $course,
333 - $settings
334 - );
335 -
336 - // For old themes use old hook.
337 - $section = self::fix_theme_course_old( $section, $course, $settings );
338 - // End.
339 -
340 - $html_item = Template::combine_components( $section );
341 - // End new layout course item.
342 - } catch ( Throwable $e ) {
343 - $html_item = $e->getMessage();
344 - }
345 -
346 - return $html_item;
347 - }
348 -
349 - /**
350 - * Button Load more
351 - *
352 - * @return string
353 - * @since 4.2.3.3
354 - * @version 1.0.0
355 - */
356 - public function html_pagination_load_more(): string {
357 - $html_wrapper = [
358 - '<button class="courses-btn-load-more learn-press-pagination lp-button courses-btn-load-more-no-css">' => '</button>',
359 - ];
360 - $content = sprintf(
361 - '%s<span class="lp-loading-circle lp-loading-no-css hide"></span>',
362 - __( 'Load more', 'learnpress' )
363 - );
364 -
365 - return Template::instance()->nest_elements( $html_wrapper, $content );
366 - }
367 -
368 - /**
369 - * Button infinite
370 - *
371 - * @return string
372 - * @since 4.2.3.3
373 - * @version 1.0.0
374 - */
375 - public function html_pagination_infinite(): string {
376 - $html_wrapper = [
377 - '<div class="courses-load-infinite-no-css courses-load-infinite learn-press-pagination">' => '</div>',
378 - ];
379 - $content = '<span class="lp-loading-circle lp-loading-no-css hide"></span>';
380 -
381 - return Template::instance()->nest_elements( $html_wrapper, $content );
382 - }
383 -
384 - /**
385 - * Pagination number
386 - *
387 - * @param array $data
388 - *
389 - * @return string
390 - * @since 4.2.3.3
391 - * @version 1.0.0
392 - */
393 - public function html_pagination_number( array $data = [] ): string {
394 - if ( empty( $data['total_pages'] ) || $data['total_pages'] <= 1 ) {
395 - return '';
396 - }
397 -
398 - $html_wrapper = $data['wrapper'] ?? [
399 - '<nav class="learn-press-pagination navigation pagination">' => '</nav>',
400 - ];
401 -
402 - $pagination = paginate_links(
403 - apply_filters(
404 - 'learn_press_pagination_args',
405 - array(
406 - 'base' => $data['base'] ?? '',
407 - 'format' => '',
408 - 'add_args' => '',
409 - 'current' => max( 1, $data['paged'] ?? 1 ),
410 - 'total' => $data[ 'total_pages' ?? 1 ],
411 - 'prev_text' => '<i class="lp-icon-arrow-left"></i>',
412 - 'next_text' => '<i class="lp-icon-arrow-right"></i>',
413 - 'type' => 'list',
414 - 'end_size' => 3,
415 - 'mid_size' => 3,
416 - )
417 - )
418 - );
419 -
420 - return Template::instance()->nest_elements( $html_wrapper, $pagination );
421 - }
422 -
423 - /**
424 - * Pagination
425 - *
426 - * @param array $data
427 - *
428 - * @return string
429 - * @since 4.2.3.3
430 - * @version 1.0.0
431 - */
432 - public function html_pagination( array $data = [] ): string {
433 - if ( empty( $data['total_pages'] ) || $data['total_pages'] <= 1 ) {
434 - return '';
435 - }
436 -
437 - $pagination_type = $data['type'] ?? 'number';
438 - switch ( $pagination_type ) {
439 - case 'load-more':
440 - if ( $data['paged'] >= $data['total_pages'] ) {
441 - return '';
442 - }
443 -
444 - return $this->html_pagination_load_more();
445 - case 'infinite':
446 - if ( $data['paged'] >= $data['total_pages'] ) {
447 - return '';
448 - }
449 -
450 - return $this->html_pagination_infinite();
451 - default:
452 - return $this->html_pagination_number( $data );
453 - }
454 - }
455 -
456 - /**
457 - * Pagination
458 - *
459 - * @param array $data
460 - *
461 - * @return string
462 - * @since 4.2.3.3
463 - * @version 1.0.1
464 - */
465 - public function html_courses_page_result( array $data = [] ): string {
466 - $html = '';
467 -
468 - try {
469 - $total_rows = $data['total_rows'] ?? 0;
470 - $paged = $data['paged'] ?? 1;
471 - $course_per_page = $data['courses_per_page'] ?? 8;
472 - $pagination_type = $data['pagination_type'] ?? 'number';
473 -
474 - $from = 1 + ( $paged - 1 ) * $course_per_page;
475 - $to = ( $paged * $course_per_page > $total_rows ) ? $total_rows : $paged * $course_per_page;
476 -
477 - $content = '';
478 - if ( 0 === $total_rows ) {
479 -
480 - } elseif ( 1 === $total_rows ) {
481 - $content = esc_html__( 'Showing only one result', 'learnpress' );
482 - } else {
483 - $from_to = $from . '-' . $to;
484 - // For pagination type is load-more or infinite.
485 - if ( $pagination_type !== 'number' ) {
486 - $from_to = '1 - ' . $to;
487 - }
488 - $content = sprintf( esc_html__( 'Showing %1$s of %2$s results', 'learnpress' ), $from_to, $total_rows );
489 - }
490 -
491 - if ( ! empty( $content ) ) {
492 - $html = '<span class="courses-page-result">' . $content . '</span>';
493 - }
494 - } catch ( Throwable $e ) {
495 - error_log( $e->getMessage() );
496 - }
497 -
498 - return $html;
499 - }
500 -
501 - /**
502 - * Layouts type
503 - *
504 - * @param array $data
505 - *
506 - * @return string
507 - * @since 4.2.3.3
508 - * @version 1.0.0
509 - */
510 - public function html_layout_type( array $data = [] ): string {
511 - $html_wrapper = [
512 - '<div class="courses-layouts-display">' => '</div>',
513 - ];
514 -
515 - $ico_grid_default = LP_WP_Filesystem::get_icon_svg( 'ico-grid.svg' );
516 - $ico_list_default = LP_WP_Filesystem::get_icon_svg( 'ico-list.svg' );
517 -
518 - $layouts = [
519 - 'list' => $data['courses_ico_list'] ?? $ico_list_default,
520 - 'grid' => $data['courses_ico_grid'] ?? $ico_grid_default,
521 - ];
522 -
523 - $content = '<ul class="courses-layouts-display-list">';
524 - foreach ( $layouts as $k => $v ) {
525 - $active = ( $data['courses_layout_default'] ?? '' ) === $k ? 'active' : '';
526 - $content .= '<li class="courses-layout ' . $active . '" data-layout="' . $k . '">' . $v . '</li>';
527 - }
528 - $content .= '</ul>';
529 -
530 - return Template::instance()->nest_elements( $html_wrapper, $content );
531 - }
532 -
533 - /**
534 - * Order by
535 - *
536 - * @param string $default_value
537 - *
538 - * @return string
539 - * @since 4.2.3.2
540 - * @version 1.0.1
541 - */
542 - public function html_order_by( string $default_value = 'post_date' ): string {
543 - $html_wrapper = [
544 - '<div class="courses-order-by-wrapper">' => '</div>',
545 - ];
546 -
547 - $values = apply_filters(
548 - 'learn-press/courses/order-by/values',
549 - [
550 - 'post_date' => esc_html__( 'Newly published', 'learnpress' ),
551 - 'post_title' => esc_html__( 'Title a-z', 'learnpress' ),
552 - 'post_title_desc' => esc_html__( 'Title z-a', 'learnpress' ),
553 - 'price' => esc_html__( 'Price high to low', 'learnpress' ),
554 - 'price_low' => esc_html__( 'Price low to high', 'learnpress' ),
555 - 'popular' => esc_html__( 'Popular', 'learnpress' ),
556 - ]
557 - );
558 -
559 - $content = '<select name="order_by" class="courses-order-by">';
560 - foreach ( $values as $k => $v ) {
561 - $content .= '<option value="' . $k . '" ' . selected( $default_value, $k, false ) . '>' . $v . '</option>';
562 - }
563 - $content .= '</select>';
564 -
565 - return Template::instance()->nest_elements( $html_wrapper, $content );
566 - }
567 -
568 - public function html_search_form( array $data = [] ) {
569 - $s = $data['c_search'] ?? '';
570 - $class = $data['class'] ?? 'search-courses';
571 - ob_start();
572 - ?>
573 - <form class="<?php echo esc_attr( $class ); ?>" method="get"
574 - action="<?php echo esc_url_raw( learn_press_get_page_link( 'courses' ) ); ?>">
575 - <input type="search" placeholder="<?php esc_attr_e( 'Search courses...', 'learnpress' ); ?>"
576 - name="c_search"
577 - value="<?php echo esc_attr( $s ); ?>">
578 - <button type="submit" name="lp-btn-search-courses"><i class="lp-icon-search"></i></button>
579 - </form>
580 - <?php
581 - return ob_get_clean();
582 - }
583 -
584 - /**
585 - * Html switch layout.
586 - *
587 - * @return string
588 - */
589 - public function switch_layout(): string {
590 - $layouts = learn_press_courses_layouts();
591 - $active = learn_press_get_courses_layout();
592 -
593 - $html_layouts = '';
594 - foreach ( $layouts as $layout => $value ) {
595 - $html_layouts .= sprintf(
596 - '<input type="radio" name="lp-switch-layout-btn" value="%s" id="lp-switch-layout-btn-%s" %s>
597 - <label class="switch-btn %s" title="%s" for="lp-switch-layout-btn-%s"></label>',
598 - esc_attr( $layout ),
599 - esc_attr( $layout ),
600 - checked( $layout, $active, false ),
601 - esc_attr( $layout ),
602 - sprintf( esc_attr__( 'Switch to %s', 'learnpress' ), $value ),
603 - esc_attr( $layout )
604 - );
605 - }
606 -
607 - $section = apply_filters(
608 - 'learn-press/layout/list-courses/section/switch-layout',
609 - [
610 - 'wrapper' => '<div class="switch-layout">',
611 - 'layouts' => $html_layouts,
612 - 'wrapper_end' => '</div>',
613 - ]
614 - );
615 -
616 - return Template::combine_components( $section );
617 - }
618 -
619 - /**
620 - * Layout course search suggest result.
621 - *
622 - * @param array $data
623 - *
624 - * @return void
625 - * @since 4.2.3.2
626 - * @version 1.0.1
627 - */
628 - public function sections_course_suggest( array $data = [] ) {
629 - $content = '';
630 - $singleCourseTemplate = SingleCourseTemplate::instance();
631 -
632 - ob_start();
633 - try {
634 - $courses = $data['courses'] ?? [];
635 - $key_search = $data['keyword'] ?? '';
636 - $total_courses = $data['total_course'] ?? 0;
637 -
638 - // HTML section list courses.
639 - $html_list_course = '';
640 - foreach ( $courses as $courseObj ) {
641 - if ( ! is_object( $courseObj ) ) {
642 - continue;
643 - }
644 - $course_id = $courseObj->ID;
645 - $courseModel = CourseModel::find( $course_id, true );
646 - if ( ! $courseModel ) {
647 - continue;
648 - }
649 -
650 - $section_item = apply_filters(
651 - 'learn-press/course-suggest/item/sections',
652 - [
653 - 'wrapper' => '<li class="item-course-suggest">',
654 - 'course_image' => $singleCourseTemplate->html_image( $courseModel ),
655 - 'course_title' => sprintf(
656 - '<a href="%s">%s</a>',
657 - esc_url_raw( $courseModel->get_permalink() ),
658 - $singleCourseTemplate->html_title( $courseModel )
659 - ),
660 - 'wrapper_end' => '</li>',
661 - ],
662 - $courseModel,
663 - $key_search,
664 - $data
665 - );
666 - $html_list_course .= Template::combine_components( $section_item );
667 - }
668 -
669 - $count_courses = sprintf(
670 - '%s %s',
671 - $total_courses,
672 - _n( 'Course Found', 'Courses Found', $total_courses, 'learnpress' )
673 - );
674 - $view_all = sprintf(
675 - '<a href="%s">%s</a>',
676 - add_query_arg( 'c_search', $key_search, learn_press_get_page_link( 'courses' ) ),
677 - __( 'View All', 'learnpress' )
678 - );
679 -
680 - // Section.
681 - $section = [
682 - 'wrapper' => '<ul class="lp-courses-suggest-list">',
683 - 'courses' => $html_list_course,
684 - 'wrapper_end' => '</ul>',
685 - 'wrapper_info' => '<div class="lp-courses-suggest-info">',
686 - 'count' => $count_courses,
687 - 'view_all' => $view_all,
688 - 'wrapper_info_end' => '</div>',
689 - ];
690 - $content = Template::combine_components( $section );
691 - echo $content;
692 - } catch ( Throwable $e ) {
693 - Template::print_message( $e->getMessage(), 'error' );
694 - }
695 - }
696 -
697 - /**
698 - * Sidebar
699 - *
700 - * @return void
701 - * @version 1.0.0
702 - * @since 4.2.3.2
703 - */
704 - public function sidebar() {
705 - try {
706 - if ( is_active_sidebar( 'archive-courses-sidebar' ) ) {
707 - $html_wrapper = [
708 - '<div class="lp-archive-courses-sidebar">' => '</div>',
709 - ];
710 -
711 - ob_start();
712 - dynamic_sidebar( 'archive-courses-sidebar' );
713 - echo Template::instance()->nest_elements( $html_wrapper, ob_get_clean() );
714 - }
715 - } catch ( Throwable $e ) {
716 - error_log( $e->getMessage() );
717 - }
718 - }
719 -
720 - /**
721 - * Show total course free by Category.
722 - * @return string
723 - * @version 1.0.0
724 - *
725 - * @since 4.2.5.4
726 - */
727 - public function html_count_course_free(): string {
728 - $html_wrapper = [
729 - '<div class="courses-count-free">' => '</div>',
730 - ];
731 -
732 - $category_current = 0;
733 - $category_current_slug = get_query_var( 'term' );
734 - if ( ! empty( $category_current_slug ) ) {
735 - $category_current_obj = get_term_by( 'slug', $category_current_slug, LP_COURSE_CATEGORY_TAX );
736 - if ( $category_current_obj instanceof WP_Term ) {
737 - $category_current = $category_current_obj->term_id;
738 - }
739 - }
740 -
741 - $filter = new LP_Course_Filter();
742 - if ( ! empty( $category_current ) ) {
743 - $filter->term_ids = [ $category_current ];
744 - }
745 -
746 - $count = Courses::count_course_free( $filter );
747 - $content = sprintf(
748 - '<span class="courses-count-free-number">%1$s</span> %2$s',
749 - $count,
750 - _n( 'Free Course', 'Free Courses', $count, 'learnpress' )
751 - );
752 -
753 - return Template::instance()->nest_elements( $html_wrapper, $content );
754 - }
755 -
756 - /**
757 - * Show total students on Course Category.
758 - *
759 - * @return string
760 - * @since 4.2.5.4
761 - * @version 1.0.0
762 - */
763 - public function html_count_students(): string {
764 - $html_wrapper = [
765 - '<div class="courses-count-students">' => '</div>',
766 - ];
767 -
768 - $category_current = 0;
769 - $category_current_slug = get_query_var( 'term' );
770 - if ( ! empty( $category_current_slug ) ) {
771 - $category_current_obj = get_term_by( 'slug', $category_current_slug, LP_COURSE_CATEGORY_TAX );
772 - if ( $category_current_obj instanceof WP_Term ) {
773 - $category_current = $category_current_obj->term_id;
774 - }
775 - }
776 -
777 - $lp_user_items_db = LP_User_Items_DB::getInstance();
778 - $filter = new LP_User_Items_Filter();
779 -
780 - // If page is course category, get total students of this category.
781 - if ( ! empty( $category_current ) ) {
782 - $filter->join[] = "INNER JOIN {$lp_user_items_db->tb_posts} AS p ON ui.item_id = p.ID";
783 - $filter->join[] = "INNER JOIN {$lp_user_items_db->tb_term_relationships} AS r_term ON ui.item_id = r_term.object_id";
784 - $filter->where[] = $lp_user_items_db->wpdb->prepare( 'AND r_term.term_taxonomy_id = %d', $category_current );
785 - }
786 -
787 - $count = UserCourseModel::count_students( $filter );
788 - $content = sprintf(
789 - '<span class="courses-count-students-number">%1$s</span> %2$s',
790 - $count,
791 - _n( 'Student', 'Students', $count, 'learnpress' )
792 - );
793 -
794 - return Template::instance()->nest_elements( $html_wrapper, $content );
795 - }
796 -
797 - /************************** Hook old *****************************/
798 -
799 - /**
800 - * Fix theme course-builder use old hook.
801 - *
802 - * @param array $section
803 - *
804 - * @return array
805 - */
806 - public static function fix_theme_cb_hook_courses( $section, $courses, $settings ) {
807 - /*$theme_name = wp_get_theme()->get( 'Name' );
808 - if ( 'Course Builder' !== $theme_name ) {
809 - return $section;
810 - }*/
811 -
812 - $section_top = apply_filters(
813 - 'learn-press/list-courses/layout/section/top',
814 - [],
815 - $courses,
816 - $settings
817 - );
818 -
819 - $section_new = [];
820 - if ( ! empty( $section_top ) ) {
821 - foreach ( $section_top as $k => $v ) {
822 - $section_new[ $k ] = $v['text_html'] ?? '';
823 - }
824 -
825 - return $section_new;
826 - }
827 -
828 - return $section;
829 - }
830 -
831 - /**
832 - * Fix theme course-builder use old hook.
833 - *
834 - * @param array $section
835 - *
836 - * @return array
837 - */
838 - public static function fix_theme_course_old( $section, $course, $settings ) {
839 - /*$theme_name = wp_get_theme()->get( 'Name' );
840 - if ( 'Course Builder' !== $theme_name ) {
841 - return $section;
842 - }*/
843 -
844 - $course = learn_press_get_course( $course->get_id() );
845 -
846 - $wrapper = apply_filters(
847 - 'learn-press/list-courses/layout/item/wrapper',
848 - [],
849 - $course,
850 - $settings
851 - );
852 - if ( ! empty( $wrapper ) ) {
853 - $i = 0;
854 - foreach ( $wrapper as $k => $v ) {
855 - if ( $i === 0 ) {
856 - $section['wrapper_li'] = $k;
857 - $section['wrapper_li_end'] = $v;
858 - } elseif ( $i === 1 ) {
859 - $section['wrapper_div'] = $k;
860 - $section['wrapper_div_end'] = $v;
861 - break;
862 - }
863 -
864 - ++$i;
865 - }
866 - }
867 -
868 - $section_item = apply_filters(
869 - 'learn-press/list-courses/layout/item/section',
870 - [],
871 - $course,
872 - $settings
873 - );
874 - if ( ! empty( $section_item ) ) {
875 - $section['top'] = $section_item['thim_top']['text_html'] ?? '';
876 - }
877 -
878 - $section_bottom = apply_filters(
879 - 'learn-press/list-courses/layout/item/section/bottom',
880 - [],
881 - $course,
882 - $settings
883 - );
884 - $section_bottom_new = [];
885 - if ( ! empty( $section_bottom ) ) {
886 - foreach ( $section_bottom as $k => $v ) {
887 - $section_bottom_new[ $k ] = $v['text_html'] ?? '';
888 - }
889 -
890 - $section['bottom'] = Template::combine_components( $section_bottom_new );
891 - }
892 -
893 - return $section;
894 - }
895 -
896 - /**
897 - * Render string to data content
898 - *
899 - * @param array $data
900 - * @param string $data_content
901 - *
902 - * @return string
903 - */
904 - public function render_data( array $data, string $data_content ): string {
905 - return str_replace(
906 - [
907 - '{{courses_order_by}}',
908 - '{{courses_layout_type}}',
909 - '{{courses_pagination}}',
910 - '{{courses_page_result}}',
911 - ],
912 - [
913 - $this->html_order_by( $data['order_by'] ?? '' ),
914 - $this->html_layout_type( $data ),
915 - $this->html_pagination( $data['pagination'] ?? [] ),
916 - $this->html_courses_page_result( $data ),
917 - ],
918 - $data_content
919 - );
920 - }
921 -}
1 +<?php
2 +/**
3 + * Template hooks List Courses.
4 + *
5 + * @since 4.2.3.2
6 + * @version 1.0.3
7 + */
8 +
9 +namespace LearnPress\TemplateHooks\Course;
10 +
11 +use LearnPress\Helpers\Singleton;
12 +use LearnPress\Helpers\Template;
13 +use LearnPress\Models\CourseModel;
14 +use LearnPress\Models\Courses;
15 +use LearnPress\Models\UserItems\UserCourseModel;
16 +use LearnPress\TemplateHooks\TemplateAJAX;
17 +use LP_Course;
18 +use LP_Course_Filter;
19 +use LP_Database;
20 +use LP_Settings;
21 +use LP_Settings_Courses;
22 +use LP_User_Items_DB;
23 +use LP_User_Items_Filter;
24 +use LP_WP_Filesystem;
25 +use stdClass;
26 +use Throwable;
27 +use WP_Term;
28 +
29 +class ListCoursesTemplate {
30 + use Singleton;
31 +
32 + public function init() {
33 + add_action( 'learn-press/list-courses/layout', [ $this, 'layout_courses' ] );
34 + add_action( 'learn-press/rest-api/courses/suggest/layout', [ $this, 'sections_course_suggest' ] );
35 + add_action( 'learn-press/archive-course/sidebar', [ $this, 'sidebar' ] );
36 + add_filter( 'lp/rest/ajax/allow_callback', [ $this, 'allow_callback' ] );
37 + }
38 +
39 + public function allow_callback( $callbacks ) {
40 + /**
41 + * @uses self::render_courses()
42 + */
43 + $callbacks[] = get_class( $this ) . ':render_courses';
44 +
45 + return $callbacks;
46 + }
47 +
48 + /**
49 + * Layout default list courses.
50 + *
51 + * @return void
52 + * @since 4.2.5.8
53 + * @version 1.0.1
54 + */
55 + public function layout_courses() {
56 + $html_wrapper = [
57 + '<div class="lp-list-courses-default">' => '</div>',
58 + ];
59 +
60 + $callback = [
61 + 'class' => get_class( $this ),
62 + 'method' => 'render_courses',
63 + ];
64 +
65 + $args = lp_archive_skeleton_get_args();
66 + $args['id_url'] = 'list-courses-default';
67 + $args['courses_load_ajax'] = LP_Settings_Courses::is_ajax_load_courses() ? 1 : 0;
68 + $args['courses_first_no_ajax'] = LP_Settings_Courses::is_no_load_ajax_first_courses() ? 1 : 0;
69 +
70 + // Load list courses via AJAX.
71 + if ( LP_Settings_Courses::is_ajax_load_courses() && ! LP_Settings_Courses::is_no_load_ajax_first_courses() ) {
72 + $content = TemplateAJAX::load_content_via_ajax( $args, $callback );
73 + } else { // Load courses first not AJAX, or for filter.
74 + $content_obj = static::render_courses( $args );
75 + $args['html_no_load_ajax_first'] = $content_obj->content;
76 + $content = TemplateAJAX::load_content_via_ajax( $args, $callback );
77 + }
78 +
79 + echo Template::instance()->nest_elements( $html_wrapper, $content );
80 + }
81 +
82 + /**
83 + * Render template list courses with settings param.
84 + *
85 + * @param array $settings
86 + *
87 + * @return stdClass { content: string_html }
88 + * @since 4.2.5.7
89 + * @version 1.0.5
90 + */
91 + public static function render_courses( array $settings = [] ): stdClass {
92 + $filter = new LP_Course_Filter();
93 + Courses::handle_params_for_query_courses( $filter, $settings );
94 +
95 + $total_rows = 0;
96 + $courses = Courses::get_courses( $filter, $total_rows );
97 + $total_pages = LP_Database::get_total_pages( $filter->limit, $total_rows );
98 + $settings['total_pages'] = $total_pages;
99 + $settings['total_rows'] = $total_rows;
100 + $settings['courses_per_page'] = $filter->limit;
101 + $skin = $settings['skin'] ?? ( wp_is_mobile() ? 'grid' : learn_press_get_courses_layout() );
102 + $paged = $settings['paged'] ?? 1;
103 + $settings['paged'] = $paged;
104 + $listCoursesTemplate = self::instance();
105 +
106 + // HTML section courses.
107 + $html_courses = '';
108 + if ( empty( $courses ) ) {
109 + $html_courses = Template::print_message( __( 'No courses found', 'learnpress' ), 'info', false );
110 + } else {
111 + foreach ( $courses as $courseObj ) {
112 + $course = CourseModel::find( $courseObj->ID, true );
113 + $html_courses .= static::render_course( $course, $settings );
114 + }
115 + }
116 +
117 + $section_courses = apply_filters(
118 + 'learn-press/layout/list-courses/section/courses',
119 + [
120 + 'wrap' => sprintf(
121 + '<ul class="learn-press-courses lp-list-courses-no-css %1$s" data-layout="%1$s">',
122 + esc_attr( $skin )
123 + ),
124 + 'courses' => $html_courses,
125 + 'wrap_end' => '</ul>',
126 + ],
127 + $courses,
128 + $settings
129 + );
130 +
131 + $section_top = apply_filters(
132 + 'learn-press/layout/list-courses/section/top',
133 + [
134 + 'wrapper' => '<div class="lp-courses-bar">',
135 + 'search' => $listCoursesTemplate->html_search_form( $settings ),
136 + 'order_by' => $listCoursesTemplate->html_order_by( $settings['order_by'] ?? 'post_date' ),
137 + 'switch_layout' => $listCoursesTemplate->switch_layout(),
138 + 'btn_filter_courses_mobile' => FilterCourseTemplate::instance()->html_btn_filter_mobile( $settings ),
139 + 'wrapper_end' => '</div>',
140 + ],
141 + $courses,
142 + $settings
143 + );
144 +
145 + // For old themes use old hook.
146 + $section_top = self::fix_theme_cb_hook_courses( $section_top, $courses, $settings );
147 + //$section= self::fix_theme_el_hook_old_render_courses( $section, $courses, $settings );
148 + // End.
149 +
150 + // Pagination html
151 + $data_pagination_type = LP_Settings::get_option( 'course_pagination_type', 'number' );
152 + if ( empty( $settings['courses_load_ajax'] ) ) {
153 + $data_pagination_type = 'number';
154 + }
155 + $data_pagination = [
156 + 'total_pages' => $total_pages,
157 + 'type' => $data_pagination_type,
158 + 'base' => add_query_arg( 'paged', '%#%', $settings['url_current'] ?? '' ),
159 + 'paged' => $settings['paged'] ?? 1,
160 + ];
161 + $html_pagination = static::instance()->html_pagination( $data_pagination );
162 +
163 + $section = apply_filters(
164 + 'learn-press/layout/list-courses/section',
165 + [
166 + 'top' => Template::combine_components( $section_top ),
167 + 'courses' => Template::combine_components( $section_courses ),
168 + 'pagination' => $html_pagination,
169 + ],
170 + $courses,
171 + $settings
172 + );
173 +
174 + $content = new stdClass();
175 + $content->content = Template::combine_components( $section );
176 + $content->total_pages = $total_pages;
177 + $content->paged = $paged;
178 +
179 + return $content;
180 + }
181 +
182 + /**
183 + * Render single item course
184 + *
185 + * @param CourseModel|LP_Course $course
186 + * @param array $settings
187 + *
188 + * @return string
189 + * @since 4.2.5.8
190 + * @version 1.0.4
191 + */
192 + public static function render_course( $course, array $settings = [] ): string {
193 + if ( ! $course instanceof CourseModel ) {
194 + $course = CourseModel::find( $course->get_id(), true );
195 + }
196 + $singleCourseTemplate = SingleCourseTemplate::instance();
197 + $show_view_students = ! empty( $settings['show_view_students'] );
198 +
199 + try {
200 + // New layout course item.
201 +
202 + // HTML top section, show image.
203 + $section_top = apply_filters(
204 + 'learn-press/layout/list-courses/item/section-top',
205 + [
206 + 'wrapper' => '<div class="course-thumbnail">',
207 + 'img' => sprintf(
208 + '<a href="%s">%s</a>',
209 + esc_url( $course->get_permalink() ),
210 + $singleCourseTemplate->html_image( $course )
211 + ),
212 + 'wrapper_end' => '</div>',
213 + ],
214 + $course,
215 + $settings
216 + );
217 +
218 + // HTML meta section.
219 + $meta_data = apply_filters(
220 + 'learn-press/layout/list-courses/item/meta-data',
221 + [
222 + 'duration' => $singleCourseTemplate->html_duration( $course ),
223 + 'level' => $singleCourseTemplate->html_level( $course ),
224 + 'lesson' => $singleCourseTemplate->html_count_item( $course, LP_LESSON_CPT ),
225 + 'quiz' => $singleCourseTemplate->html_count_item( $course, LP_QUIZ_CPT ),
226 + 'student' => $singleCourseTemplate->html_count_student( $course ),
227 + ],
228 + $course,
229 + $settings
230 + );
231 +
232 + if ( $course->is_offline() ) {
233 + $singleCourseOfflineTemplate = SingleCourseOfflineTemplate::instance();
234 + unset( $meta_data['quiz'] );
235 + unset( $meta_data['student'] );
236 + if ( ! empty( $meta_data['lesson'] ) ) {
237 + $meta_data['lesson'] = $singleCourseOfflineTemplate->html_lesson_info( $course, true );
238 + }
239 +
240 + // Add address for offline course.
241 + $html_address = $singleCourseOfflineTemplate->html_address( $course );
242 + if ( ! empty( $html_address ) ) {
243 + $meta_data['address'] = $singleCourseOfflineTemplate->html_address( $course );
244 + }
245 + }
246 +
247 + $html_meta_data = '';
248 + if ( ! empty( $meta_data ) ) {
249 + foreach ( $meta_data as $k => $v ) {
250 + $html_meta_data .= sprintf( '<div class="meta-item meta-item-%s">%s</div>', $k, $v );
251 + }
252 +
253 + $html_meta_data = sprintf( '<div class="course-wrap-meta">%s</div>', $html_meta_data );
254 + }
255 +
256 + $btn_read_more_text = SingleCourseTemplate::text_button_course( $course );
257 +
258 + // HTML bottom section end.
259 + $section_bottom_end = apply_filters(
260 + 'learn-press/layout/list-courses/item/section/bottom/end',
261 + [
262 + 'short_des' => $singleCourseTemplate->html_short_description( $course ),
263 + 'wrapper' => '<div class="course-info">',
264 + //'clearfix' => '<div class="clearfix"></div>',
265 + //'course_footer' => '<div class="course-footer">',
266 + 'price' => $singleCourseTemplate->html_price( $course ),
267 + 'btn_read_more' => sprintf(
268 + '<div class="course-readmore"><a href="%s">%s</a></div>',
269 + esc_url( $course->get_permalink() ),
270 + $btn_read_more_text
271 + ),
272 + 'btn_view_list' => $show_view_students ? sprintf(
273 + '<div class="lp-wrap-btn-view-course-students">
274 + <button type="button" class="lp-button lp-btn-view-students" data-course-id="%s" data-course-title="%s">%s</button>
275 + </div>',
276 + esc_attr( $course->get_id() ),
277 + esc_attr( $course->get_title() ),
278 + esc_html__( 'View List Students', 'learnpress' )
279 + ) : '',
280 + //'course_footer_end' => '</div>',
281 + 'wrapper_end' => '</div>',
282 + ],
283 + $course,
284 + $settings
285 + );
286 +
287 + // HTML bottom section.
288 + $html_categories = $singleCourseTemplate->html_categories( $course );
289 + if ( ! empty( $html_categories ) ) {
290 + $html_categories = sprintf(
291 + '<div>%s %s</div>',
292 + sprintf( '<label>%s</label>', __( 'in', 'learnpress' ) ),
293 + $html_categories
294 + );
295 + }
296 + $section_bottom = apply_filters(
297 + 'learn-press/layout/list-courses/item/section/bottom',
298 + [
299 + 'wrapper' => '<div class="course-content">',
300 + 'title' => sprintf(
301 + '<h3 class="wap-course-title"><a class="course-permalink" href="%s">%s</a></h3>',
302 + esc_url( $course->get_permalink() ),
303 + $singleCourseTemplate->html_title( $course )
304 + ),
305 + 'featured' => $singleCourseTemplate->html_featured( $course ),
306 + 'wrapper_instructor_cate' => '<div class="course-instructor-category">',
307 + 'instructor' => sprintf(
308 + '<div>%s %s</div>',
309 + sprintf( '<label>%s</label>', __( 'by', 'learnpress' ) ),
310 + $singleCourseTemplate->html_instructor( $course )
311 + ),
312 + 'category' => $html_categories,
313 + 'wrapper_instructor_cate_end' => '</div>',
314 + 'meta' => $html_meta_data,
315 + 'info' => Template::combine_components( $section_bottom_end ),
316 + 'wrapper_end' => '</div>',
317 + ],
318 + $course,
319 + $settings
320 + );
321 +
322 + $section = apply_filters(
323 + 'learn-press/layout/list-courses/item-li',
324 + [
325 + 'wrapper_li' => '<li class="course">',
326 + 'wrapper_div' => sprintf( '<div class="course-item" data-id="%s">', esc_attr( $course->get_id() ) ),
327 + 'top' => Template::combine_components( $section_top ),
328 + 'bottom' => Template::combine_components( $section_bottom ),
329 + 'wrapper_div_end' => '</div>',
330 + 'wrapper_li_end' => '</li>',
331 + ],
332 + $course,
333 + $settings
334 + );
335 +
336 + // For old themes use old hook.
337 + $section = self::fix_theme_course_old( $section, $course, $settings );
338 + // End.
339 +
340 + $html_item = Template::combine_components( $section );
341 + // End new layout course item.
342 + } catch ( Throwable $e ) {
343 + $html_item = $e->getMessage();
344 + }
345 +
346 + return $html_item;
347 + }
348 +
349 + /**
350 + * Button Load more
351 + *
352 + * @return string
353 + * @since 4.2.3.3
354 + * @version 1.0.0
355 + */
356 + public function html_pagination_load_more(): string {
357 + $html_wrapper = [
358 + '<button class="courses-btn-load-more learn-press-pagination lp-button courses-btn-load-more-no-css">' => '</button>',
359 + ];
360 + $content = sprintf(
361 + '%s<span class="lp-loading-circle lp-loading-no-css hide"></span>',
362 + __( 'Load more', 'learnpress' )
363 + );
364 +
365 + return Template::instance()->nest_elements( $html_wrapper, $content );
366 + }
367 +
368 + /**
369 + * Button infinite
370 + *
371 + * @return string
372 + * @since 4.2.3.3
373 + * @version 1.0.0
374 + */
375 + public function html_pagination_infinite(): string {
376 + $html_wrapper = [
377 + '<div class="courses-load-infinite-no-css courses-load-infinite learn-press-pagination">' => '</div>',
378 + ];
379 + $content = '<span class="lp-loading-circle lp-loading-no-css hide"></span>';
380 +
381 + return Template::instance()->nest_elements( $html_wrapper, $content );
382 + }
383 +
384 + /**
385 + * Pagination number
386 + *
387 + * @param array $data
388 + *
389 + * @return string
390 + * @since 4.2.3.3
391 + * @version 1.0.0
392 + */
393 + public function html_pagination_number( array $data = [] ): string {
394 + if ( empty( $data['total_pages'] ) || $data['total_pages'] <= 1 ) {
395 + return '';
396 + }
397 +
398 + $html_wrapper = $data['wrapper'] ?? [
399 + '<nav class="learn-press-pagination navigation pagination">' => '</nav>',
400 + ];
401 +
402 + $pagination = paginate_links(
403 + apply_filters(
404 + 'learn_press_pagination_args',
405 + array(
406 + 'base' => $data['base'] ?? '',
407 + 'format' => '',
408 + 'add_args' => '',
409 + 'current' => max( 1, $data['paged'] ?? 1 ),
410 + 'total' => $data[ 'total_pages' ?? 1 ],
411 + 'prev_text' => '<i class="lp-icon-arrow-left"></i>',
412 + 'next_text' => '<i class="lp-icon-arrow-right"></i>',
413 + 'type' => 'list',
414 + 'end_size' => 3,
415 + 'mid_size' => 3,
416 + )
417 + )
418 + );
419 +
420 + return Template::instance()->nest_elements( $html_wrapper, $pagination );
421 + }
422 +
423 + /**
424 + * Pagination
425 + *
426 + * @param array $data
427 + *
428 + * @return string
429 + * @since 4.2.3.3
430 + * @version 1.0.0
431 + */
432 + public function html_pagination( array $data = [] ): string {
433 + if ( empty( $data['total_pages'] ) || $data['total_pages'] <= 1 ) {
434 + return '';
435 + }
436 +
437 + $pagination_type = $data['type'] ?? 'number';
438 + switch ( $pagination_type ) {
439 + case 'load-more':
440 + if ( $data['paged'] >= $data['total_pages'] ) {
441 + return '';
442 + }
443 +
444 + return $this->html_pagination_load_more();
445 + case 'infinite':
446 + if ( $data['paged'] >= $data['total_pages'] ) {
447 + return '';
448 + }
449 +
450 + return $this->html_pagination_infinite();
451 + default:
452 + return $this->html_pagination_number( $data );
453 + }
454 + }
455 +
456 + /**
457 + * Pagination
458 + *
459 + * @param array $data
460 + *
461 + * @return string
462 + * @since 4.2.3.3
463 + * @version 1.0.1
464 + */
465 + public function html_courses_page_result( array $data = [] ): string {
466 + $html = '';
467 +
468 + try {
469 + $total_rows = $data['total_rows'] ?? 0;
470 + $paged = $data['paged'] ?? 1;
471 + $course_per_page = $data['courses_per_page'] ?? 8;
472 + $pagination_type = $data['pagination_type'] ?? 'number';
473 +
474 + $from = 1 + ( $paged - 1 ) * $course_per_page;
475 + $to = ( $paged * $course_per_page > $total_rows ) ? $total_rows : $paged * $course_per_page;
476 +
477 + $content = '';
478 + if ( 0 === $total_rows ) {
479 +
480 + } elseif ( 1 === $total_rows ) {
481 + $content = esc_html__( 'Showing only one result', 'learnpress' );
482 + } else {
483 + $from_to = $from . '-' . $to;
484 + // For pagination type is load-more or infinite.
485 + if ( $pagination_type !== 'number' ) {
486 + $from_to = '1 - ' . $to;
487 + }
488 + $content = sprintf( esc_html__( 'Showing %1$s of %2$s results', 'learnpress' ), $from_to, $total_rows );
489 + }
490 +
491 + if ( ! empty( $content ) ) {
492 + $html = '<span class="courses-page-result">' . $content . '</span>';
493 + }
494 + } catch ( Throwable $e ) {
495 + error_log( $e->getMessage() );
496 + }
497 +
498 + return $html;
499 + }
500 +
501 + /**
502 + * Layouts type
503 + *
504 + * @param array $data
505 + *
506 + * @return string
507 + * @since 4.2.3.3
508 + * @version 1.0.0
509 + */
510 + public function html_layout_type( array $data = [] ): string {
511 + $html_wrapper = [
512 + '<div class="courses-layouts-display">' => '</div>',
513 + ];
514 +
515 + $ico_grid_default = LP_WP_Filesystem::get_icon_svg( 'ico-grid.svg' );
516 + $ico_list_default = LP_WP_Filesystem::get_icon_svg( 'ico-list.svg' );
517 +
518 + $layouts = [
519 + 'list' => $data['courses_ico_list'] ?? $ico_list_default,
520 + 'grid' => $data['courses_ico_grid'] ?? $ico_grid_default,
521 + ];
522 +
523 + $content = '<ul class="courses-layouts-display-list">';
524 + foreach ( $layouts as $k => $v ) {
525 + $active = ( $data['courses_layout_default'] ?? '' ) === $k ? 'active' : '';
526 + $content .= sprintf(
527 + '<li class="courses-layout %s" data-layout="%s">%s</li>',
528 + esc_attr( $active ),
529 + esc_attr( $k ),
530 + $v
531 + );
532 + }
533 + $content .= '</ul>';
534 +
535 + return Template::instance()->nest_elements( $html_wrapper, $content );
536 + }
537 +
538 + /**
539 + * Order by
540 + *
541 + * @param string $default_value
542 + *
543 + * @return string
544 + * @since 4.2.3.2
545 + * @version 1.0.1
546 + */
547 + public function html_order_by( string $default_value = 'post_date' ): string {
548 + $html_wrapper = [
549 + '<div class="courses-order-by-wrapper">' => '</div>',
550 + ];
551 +
552 + $values = apply_filters(
553 + 'learn-press/courses/order-by/values',
554 + [
555 + 'post_date' => esc_html__( 'Newly published', 'learnpress' ),
556 + 'post_title' => esc_html__( 'Title a-z', 'learnpress' ),
557 + 'post_title_desc' => esc_html__( 'Title z-a', 'learnpress' ),
558 + 'price' => esc_html__( 'Price high to low', 'learnpress' ),
559 + 'price_low' => esc_html__( 'Price low to high', 'learnpress' ),
560 + 'popular' => esc_html__( 'Popular', 'learnpress' ),
561 + ]
562 + );
563 +
564 + $content = '<select name="order_by" class="courses-order-by">';
565 + foreach ( $values as $k => $v ) {
566 + $content .= sprintf(
567 + '<option value="%s" %s>%s</option>',
568 + esc_attr( $k ),
569 + selected( $default_value, $k, false ),
570 + $v
571 + );
572 + }
573 + $content .= '</select>';
574 +
575 + return Template::instance()->nest_elements( $html_wrapper, $content );
576 + }
577 +
578 + public function html_search_form( array $data = [] ) {
579 + $s = $data['c_search'] ?? '';
580 + $class = $data['class'] ?? 'search-courses';
581 + ob_start();
582 + ?>
583 + <form class="<?php echo esc_attr( $class ); ?>" method="get"
584 + action="<?php echo esc_url_raw( learn_press_get_page_link( 'courses' ) ); ?>">
585 + <input type="search" placeholder="<?php esc_attr_e( 'Search courses...', 'learnpress' ); ?>"
586 + name="c_search"
587 + value="<?php echo esc_attr( $s ); ?>">
588 + <button type="submit" name="lp-btn-search-courses"><i class="lp-icon-search"></i></button>
589 + </form>
590 + <?php
591 + return ob_get_clean();
592 + }
593 +
594 + /**
595 + * Html switch layout.
596 + *
597 + * @return string
598 + */
599 + public function switch_layout(): string {
600 + $layouts = learn_press_courses_layouts();
601 + $active = learn_press_get_courses_layout();
602 +
603 + $html_layouts = '';
604 + foreach ( $layouts as $layout => $value ) {
605 + $html_layouts .= sprintf(
606 + '<input type="radio" name="lp-switch-layout-btn" value="%s" id="lp-switch-layout-btn-%s" %s>
607 + <label class="switch-btn %s" title="%s" for="lp-switch-layout-btn-%s"></label>',
608 + esc_attr( $layout ),
609 + esc_attr( $layout ),
610 + checked( $layout, $active, false ),
611 + esc_attr( $layout ),
612 + sprintf( esc_attr__( 'Switch to %s', 'learnpress' ), $value ),
613 + esc_attr( $layout )
614 + );
615 + }
616 +
617 + $section = apply_filters(
618 + 'learn-press/layout/list-courses/section/switch-layout',
619 + [
620 + 'wrapper' => '<div class="switch-layout">',
621 + 'layouts' => $html_layouts,
622 + 'wrapper_end' => '</div>',
623 + ]
624 + );
625 +
626 + return Template::combine_components( $section );
627 + }
628 +
629 + /**
630 + * Layout course search suggest result.
631 + *
632 + * @param array $data
633 + *
634 + * @return void
635 + * @since 4.2.3.2
636 + * @version 1.0.1
637 + */
638 + public function sections_course_suggest( array $data = [] ) {
639 + $content = '';
640 + $singleCourseTemplate = SingleCourseTemplate::instance();
641 +
642 + ob_start();
643 + try {
644 + $courses = $data['courses'] ?? [];
645 + $key_search = $data['keyword'] ?? '';
646 + $total_courses = $data['total_course'] ?? 0;
647 +
648 + // HTML section list courses.
649 + $html_list_course = '';
650 + foreach ( $courses as $courseObj ) {
651 + if ( ! is_object( $courseObj ) ) {
652 + continue;
653 + }
654 + $course_id = $courseObj->ID;
655 + $courseModel = CourseModel::find( $course_id, true );
656 + if ( ! $courseModel ) {
657 + continue;
658 + }
659 +
660 + $section_item = apply_filters(
661 + 'learn-press/course-suggest/item/sections',
662 + [
663 + 'wrapper' => '<li class="item-course-suggest">',
664 + 'course_image' => $singleCourseTemplate->html_image( $courseModel ),
665 + 'course_title' => sprintf(
666 + '<a href="%s">%s</a>',
667 + esc_url_raw( $courseModel->get_permalink() ),
668 + $singleCourseTemplate->html_title( $courseModel )
669 + ),
670 + 'wrapper_end' => '</li>',
671 + ],
672 + $courseModel,
673 + $key_search,
674 + $data
675 + );
676 + $html_list_course .= Template::combine_components( $section_item );
677 + }
678 +
679 + $count_courses = sprintf(
680 + '%s %s',
681 + $total_courses,
682 + _n( 'Course Found', 'Courses Found', $total_courses, 'learnpress' )
683 + );
684 + $view_all = sprintf(
685 + '<a href="%s">%s</a>',
686 + esc_url( add_query_arg(
687 + 'c_search',
688 + $key_search,
689 + learn_press_get_page_link( 'courses' ) ) ),
690 + __( 'View All', 'learnpress' )
691 + );
692 +
693 + // Section.
694 + $section = [
695 + 'wrapper' => '<ul class="lp-courses-suggest-list">',
696 + 'courses' => $html_list_course,
697 + 'wrapper_end' => '</ul>',
698 + 'wrapper_info' => '<div class="lp-courses-suggest-info">',
699 + 'count' => $count_courses,
700 + 'view_all' => $view_all,
701 + 'wrapper_info_end' => '</div>',
702 + ];
703 + $content = Template::combine_components( $section );
704 + echo $content;
705 + } catch ( Throwable $e ) {
706 + Template::print_message( $e->getMessage(), 'error' );
707 + }
708 + }
709 +
710 + /**
711 + * Sidebar
712 + *
713 + * @return void
714 + * @version 1.0.0
715 + * @since 4.2.3.2
716 + */
717 + public function sidebar() {
718 + try {
719 + if ( is_active_sidebar( 'archive-courses-sidebar' ) ) {
720 + $html_wrapper = [
721 + '<div class="lp-archive-courses-sidebar">' => '</div>',
722 + ];
723 +
724 + ob_start();
725 + dynamic_sidebar( 'archive-courses-sidebar' );
726 + echo Template::instance()->nest_elements( $html_wrapper, ob_get_clean() );
727 + }
728 + } catch ( Throwable $e ) {
729 + error_log( $e->getMessage() );
730 + }
731 + }
732 +
733 + /**
734 + * Show total course free by Category.
735 + * @return string
736 + * @version 1.0.0
737 + *
738 + * @since 4.2.5.4
739 + */
740 + public function html_count_course_free(): string {
741 + $html_wrapper = [
742 + '<div class="courses-count-free">' => '</div>',
743 + ];
744 +
745 + $category_current = 0;
746 + $category_current_slug = get_query_var( 'term' );
747 + if ( ! empty( $category_current_slug ) ) {
748 + $category_current_obj = get_term_by( 'slug', $category_current_slug, LP_COURSE_CATEGORY_TAX );
749 + if ( $category_current_obj instanceof WP_Term ) {
750 + $category_current = $category_current_obj->term_id;
751 + }
752 + }
753 +
754 + $filter = new LP_Course_Filter();
755 + if ( ! empty( $category_current ) ) {
756 + $filter->term_ids = [ $category_current ];
757 + }
758 +
759 + $count = Courses::count_course_free( $filter );
760 + $content = sprintf(
761 + '<span class="courses-count-free-number">%1$s</span> %2$s',
762 + $count,
763 + _n( 'Free Course', 'Free Courses', $count, 'learnpress' )
764 + );
765 +
766 + return Template::instance()->nest_elements( $html_wrapper, $content );
767 + }
768 +
769 + /**
770 + * Show total students on Course Category.
771 + *
772 + * @return string
773 + * @since 4.2.5.4
774 + * @version 1.0.0
775 + */
776 + public function html_count_students(): string {
777 + $html_wrapper = [
778 + '<div class="courses-count-students">' => '</div>',
779 + ];
780 +
781 + $category_current = 0;
782 + $category_current_slug = get_query_var( 'term' );
783 + if ( ! empty( $category_current_slug ) ) {
784 + $category_current_obj = get_term_by( 'slug', $category_current_slug, LP_COURSE_CATEGORY_TAX );
785 + if ( $category_current_obj instanceof WP_Term ) {
786 + $category_current = $category_current_obj->term_id;
787 + }
788 + }
789 +
790 + $lp_user_items_db = LP_User_Items_DB::getInstance();
791 + $filter = new LP_User_Items_Filter();
792 +
793 + // If page is course category, get total students of this category.
794 + if ( ! empty( $category_current ) ) {
795 + $filter->join[] = "INNER JOIN {$lp_user_items_db->tb_posts} AS p ON ui.item_id = p.ID";
796 + $filter->join[] = "INNER JOIN {$lp_user_items_db->tb_term_relationships} AS r_term ON ui.item_id = r_term.object_id";
797 + $filter->where[] = $lp_user_items_db->wpdb->prepare( 'AND r_term.term_taxonomy_id = %d', $category_current );
798 + }
799 +
800 + $count = UserCourseModel::count_students( $filter );
801 + $content = sprintf(
802 + '<span class="courses-count-students-number">%1$s</span> %2$s',
803 + $count,
804 + _n( 'Student', 'Students', $count, 'learnpress' )
805 + );
806 +
807 + return Template::instance()->nest_elements( $html_wrapper, $content );
808 + }
809 +
810 + /************************** Hook old *****************************/
811 +
812 + /**
813 + * Fix theme course-builder use old hook.
814 + *
815 + * @param array $section
816 + *
817 + * @return array
818 + */
819 + public static function fix_theme_cb_hook_courses( $section, $courses, $settings ) {
820 + /*$theme_name = wp_get_theme()->get( 'Name' );
821 + if ( 'Course Builder' !== $theme_name ) {
822 + return $section;
823 + }*/
824 +
825 + $section_top = apply_filters(
826 + 'learn-press/list-courses/layout/section/top',
827 + [],
828 + $courses,
829 + $settings
830 + );
831 +
832 + $section_new = [];
833 + if ( ! empty( $section_top ) ) {
834 + foreach ( $section_top as $k => $v ) {
835 + $section_new[ $k ] = $v['text_html'] ?? '';
836 + }
837 +
838 + return $section_new;
839 + }
840 +
841 + return $section;
842 + }
843 +
844 + /**
845 + * Fix theme course-builder use old hook.
846 + *
847 + * @param array $section
848 + *
849 + * @return array
850 + */
851 + public static function fix_theme_course_old( $section, $course, $settings ) {
852 + /*$theme_name = wp_get_theme()->get( 'Name' );
853 + if ( 'Course Builder' !== $theme_name ) {
854 + return $section;
855 + }*/
856 +
857 + $course = learn_press_get_course( $course->get_id() );
858 +
859 + $wrapper = apply_filters(
860 + 'learn-press/list-courses/layout/item/wrapper',
861 + [],
862 + $course,
863 + $settings
864 + );
865 + if ( ! empty( $wrapper ) ) {
866 + $i = 0;
867 + foreach ( $wrapper as $k => $v ) {
868 + if ( $i === 0 ) {
869 + $section['wrapper_li'] = $k;
870 + $section['wrapper_li_end'] = $v;
871 + } elseif ( $i === 1 ) {
872 + $section['wrapper_div'] = $k;
873 + $section['wrapper_div_end'] = $v;
874 + break;
875 + }
876 +
877 + ++$i;
878 + }
879 + }
880 +
881 + $section_item = apply_filters(
882 + 'learn-press/list-courses/layout/item/section',
883 + [],
884 + $course,
885 + $settings
886 + );
887 + if ( ! empty( $section_item ) ) {
888 + $section['top'] = $section_item['thim_top']['text_html'] ?? '';
889 + }
890 +
891 + $section_bottom = apply_filters(
892 + 'learn-press/list-courses/layout/item/section/bottom',
893 + [],
894 + $course,
895 + $settings
896 + );
897 + $section_bottom_new = [];
898 + if ( ! empty( $section_bottom ) ) {
899 + foreach ( $section_bottom as $k => $v ) {
900 + $section_bottom_new[ $k ] = $v['text_html'] ?? '';
901 + }
902 +
903 + $section['bottom'] = Template::combine_components( $section_bottom_new );
904 + }
905 +
906 + return $section;
907 + }
908 +
909 + /**
910 + * Render string to data content
911 + *
912 + * @param array $data
913 + * @param string $data_content
914 + *
915 + * @return string
916 + */
917 + public function render_data( array $data, string $data_content ): string {
918 + return str_replace(
919 + [
920 + '{{courses_order_by}}',
921 + '{{courses_layout_type}}',
922 + '{{courses_pagination}}',
923 + '{{courses_page_result}}',
924 + ],
925 + [
926 + $this->html_order_by( $data['order_by'] ?? '' ),
927 + $this->html_layout_type( $data ),
928 + $this->html_pagination( $data['pagination'] ?? [] ),
929 + $this->html_courses_page_result( $data ),
930 + ],
931 + $data_content
932 + );
933 + }
934 +}