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/course/abstract-course.php +1474 -1474 4.3.84.4.8 View file →
@@ -1,1474 +1,1474 @@
1 -<?php
2 -/**
3 - * Class LP_Abstract_Course.
4 - *
5 - * @author ThimPress
6 - * @package LearnPress/Classes
7 - * @version 4.0.0
8 - */
9 -
10 -use LearnPress\Models\CourseModel;
11 -use LearnPress\TemplateHooks\Course\SingleCourseTemplate;
12 -use LearnPress\TemplateHooks\Instructor\SingleInstructorTemplate;
13 -
14 -defined( 'ABSPATH' ) || exit();
15 -
16 -if ( ! function_exists( 'LP_Abstract_Course' ) ) {
17 -
18 - /**
19 - * Class LP_Abstract_Course.
20 - */
21 - abstract class LP_Abstract_Course extends LP_Abstract_Post_Data {
22 - /**
23 - * Course type
24 - *
25 - * @var string .
26 - */
27 - public $course_type = null;
28 -
29 - /**
30 - * @var LP_Course_CURD|null
31 - */
32 - protected $_curd = null;
33 -
34 - /**
35 - * Post type
36 - *
37 - * @var string
38 - */
39 - protected $_post_type = LP_COURSE_CPT;
40 -
41 - /**
42 - * @var array
43 - */
44 - protected $_data = array(
45 - 'status' => '',
46 - 'require_enrollment' => '',
47 - 'price' => '',
48 - 'regular_price' => '',
49 - 'sale_price' => '',
50 - 'sale_start' => '',
51 - 'sale_end' => '',
52 - 'duration' => 0,
53 - 'max_students' => 0,
54 - 'students' => 0,
55 - 'retake_count' => 0,
56 - 'featured' => '',
57 - 'course_result' => '',
58 - 'passing_conditional' => '',
59 - 'external_link' => '',
60 - 'payment' => '',
61 - );
62 -
63 - protected $_loaded = false;
64 -
65 - /**
66 - * @var int
67 - */
68 - protected $user_id = 0;
69 -
70 - public function set_user( $user ) {
71 - if ( is_numeric( $user ) ) {
72 - $this->user_id = absint( $user );
73 - } elseif ( $user instanceof LP_User ) {
74 - $this->user_id = $user->get_id();
75 - }
76 - }
77 -
78 - public function get_user( $return = 'id' ) {
79 - if ( $return === 'id' ) {
80 - return $this->user_id;
81 - }
82 -
83 - if ( $this->user_id ) {
84 - return learn_press_get_user( $this->user_id );
85 - }
86 -
87 - return false;
88 - }
89 -
90 - /**
91 - * Constructor gets the post object and sets the ID for the loaded course.
92 - *
93 - * @param mixed $the_course Course ID, post object, or course object
94 - * @param mixed $deprecated Deprecated
95 - */
96 - public function __construct( $the_course, $deprecated = '' ) {
97 -
98 - $this->_curd = new LP_Course_CURD();
99 -
100 - if ( is_numeric( $the_course ) && $the_course > 0 ) {
101 - $this->set_id( $the_course );
102 - } elseif ( $the_course instanceof self ) {
103 - $this->set_id( absint( $the_course->get_id() ) );
104 - } elseif ( ! empty( $the_course->ID ) ) {
105 - $this->set_id( absint( $the_course->ID ) );
106 - }
107 -
108 - if ( $this->get_id() > 0 ) {
109 -
110 - }
111 - }
112 -
113 - public function refresh() {
114 - $this->_loaded = false;
115 - if ( $this->get_id() > 0 ) {
116 - $this->load();
117 - }
118 - }
119 -
120 - /**
121 - * Read course data.
122 - * - Curriculum: sections, items, etc...
123 - *
124 - * Todo: optimize this function
125 - *
126 - * @since 3.0.0
127 - * @editor tungnx
128 - * @version 1.0.1
129 - */
130 - public function load() {
131 - if ( $this->_loaded ) {
132 - return;
133 - }
134 -
135 - $this->load_data();
136 -
137 - /*$can_load_curriculum = false;
138 - // Check if edit course, single course, single item can be load
139 - if ( in_array( LP_Page_Controller::page_current(), array( LP_PAGE_SINGLE_COURSE, LP_PAGE_SINGLE_COURSE_CURRICULUM ) ) ) {
140 - $can_load_curriculum = true;
141 - } elseif ( is_admin() && is_callable( 'get_current_screen' ) ) {
142 - $current_screen = get_current_screen();
143 - if ( $current_screen && LP_COURSE_CPT === $current_screen->id ) {
144 - $can_load_curriculum = true;
145 - }
146 - } elseif ( defined( 'REST_REQUEST' ) && REST_REQUEST ) {
147 - if ( isset( $_REQUEST['sectionID'] ) || isset( $_REQUEST['sectionId'] ) ) {
148 - $can_load_curriculum = true;
149 - }
150 - }
151 -
152 - if ( $can_load_curriculum ) {
153 - $this->load_curriculum();
154 - }*/
155 -
156 - $this->_loaded = true;
157 - }
158 -
159 - public function load_data() {
160 - $id = $this->get_id();
161 - $post_object = get_post( $id );
162 -
163 - // Regular price
164 - $regular_price = get_post_meta( $id, '_lp_price', true ); // For LP version < 1.4.1.2
165 - if ( metadata_exists( 'post', $this->get_id(), '_lp_regular_price' ) ) {
166 - $regular_price = get_post_meta( $id, '_lp_regular_price', true );
167 - }
168 -
169 - $block_course_finished = get_post_meta(
170 - $id,
171 - '_lp_block_finished',
172 - true
173 - );
174 -
175 - $this->_set_data(
176 - array(
177 - 'status' => $post_object->post_status,
178 - 'no_required_enroll' => get_post_meta( $id, '_lp_no_required_enroll', true ),
179 - 'price' => get_post_meta( $id, '_lp_price', true ),
180 - 'regular_price' => $regular_price,
181 - 'sale_price' => get_post_meta( $id, '_lp_sale_price', true ),
182 - 'sale_start' => get_post_meta( $id, '_lp_sale_start', true ),
183 - 'sale_end' => get_post_meta( $id, '_lp_sale_end', true ),
184 - 'duration' => get_post_meta( $id, '_lp_duration', true ),
185 - 'max_students' => get_post_meta( $id, '_lp_max_students', true ),
186 - 'students' => false,
187 - 'fake_students' => get_post_meta( $id, '_lp_students', true ),
188 - 'retake_count' => get_post_meta( $id, '_lp_retake_count', true ),
189 - 'featured' => get_post_meta( $id, '_lp_featured', true ),
190 - 'course_result' => get_post_meta( $id, '_lp_course_result', true ),
191 - 'passing_condition' => get_post_meta( $id, '_lp_passing_condition', true ),
192 - 'final_quiz' => get_post_meta( $id, '_lp_final_quiz', true ),
193 - 'external_link' => get_post_meta( $id, '_lp_external_link_buy_course', true ),
194 - 'block_course_duration_expire' => get_post_meta(
195 - $id,
196 - '_lp_block_expire_duration',
197 - true
198 - ),
199 - 'block_course_finished' => $block_course_finished ? $block_course_finished : 'yes',
200 - 'allow_repurchase' => get_post_meta( $id, '_lp_allow_course_repurchase', true ),
201 - 'allow_repurchase_course_option' => get_post_meta( $id, '_lp_course_repurchase_option', true ),
202 - 'excerpt' => $post_object->post_excerpt,
203 - )
204 - );
205 - }
206 -
207 - /**
208 - * Load course curriculum.
209 - *
210 - * @deprecated 4.1.6.9
211 - */
212 - public function load_curriculum() {
213 - _deprecated_function( __FUNCTION__, '4.1.6.9' );
214 - /*$item_ids = array();
215 - $item_types = array();
216 - $item_by_types = array();
217 - $section_items = array();
218 -
219 - $items = $this->_curd->read_course_curriculum( $this->get_id() );
220 - if ( $items ) {
221 - foreach ( $items as $item ) {
222 - $item_ids[] = $item->id;
223 -
224 - // Group items by it type
225 - if ( empty( $item_types[ $item->type ] ) ) {
226 - $item_types[ $item->type ] = array();
227 - }
228 -
229 - $item_types[ $item->type ][] = $item->id;
230 -
231 - // Group items by it section
232 - if ( empty( $section_items[ $item->section_id ] ) ) {
233 - $section_items[ $item->section_id ] = array();
234 - }
235 -
236 - $section_items[ $item->section_id ][] = $item->id;
237 - $item_by_types[ $item->id ] = $item->type;
238 -
239 - }
240 - }
241 -
242 - LP_Course_Utils::set_course_items( $this->get_id(), $item_ids );
243 - LP_Course_Utils::set_course_item_types( $this->get_id(), $item_by_types );
244 - LP_Course_Utils::set_course_items_group_types( $this->get_id(), $item_types );
245 -
246 - foreach ( $section_items as $section_id => $its ) {
247 - LP_Course_Utils::set_section_items( $section_id, $its );
248 - }*/
249 - }
250 -
251 - /**
252 - * __isset function.
253 - *
254 - * @param mixed $key
255 - *
256 - * @return bool
257 - */
258 - public function __isset( $key ) {
259 - return metadata_exists( 'post', $this->get_id(), '_' . $key );
260 - }
261 -
262 - /**
263 - * __get function.
264 - *
265 - * @param string $key
266 - *
267 - * @return mixed
268 - */
269 - public function __get( $key ) {
270 - _deprecated_argument( __CLASS__ . '::' . $key, '3.0.11' );
271 -
272 - return false;
273 - }
274 -
275 - /**
276 - * Get course thumbnail, return placeholder if it does not exists
277 - *
278 - * @param string $size
279 - * @param array $attr
280 - *
281 - * @return string
282 - */
283 - public function get_image( string $size = 'course_thumbnail', array $attr = array() ): string {
284 - $image = LP_Thumbnail_Helper::instance()->get_course_image( $this->get_id(), $size, $attr );
285 -
286 - return apply_filters( 'learn-press/course/image', $image, $this->get_id(), $size, $attr );
287 - }
288 -
289 - /**
290 - * Get course thumbnail, return placeholder if it does not exists
291 - *
292 - * @param string $size
293 - *
294 - * @return string
295 - */
296 - public function get_image_url( $size = 'course_thumbnail' ) {
297 - $course_id = $this->get_id();
298 - $url = '';
299 - $parent_id = wp_get_post_parent_id( $course_id );
300 -
301 - if ( has_post_thumbnail( $course_id ) ) {
302 - $url = get_the_post_thumbnail_url( $course_id, $size );
303 - } elseif ( $parent_id && has_post_thumbnail( $parent_id ) ) {
304 - $url = get_the_post_thumbnail_url( $parent_id, $size );
305 - }
306 -
307 - if ( ! $url ) {
308 - $url = LearnPress::instance()->image( 'no-image.png' );
309 - }
310 -
311 - return apply_filters( 'learn-press/course-thumbnail-url', $url, $this->get_id(), $size );
312 - }
313 -
314 - /**
315 - * @return false|string
316 - */
317 - public function get_permalink() {
318 - return get_the_permalink( $this->get_id() );
319 - }
320 -
321 - /**
322 - * Course is exists if the post is not empty
323 - *
324 - * @return bool
325 - */
326 - public function exists() {
327 - return LP_COURSE_CPT === learn_press_get_post_type( $this->get_id() );
328 - }
329 -
330 - /**
331 - * @return bool
332 - */
333 - public function is_publish() {
334 - return 'publish' === get_post_status( $this->get_id() );
335 - }
336 -
337 - /**
338 - * Check this course is required enroll.
339 - *
340 - * @return bool
341 - */
342 - public function is_required_enroll(): bool {
343 - return ! $this->is_no_required_enroll();
344 - }
345 -
346 - /**
347 - * @deprecated 4.2.0
348 - */
349 - public function is_require_enrollment() {
350 - _deprecated_function( __METHOD__, '4.2.0' );
351 - return ! $this->is_no_required_enroll();
352 - }
353 -
354 - /**
355 - * Check if this course is required enroll or not.
356 - *
357 - * @author hungkv
358 - * @since 4.0.5
359 - * @return bool
360 - * @version 1.0.1
361 - */
362 - public function is_no_required_enroll(): bool {
363 - return $this->get_data( 'no_required_enroll', 'no' ) === 'yes' && ! is_user_logged_in();
364 - }
365 -
366 - /**
367 - * @deprecated 4.1.6.9
368 - */
369 - /*public function get_item_types( $group = false ) {
370 - $cache_key = $group ? 'course-item-group-types' : 'course-item-types';
371 - $items = LP_Object_Cache::get( 'course-' . $this->get_id(), "learn-press/{$cache_key}" );
372 - $items = false;
373 -
374 - if ( false === $items ) {
375 - $item_types = array();
376 - $items = array();
377 - $sections = array();
378 - $all_items = $this->_curd->read_course_items( $this->get_id() );
379 -
380 - if ( $all_items ) {
381 - foreach ( $all_items as $item ) {
382 - if ( empty( $item_types[ $item->type ] ) ) {
383 - $item_types[ $item->type ] = array();
384 - }
385 - $item_types[ $item->type ][] = $item->id;
386 - $items[ $item->id ] = $item->type;
387 -
388 - if ( empty( $sections[ $item->section_id ] ) ) {
389 - $sections[ $item->section_id ] = array();
390 - }
391 - $sections[ $item->section_id ][] = $item->id;
392 - }
393 - }
394 -
395 - LP_Object_Cache::set( 'course-' . $this->get_id(), $item_types, 'learn-press/course-item-group-types' );
396 - LP_Object_Cache::set( 'course-' . $this->get_id(), $items, 'learn-press/course-item-types' );
397 -
398 - foreach ( $sections as $section_id => $section_items ) {
399 - LP_Object_Cache::set( 'section-' . $section_id, $section_items, 'learn-press/section-items' );
400 - }
401 -
402 - $items = $group ? $item_types : $items;
403 - }
404 -
405 - return apply_filters( "learn-press/{$cache_key}", $items, $this->get_id() );
406 - }*/
407 -
408 - /**
409 - * Get all item's ids in a course.
410 - *
411 - * @param int $section_id
412 - *
413 - * @return array
414 - * @version 4.0.1
415 - * @modify 4.1.6.9 tungnx
416 - */
417 - public function get_item_ids( int $section_id = 0 ): array {
418 - $item_ids = array();
419 -
420 - $sections_items = $this->get_full_sections_and_items_course();
421 - foreach ( $sections_items as $section_items ) {
422 - foreach ( $section_items->items as $item ) {
423 - if ( $section_id ) {
424 - if ( $section_id == $section_items->id ) {
425 - $item_ids[] = $item->id;
426 - }
427 -
428 - continue;
429 - }
430 -
431 - $item_ids[] = $item->id;
432 - }
433 - }
434 -
435 - return apply_filters( 'learn-press/course-item-ids', $item_ids, $this->get_id() );
436 - }
437 -
438 - /**
439 - * Get item is viewing in single course.
440 - *
441 - * @return LP_Course_Item
442 - * @deprecated 4.1.6.9
443 - */
444 - public function get_viewing_item() {
445 - _deprecated_function( __FUNCTION__, '4.1.6.9' );
446 - //return apply_filters( 'learn-press/single-course-viewing-item', $this->_viewing_item, $this->get_id() );
447 - }
448 -
449 - /**
450 - * Get total students fake.
451 - *
452 - * @return int
453 - */
454 - public function get_fake_students(): int {
455 - return absint( $this->get_data( 'fake_students', 0 ) );
456 - }
457 -
458 - /**
459 - * Count the real users has enrolled
460 - *
461 - * @return int
462 - */
463 - public function get_users_enrolled() {
464 - $enrolled = $this->get_data( 'students' );
465 -
466 - if ( false === $enrolled ) {
467 - $enrolled = $this->count_students();
468 -
469 - $this->_set_data( 'students', $enrolled );
470 - }
471 -
472 - $enrolled = absint( $enrolled );
473 -
474 - return apply_filters( 'learn-press/course/users-enrolled', $enrolled, $this );
475 - }
476 -
477 - /**
478 - * @param string $field
479 - *
480 - * @return LP_User|mixed
481 - */
482 - public function get_instructor( $field = '' ) {
483 - $user = learn_press_get_user( get_post_field( 'post_author', $this->get_id() ) );
484 -
485 - return $field ? $user->get_data( $field ) : $user;
486 - }
487 -
488 - /**
489 - * @return mixed
490 - */
491 - public function get_instructor_name() {
492 - $instructor = $this->get_instructor();
493 - $name = '';
494 -
495 - if ( $instructor ) {
496 - if ( $instructor->get_data( 'display_name' ) ) {
497 - $name = $instructor->get_data( 'display_name' );
498 - } elseif ( $instructor->get_data( 'user_nicename' ) ) {
499 - $name = $instructor->get_data( 'user_nicename' );
500 - } elseif ( $instructor->get_data( 'user_login' ) ) {
501 - $name = $instructor->get_data( 'user_login' );
502 - }
503 - }
504 -
505 - return apply_filters( 'learn-press/course/instructor-name', $name, $this->get_id() );
506 - }
507 -
508 - /**
509 - * Get instructor html of course.
510 - *
511 - * @param int|bool $with_avatar
512 - * @param string $link_class
513 - *
514 - * @return string
515 - */
516 - public function get_instructor_html( $with_avatar = false, $link_class = '' ): string {
517 - $html = '';
518 -
519 - try {
520 - $instructor = $this->get_author();
521 - if ( ! $instructor ) {
522 - return '';
523 - }
524 -
525 - $singleInstructorTemplate = SingleInstructorTemplate::instance();
526 -
527 - $html = apply_filters(
528 - 'learn-press/course/instructor-html',
529 - sprintf(
530 - '<a href="%s"%s>%s %s</a>',
531 - $instructor->get_url_instructor(),
532 - $link_class ? sprintf( 'class="%s"', $link_class ) : '',
533 - $with_avatar ? $instructor->get_profile_picture() : '',
534 - $singleInstructorTemplate->html_display_name( $instructor )
535 - ),
536 - $instructor,
537 - $singleInstructorTemplate
538 - );
539 - } catch ( Throwable $e ) {
540 - error_log( $e->getMessage() );
541 - }
542 -
543 - return $html;
544 - }
545 -
546 - /**
547 - * Check if a course is Free
548 - *
549 - * @return bool
550 - */
551 - public function is_free(): bool {
552 - return apply_filters( 'learn-press/course/is-free', $this->get_price() == 0, $this->get_id() );
553 - }
554 -
555 - /**
556 - * Get the sale price of course. Check if sale price is set
557 - * and the dates are valid.
558 - *
559 - * @return string|float
560 - */
561 - public function get_sale_price() {
562 - $sale_price_value = $this->get_data( 'sale_price', '' );
563 -
564 - if ( '' !== $sale_price_value ) {
565 - return apply_filters( 'learn-press/course/sale-price', floatval( $sale_price_value ), $this->get_id() );
566 - }
567 -
568 - return $sale_price_value;
569 - }
570 -
571 - /**
572 - * Check course has 'sale price'
573 - *
574 - * @return mixed
575 - */
576 - public function has_sale_price() {
577 - $has_sale_price = false;
578 - $regular_price = $this->get_regular_price();
579 - $sale_price = $this->get_sale_price();
580 - $start_date = $this->get_data( 'sale_start', '' );
581 - $end_date = $this->get_data( 'sale_end', '' );
582 -
583 - if ( $regular_price > $sale_price && is_float( $sale_price ) ) {
584 - $has_sale_price = true;
585 - }
586 -
587 - // Check in days sale
588 - if ( $has_sale_price && '' !== $start_date && '' !== $end_date ) {
589 - $nowObj = new LP_Datetime();
590 - // Compare via timezone WP
591 - $nowStr = $nowObj->toSql( true );
592 - $now = strtotime( $nowStr );
593 - $end = strtotime( $end_date );
594 - $start = strtotime( $start_date );
595 -
596 - $has_sale_price = $now >= $start && $now <= $end;
597 - }
598 -
599 - return apply_filters( 'learn-press/course/has-sale-price', $has_sale_price, $this->get_id() );
600 - }
601 -
602 - /**
603 - * Get the regular price of course.
604 - *
605 - * @return float
606 - */
607 - public function get_regular_price(): float {
608 - $price = floatval( $this->get_data( 'regular_price', 0 ) );
609 -
610 - return apply_filters( 'learn-press/course/regular-price', $price, $this->get_id() );
611 - }
612 -
613 - /**
614 - * Get the regular price format of course.
615 - *
616 - * @since 4.1.5
617 - * @version 1.0.0
618 - * @author tungnx
619 - * @return mixed
620 - */
621 - public function get_regular_price_html() {
622 - $price = learn_press_format_price( $this->get_regular_price(), true );
623 -
624 - return apply_filters( 'learn-press/course/regular-price-html', $price, $this->get_id() );
625 - }
626 -
627 - /**
628 - * Get the price of course.
629 - *
630 - * @return mixed
631 - */
632 - public function get_price() {
633 - $key_cache = "{$this->get_id()}/price";
634 - $price = LP_Course_Cache::cache_load_first( 'get', $key_cache );
635 -
636 - if ( false === $price ) {
637 - if ( $this->has_sale_price() ) {
638 - $price = $this->get_sale_price();
639 - // Add key _lp_course_is_sale for query - Todo: Check performance, need write function get all courses, and set on Admin, on background
640 - //update_post_meta( $this->get_id(), '_lp_course_is_sale', 1 );
641 - } else {
642 - // Delete key _lp_course_is_sale
643 - //delete_post_meta( $this->get_id(), '_lp_course_is_sale' );
644 - $price = $this->get_regular_price();
645 - }
646 -
647 - // Save price only on page Single Course
648 - /*if ( LP_PAGE_SINGLE_COURSE === LP_Page_Controller::page_current() ) {
649 - update_post_meta( $this->get_id(), '_lp_price', $price );
650 - }*/
651 -
652 - LP_Course_Cache::cache_load_first( 'set', $key_cache, $price );
653 - }
654 -
655 - return apply_filters( 'learn-press/course/price', $price, $this->get_id() );
656 - }
657 -
658 - /**
659 - * Get html course price
660 - *
661 - * @author tungnx
662 - * @since 4.1.5
663 - * @version 1.0.2
664 - * @return string
665 - */
666 - public function get_course_price_html(): string {
667 - $price_html = '';
668 -
669 - if ( $this->is_free() ) {
670 - if ( is_float( $this->get_sale_price() ) ) {
671 - $price_html .= sprintf( '<span class="origin-price">%s</span>', $this->get_regular_price_html() );
672 - }
673 -
674 - $price_html .= sprintf( '<span class="free">%s</span>', esc_html__( 'Free', 'learnpress' ) );
675 - $price_html = apply_filters( 'learn_press_course_price_html_free', $price_html, $this );
676 - } elseif ( $this->get_data( 'no_required_enroll', 'no' ) === 'yes' ) {
677 - $price_html .= '';
678 - } else {
679 - if ( $this->has_sale_price() ) {
680 - $price_html .= sprintf( '<span class="origin-price">%s</span>', $this->get_regular_price_html() );
681 - }
682 -
683 - $price_html .= sprintf( '<span class="price">%s</span>', learn_press_format_price( $this->get_price(), true ) );
684 - $course = CourseModel::find( $this->get_id(), true );
685 - $singleCourseTemplate = SingleCourseTemplate::instance();
686 - $price_html = sprintf(
687 - '%1$s %2$s %3$s',
688 - $singleCourseTemplate->html_price_prefix( $course ),
689 - $price_html,
690 - $singleCourseTemplate->html_price_suffix( $course )
691 - );
692 - $price_html = apply_filters( 'learn_press_course_price_html', $price_html, $this->has_sale_price(), $this->get_id() );
693 - }
694 -
695 - return sprintf( '<span class="course-item-price">%s</span>', $price_html );
696 - }
697 -
698 - /**
699 - * Get the price of course with html
700 - *
701 - * @editor tungnx
702 - * @modify 4.1.5
703 - * @version 1.0.1
704 - * @return mixed
705 - * @deprecated 4.1.5
706 - */
707 - public function get_price_html() {
708 - $price_html = '';
709 -
710 - if ( $this->is_free() ) {
711 - $price_html = apply_filters(
712 - 'learn_press_course_price_html_free',
713 - esc_html__( 'Free', 'learnpress' ),
714 - $this
715 - );
716 - } else {
717 - $price_html .= learn_press_format_price( $this->get_price() );
718 - $price_html = apply_filters( 'learn_press_course_price_html', $price_html, $this->has_sale_price(), $this->get_id() );
719 - }
720 -
721 - return $price_html;
722 - }
723 -
724 - /**
725 - * Get the origin price of course
726 - *
727 - * @return float
728 - * @deprecated 4.1.5
729 - */
730 - public function get_origin_price() {
731 - return $this->get_regular_price();
732 - }
733 -
734 - /**
735 - * Get the price of course with html
736 - *
737 - * @return mixed
738 - * @deprecated 4.1.5
739 - */
740 - public function get_origin_price_html() {
741 - return $this->get_regular_price_html();
742 - }
743 -
744 - /**
745 - * @param bool $item_id
746 - *
747 - * @return bool|mixed
748 - */
749 - public function is_viewing_item( $item_id = false ) {
750 - $item = LP_Global::course_item();
751 -
752 - if ( empty( $item ) ) {
753 - return false;
754 - }
755 -
756 - return apply_filters(
757 - 'learn-press/is-viewing-item',
758 - false !== $item_id ? $item_id == $item->get_id() : $item->get_id(),
759 - $item_id,
760 - $this->get_id()
761 - );
762 - }
763 -
764 - /**
765 - * @param $item_id
766 - *
767 - * @return bool|mixed
768 - */
769 - public function is_current_item( $item_id ) {
770 - return $this->is_viewing_item( $item_id );
771 - }
772 -
773 - /**
774 - * Check if the course has 'feature'
775 - * This function call to a function with prefix 'has'
776 - *
777 - * @param string
778 - *
779 - * @return mixed
780 - * @throws Exception
781 - */
782 - public function has( $tag ) {
783 - _deprecated_function( __FUNCTION__, '3.0.8' );
784 -
785 - $args = func_get_args();
786 - unset( $args[0] );
787 - $method = 'has_' . preg_replace( '!-!', '_', $tag );
788 - $callback = array( $this, $method );
789 -
790 - if ( is_callable( $callback ) ) {
791 - return call_user_func_array( $callback, $args );
792 - } else {
793 - throw new Exception( sprintf( __( 'The function %s doesn\'t exist', 'learnpress' ), $tag ) );
794 - }
795 - }
796 -
797 - /**
798 - * @param string
799 - *
800 - * @return mixed
801 - * @throws Exception
802 - */
803 - public function is( $tag ) {
804 - _deprecated_function( __FUNCTION__, '3.0.8' );
805 - $args = func_get_args();
806 - unset( $args[0] );
807 - $method = 'is_' . preg_replace( '!-!', '_', $tag );
808 - $callback = array( $this, $method );
809 -
810 - if ( is_callable( $callback ) ) {
811 - return call_user_func_array( $callback, $args );
812 - } else {
813 - throw new Exception( sprintf( __( 'The function %s doesn\'t exist', 'learnpress' ), $tag ) );
814 - }
815 - }
816 -
817 - /**
818 - * Return true if this course can be purchasable.
819 - * Required enroll.
820 - * Status is publish.
821 - *
822 - * @return mixed
823 - */
824 - public function is_purchasable() {
825 - $is_purchasable = $this->exists() && ! $this->is_no_required_enroll() && get_post_status( $this->get_id() ) == 'publish';
826 -
827 - return apply_filters( 'learn-press/is-purchasable', $is_purchasable, $this->get_id() );
828 - }
829 -
830 - /**
831 - * Check if students have purchased course is reached.
832 - * For case check course can purchase.
833 - *
834 - * @return mixed
835 - * @since 3.0.0
836 - * @version 1.0.1
837 - */
838 - public function is_in_stock() {
839 - $in_stock = true;
840 - $max_allowed = $this->get_max_students();
841 -
842 - if ( $max_allowed ) {
843 - $in_stock = $max_allowed > $this->get_total_user_enrolled_or_purchased();
844 - }
845 -
846 - return apply_filters( 'learn-press/is-in-stock', $in_stock, $this->get_id() );
847 - }
848 -
849 - /**
850 - * Check max student can enroll.
851 - * For case check course can enroll.
852 - *
853 - * @return mixed
854 - * @since 4.2.5.7
855 - * @version 1.0.0
856 - */
857 - public function is_in_stock_enroll() {
858 - $in_stock = true;
859 - $max_allowed = $this->get_max_students();
860 -
861 - if ( $max_allowed ) {
862 - $in_stock = $max_allowed > $this->get_total_user_enrolled_or_purchased();
863 - }
864 -
865 - return apply_filters( 'learn-press/is-in-stock', $in_stock, $this->get_id() );
866 - }
867 -
868 - /**
869 - * Get max students can enroll to course.
870 - *
871 - * @return int
872 - *
873 - * @since 3.0.0
874 - */
875 - public function get_max_students() {
876 - return apply_filters(
877 - 'learn-press/max-students',
878 - absint( $this->get_data( 'max_students' ) ),
879 - $this->get_id()
880 - );
881 - }
882 -
883 - /**
884 - * Count number of students enrolled course.
885 - * Check global settings `enrolled_students_number`
886 - * and add the fake value if both are set.
887 - *
888 - * @return int
889 - * @editor tungnx
890 - * @version 1.0.1
891 - * @since 3.0.0
892 - * @Todo: view and rewrite this function
893 - */
894 - public function count_students(): int {
895 - $total = $this->get_total_user_enrolled_or_purchased();
896 - $total += $this->get_fake_students();
897 -
898 - return $total;
899 - }
900 -
901 - /**
902 - * Get total user enrolled
903 - *
904 - * @since 4.1.4
905 - * @version 1.0.1
906 - * @return int
907 - */
908 - public function get_total_user_enrolled(): int {
909 - $total = 0;
910 - $lp_course_cache = new LP_Course_Cache( true );
911 -
912 - try {
913 - $total = $lp_course_cache->get_total_students_enrolled( $this->get_id() );
914 - if ( false !== $total ) {
915 - return $total;
916 - }
917 -
918 - $lp_course_db = LP_Course_DB::getInstance();
919 - $total = $lp_course_db->get_total_user_enrolled( $this->get_id() );
920 - $lp_course_cache->set_total_students_enrolled( $this->get_id(), $total );
921 - } catch ( Throwable $e ) {
922 - error_log( $e->getMessage() );
923 - }
924 -
925 - return $total;
926 - }
927 -
928 - /**
929 - * Get total user enrolled include fake
930 - * @since 4.2.2
931 - * @return int
932 - */
933 - /*public function get_total_user_enrolled_include_fake() {
934 - $total_user_enrolled = 0;
935 - $key_cache = "{$this->get_id()}/total-students-include-fake";
936 -
937 - try {
938 - $total_user_enrolled = LP_Course_Cache::cache_load_first( 'get', $key_cache );
939 -
940 - if ( false === $total_user_enrolled ) {
941 - $lp_course_db = LP_Course_DB::getInstance();
942 - $total_user_enrolled = $lp_course_db->get_total_user_enrolled( $this->get_id() );
943 - $total_user_enrolled += $this->get_fake_students();
944 -
945 - LP_Course_Cache::cache_load_first( 'set', $key_cache, $total_user_enrolled );
946 - }
947 - } catch ( Throwable $e ) {
948 - error_log( $e->getMessage() );
949 - }
950 -
951 - return $total_user_enrolled;
952 - }*/
953 -
954 - /**
955 - * Get total user enrolled, purchased or finished
956 - *
957 - * @since 4.1.4
958 - * @version 1.0.1
959 - * @return int
960 - */
961 - public function get_total_user_enrolled_or_purchased(): int {
962 - $total = 0;
963 - $lp_course_cache = new LP_Course_Cache( true );
964 -
965 - try {
966 - $total = $lp_course_cache->get_total_students_enrolled_or_purchased( $this->get_id() );
967 - if ( false !== $total ) {
968 - return $total;
969 - }
970 -
971 - $lp_course_db = LP_Course_DB::getInstance();
972 - $total = $lp_course_db->get_total_user_enrolled_or_purchased( $this->get_id() );
973 - $lp_course_cache->set_total_students_enrolled_or_purchased( $this->get_id(), $total );
974 - } catch ( Throwable $e ) {
975 - error_log( $e->getMessage() );
976 - }
977 -
978 - return $total;
979 - }
980 -
981 - /**
982 - * @param string|array $statuses
983 - *
984 - * @return mixed
985 - */
986 - public function count_in_order( $statuses = 'completed' ) {
987 - settype( $statuses, 'array' );
988 - $count = 0;
989 -
990 - foreach ( $statuses as $status ) {
991 - $orders = get_post_meta( $this->get_id(), 'order-' . $status, true );
992 -
993 - if ( $orders ) {
994 - $count += sizeof( $orders );
995 - }
996 - }
997 -
998 - return $count;
999 - }
1000 -
1001 - /**
1002 - * @editor tungnx
1003 - * @modify 4.1.4 - comment - not use
1004 - */
1005 - /*public function count_completed_orders() {
1006 - $orders = $this->get_meta( 'order-completed' );
1007 -
1008 - if ( $orders ) {
1009 - $count = sizeof( $orders );
1010 - } else {
1011 - $count = 0;
1012 - }
1013 -
1014 - return $count;
1015 - }*/
1016 -
1017 - /**
1018 - * Check if course contain an item in curriculum.
1019 - * Actually, find the item in each section inside curriculum.
1020 - *
1021 - * @param $item_id
1022 - *
1023 - * @return bool
1024 - */
1025 - public function has_item( $item_id ) {
1026 - $found = false;
1027 - $items = $this->get_item_ids();
1028 -
1029 - if ( $items ) {
1030 - $found = in_array( $item_id, $items );
1031 - }
1032 -
1033 - return apply_filters( 'learn-press/course-has-item', $found, $item_id, $this->get_id() );
1034 - }
1035 -
1036 - /**
1037 - * Get course's item (lesson/quiz/etc...).
1038 - *
1039 - * @param int $item_id Course's item Id.
1040 - *
1041 - * @return LP_Lesson|LP_Quiz|boolean
1042 - */
1043 - public function get_item( $item_id ) {
1044 - $item = LP_Course_Item::get_item( $item_id );
1045 - if ( $item instanceof LP_Course_Item ) {
1046 - $item->set_course( $this->get_id() );
1047 - }
1048 -
1049 - return apply_filters( 'learn-press/course-item', $item, $item_id, $this->get_id() );
1050 - }
1051 -
1052 - /**
1053 - * Get course passing condition value.
1054 - *
1055 - * @param bool $format
1056 - * @param string $context
1057 - *
1058 - * @return array|mixed|string
1059 - */
1060 - public function get_passing_condition( $format = false ) {
1061 - $value = absint( $this->get_data( 'passing_condition' ) );
1062 -
1063 - if ( $format ) {
1064 - $value = "{$value}%";
1065 - }
1066 -
1067 - return apply_filters(
1068 - 'learn-press/course-passing-condition',
1069 - $value,
1070 - $format,
1071 - $this->get_id()
1072 - );
1073 - }
1074 -
1075 - /**
1076 - * Get item's link
1077 - *
1078 - * @param int $item_id
1079 - *
1080 - * @editor tungnx
1081 - * @since 3.0.0
1082 - * @version 1.0.1
1083 - * @return string
1084 - */
1085 - public function get_item_link( int $item_id ): string {
1086 - $item_link = '';
1087 - $item_type = get_post_type( $item_id );
1088 -
1089 - $course_permalink = trailingslashit( $this->get_permalink() );
1090 - $item_slug = get_post_field( 'post_name', $item_id );
1091 -
1092 - $slug_prefixes = apply_filters(
1093 - 'learn-press/course/custom-item-prefixes',
1094 - array(
1095 - LP_QUIZ_CPT => sanitize_title_with_dashes( LP_Settings::get_option( 'quiz_slug', 'quizzes' ) ),
1096 - LP_LESSON_CPT => sanitize_title_with_dashes( LP_Settings::get_option( 'lesson_slug', 'lessons' ) ),
1097 - ),
1098 - $this->get_id()
1099 - );
1100 -
1101 - $slug_prefix = trailingslashit( $slug_prefixes[ $item_type ] ?? '' );
1102 - $item_link = trailingslashit( $course_permalink . $slug_prefix . $item_slug );
1103 -
1104 - return apply_filters( 'learn-press/course/item-link', $item_link, $item_id, $this );
1105 - }
1106 -
1107 - /**
1108 - * Get course's item at a position.
1109 - *
1110 - * @param int $at
1111 - *
1112 - * @return bool|mixed
1113 - * @editor tungnx
1114 - * @modify 4.1.3 - comment - not use
1115 - */
1116 - /*public function get_item_at( $at ) {
1117 - $items = $this->get_items();
1118 -
1119 - if ( ! $items ) {
1120 - return false;
1121 - }
1122 -
1123 - return ! empty( $items[ $at ] ) ? $items[ $at ] : false;
1124 - }*/
1125 -
1126 - /**
1127 - * Get position of an item in course curriculum.
1128 - *
1129 - * @param LP_Course_Item|LP_User_Item|int $item
1130 - *
1131 - * @return mixed
1132 - * @deprecated 4.1.6.9
1133 - */
1134 - /*public function get_item_position( $item ) {
1135 - $items = $this->get_items();
1136 -
1137 - if ( ! $items ) {
1138 - return false;
1139 - }
1140 -
1141 - $item_id = is_a( $item, 'LP_User_Item' ) || is_a(
1142 - $item,
1143 - 'LP_Course_Item'
1144 - ) ? $item->get_id() : absint( $item );
1145 -
1146 - return array_search( $item_id, $items );
1147 - }*/
1148 -
1149 - /**
1150 - * @return bool|mixed
1151 - */
1152 - public function get_current_item() {
1153 - return $this->is_viewing_item();
1154 - }
1155 -
1156 - /**
1157 - * Get item standing after the item is viewing.
1158 - *
1159 - * @param array $args
1160 - *
1161 - * @return int
1162 - */
1163 - public function get_next_item( $args = null ) {
1164 - $item_nav = $this->get_item_nav();
1165 -
1166 - return apply_filters( 'learn-press/course/next-item', $item_nav ? $item_nav[2] : 0, $this->get_id(), $args );
1167 - }
1168 -
1169 - /**
1170 - * Get item standing before the item is viewing.
1171 - *
1172 - * @param array $args
1173 - *
1174 - * @return int
1175 - */
1176 - public function get_prev_item( $args = null ) {
1177 - $item_nav = $this->get_item_nav();
1178 - if ( ! is_array( $item_nav ) || empty( $item_nav ) ) {
1179 - return 0;
1180 - }
1181 -
1182 - return apply_filters( 'learn-press/course/prev-item', $item_nav[0], $this->get_id(), $args );
1183 - }
1184 -
1185 - /**
1186 - * Get item standing before and after an item.
1187 - * If the item is not passed consider it is item viewing.
1188 - *
1189 - * @param bool $current_item
1190 - * @param bool $viewable - Optional. TRUE will get next item is viewable.
1191 - *
1192 - * @return array|bool
1193 - * @since 3.1.0
1194 - */
1195 - public function get_item_nav( $current_item = false, $viewable = false ) {
1196 - if ( false === $current_item ) {
1197 - $current_item = $this->get_current_item();
1198 - }
1199 -
1200 - if ( false === $current_item ) {
1201 - return false;
1202 - }
1203 -
1204 - $prev_id = $next_id = 0;
1205 - $item_ids = $this->get_item_ids();
1206 -
1207 - if ( $item_ids ) {
1208 - $pos = array_search( $current_item, $item_ids );
1209 -
1210 - if ( false !== $pos ) {
1211 - $max = sizeof( $item_ids ) - 1;
1212 - $user = learn_press_get_current_user();
1213 - $pos_tmp = $pos;
1214 -
1215 - while ( $pos_tmp < $max ) {
1216 - ++$pos_tmp;
1217 -
1218 - if ( ! $viewable ) {
1219 - $next_id = $item_ids[ $pos_tmp ];
1220 -
1221 - break;
1222 - }
1223 - }
1224 -
1225 - $pos_tmp = $pos;
1226 -
1227 - while ( $pos_tmp > 0 ) {
1228 - --$pos_tmp;
1229 -
1230 - if ( ! $viewable ) {
1231 - $prev_id = $item_ids[ $pos_tmp ];
1232 -
1233 - break;
1234 - }
1235 - }
1236 - }
1237 - }
1238 -
1239 - return array( $prev_id, $current_item, $next_id );
1240 - }
1241 -
1242 - /**
1243 - * Get link of item is standing after the item is viewing.
1244 - *
1245 - * @param array $args
1246 - *
1247 - * @return string
1248 - */
1249 - public function get_next_item_html( $args = null ) {
1250 - $args = wp_parse_args(
1251 - $args,
1252 - array(
1253 - 'current_item' => false,
1254 - 'viewable' => null,
1255 - 'dir' => 'next',
1256 - )
1257 - );
1258 -
1259 - $next_item = $this->get_next_item( $args );
1260 -
1261 - if ( $next_item ) {
1262 - ob_start();
1263 -
1264 - learn_press_get_template(
1265 - 'content-lesson/next-button.php',
1266 - array(
1267 - 'item' => $next_item,
1268 - 'course' => $this,
1269 - )
1270 - );
1271 -
1272 - return ob_get_clean();
1273 - }
1274 -
1275 - return false;
1276 - }
1277 -
1278 - public function get_prev_item_html( $args = null ) {
1279 - }
1280 -
1281 - public function get_preview_items() {
1282 - return LP_Object_Cache::get( 'course-' . $this->get_id(), 'learn-press/course-preview-items' );
1283 - }
1284 -
1285 - /**
1286 - * Check a quiz is a final quiz in this course
1287 - *
1288 - * @param $quiz_id
1289 - *
1290 - * @return mixed
1291 - */
1292 - public function is_final_quiz( $quiz_id ) {
1293 - return apply_filters( 'learn_press_is_final_quiz', $this->get_final_quiz() == $quiz_id, $quiz_id, $this->get_id() );
1294 - }
1295 -
1296 - public function get_final_quiz() {
1297 - $final_quiz = $this->get_data( 'final_quiz' );
1298 -
1299 - return apply_filters( 'learn-press/course-final-quiz', $final_quiz, $this->get_id() );
1300 - }
1301 -
1302 - public function set_final_quiz( $id ) {
1303 - $this->_set_data( 'final_quiz', $id );
1304 - }
1305 -
1306 - /**
1307 - * Get course duration in seconds
1308 - *
1309 - * @return int
1310 - */
1311 - public function get_duration() {
1312 - /**
1313 - * Duration is in string such as 10 week, 4 hour, etc...
1314 - * So we can use strtotime('+10 week') to convert it to seconds
1315 - */
1316 - return strtotime( '+' . $this->get_data( 'duration' ), 0 );
1317 - }
1318 -
1319 -
1320 - /**
1321 - * Get course remaining time message
1322 - *
1323 - * @param $user_id
1324 - *
1325 - * @return string
1326 - */
1327 - public function get_user_duration_html( $user_id = 0 ) {
1328 - if ( ! $user_id ) {
1329 - $user_id = get_current_user_id();
1330 - }
1331 -
1332 - $duration = $this->get_duration();
1333 - $user = learn_press_get_user( $user_id );
1334 - $course_info = $user->get_course_info( $this->get_id() );
1335 - $html = '';
1336 -
1337 - if ( $course_info ) {
1338 - $now = current_time( 'timestamp' );
1339 - $start_time = intval( strtotime( $course_info['start'] ) );
1340 -
1341 - if ( $start_time + $duration > $now ) {
1342 - $remain = $start_time + $duration - $now;
1343 - $remain = learn_press_seconds_to_weeks( $remain );
1344 - $html = sprintf( __( 'This course will end within the next %s', 'learnpress' ), $remain );
1345 - }
1346 - }
1347 -
1348 - return $html;
1349 - }
1350 -
1351 - /**
1352 - * Output params for single course page
1353 - *
1354 - * @param null $args
1355 - *
1356 - * @return mixed
1357 - */
1358 - public function output_args( $args = null ) {
1359 - return array();
1360 - }
1361 -
1362 - /**
1363 - * Get external link of "Buy this course" button
1364 - *
1365 - * @return mixed
1366 - */
1367 - public function get_external_link() {
1368 - return apply_filters( 'learn-press/course-external-link', $this->get_data( 'external_link', '' ), $this->get_id() );
1369 - }
1370 -
1371 - public function get_external_link_text() {
1372 - return apply_filters( 'learn-press/course-external-link-text', _x( 'More Info', 'External Link button text', 'learnpress' ), $this->get_id() );
1373 - }
1374 -
1375 - /**
1376 - * Get main author of course.
1377 - *
1378 - * @param string $field
1379 - *
1380 - * @return LP_User|int
1381 - */
1382 - public function get_author( string $field = '' ) {
1383 - $author_id = absint( get_post_field( 'post_author', $this->get_id() ) );
1384 -
1385 - return strtolower( $field ) === 'id' ? $author_id : learn_press_get_user( $author_id );
1386 - }
1387 -
1388 - /**
1389 - * Get author's display name
1390 - *
1391 - * @return string
1392 - * @since 3.0.9
1393 - */
1394 - public function get_author_display_name() {
1395 - $display_name = '';
1396 - $user = $this->get_author();
1397 -
1398 - if ( $user ) {
1399 - $display_name = $user->get_display_name();
1400 - }
1401 -
1402 - return $display_name;
1403 - }
1404 -
1405 - /**
1406 - * @return mixed
1407 - */
1408 - public function get_tags() {
1409 - return apply_filters( 'learn-press/course-tags', get_the_term_list( $this->get_id(), 'course_tag', __( 'Tags: ', 'learnpress' ), ' ', '' ) );
1410 - }
1411 -
1412 - /**
1413 - * Get extra info of course.
1414 - * Target Audience, Key Features, Requirements, etc...
1415 - *
1416 - * @param string $type [target_audience, key_features, requirements]
1417 - *
1418 - * @return string|array
1419 - * @since 3.x.x
1420 - */
1421 - public function get_extra_info( $type ) {
1422 - $extra_info_meta = get_post_meta( $this->get_id(), '_lp_' . $type, true );
1423 -
1424 - return apply_filters( 'learn-press/course-extra-info', $extra_info_meta, $type, $this->get_id() );
1425 - }
1426 -
1427 - /**
1428 - * Get FAQs in Tab metabox.
1429 - *
1430 - * @return array
1431 - * @since 4.0.0
1432 - */
1433 - public function get_faqs(): array {
1434 - $faqs = array();
1435 - $data = get_post_meta( $this->get_id(), '_lp_faqs', true );
1436 -
1437 - if ( $data ) {
1438 - foreach ( $data as $faq ) {
1439 - $faqs[] = array(
1440 - 'question' => $faq[0],
1441 - 'answer' => $faq[1],
1442 - );
1443 - }
1444 - }
1445 -
1446 - return apply_filters( 'learn-press/course-faqs', $faqs, $this->get_id() );
1447 - }
1448 -
1449 - /**
1450 - * Get course is set featured
1451 - *
1452 - * @return bool
1453 - */
1454 - public function is_featured(): bool {
1455 - return apply_filters(
1456 - 'learn-press/course-is-featured',
1457 - get_post_meta( $this->get_id(), '_lp_featured', true ) === 'yes',
1458 - $this->get_id(),
1459 - $this
1460 - );
1461 - }
1462 -
1463 - /**
1464 - * [get_downloadable_material get all material files of this course and lesson of this course]
1465 - * @return [array] [array of material files or empty array]
1466 - * @deprecated 4.2.6.4
1467 - */
1468 - /*public function get_downloadable_material(): array {
1469 - $material = LP_Material_Files_DB::getInstance();
1470 - $materials = $material->get_material_by_item_id( $this->get_id(), 1 );
1471 - return apply_filters( 'learn-press/course-materials', $materials, $this->get_id() );
1472 - }*/
1473 - }
1474 -}
1 +<?php
2 +/**
3 + * Class LP_Abstract_Course.
4 + *
5 + * @author ThimPress
6 + * @package LearnPress/Classes
7 + * @version 4.0.0
8 + */
9 +
10 +use LearnPress\Models\CourseModel;
11 +use LearnPress\TemplateHooks\Course\SingleCourseTemplate;
12 +use LearnPress\TemplateHooks\Instructor\SingleInstructorTemplate;
13 +
14 +defined( 'ABSPATH' ) || exit();
15 +
16 +if ( ! function_exists( 'LP_Abstract_Course' ) ) {
17 +
18 + /**
19 + * Class LP_Abstract_Course.
20 + */
21 + abstract class LP_Abstract_Course extends LP_Abstract_Post_Data {
22 + /**
23 + * Course type
24 + *
25 + * @var string .
26 + */
27 + public $course_type = null;
28 +
29 + /**
30 + * @var LP_Course_CURD|null
31 + */
32 + protected $_curd = null;
33 +
34 + /**
35 + * Post type
36 + *
37 + * @var string
38 + */
39 + protected $_post_type = LP_COURSE_CPT;
40 +
41 + /**
42 + * @var array
43 + */
44 + protected $_data = array(
45 + 'status' => '',
46 + 'require_enrollment' => '',
47 + 'price' => '',
48 + 'regular_price' => '',
49 + 'sale_price' => '',
50 + 'sale_start' => '',
51 + 'sale_end' => '',
52 + 'duration' => 0,
53 + 'max_students' => 0,
54 + 'students' => 0,
55 + 'retake_count' => 0,
56 + 'featured' => '',
57 + 'course_result' => '',
58 + 'passing_conditional' => '',
59 + 'external_link' => '',
60 + 'payment' => '',
61 + );
62 +
63 + protected $_loaded = false;
64 +
65 + /**
66 + * @var int
67 + */
68 + protected $user_id = 0;
69 +
70 + public function set_user( $user ) {
71 + if ( is_numeric( $user ) ) {
72 + $this->user_id = absint( $user );
73 + } elseif ( $user instanceof LP_User ) {
74 + $this->user_id = $user->get_id();
75 + }
76 + }
77 +
78 + public function get_user( $return = 'id' ) {
79 + if ( $return === 'id' ) {
80 + return $this->user_id;
81 + }
82 +
83 + if ( $this->user_id ) {
84 + return learn_press_get_user( $this->user_id );
85 + }
86 +
87 + return false;
88 + }
89 +
90 + /**
91 + * Constructor gets the post object and sets the ID for the loaded course.
92 + *
93 + * @param mixed $the_course Course ID, post object, or course object
94 + * @param mixed $deprecated Deprecated
95 + */
96 + public function __construct( $the_course, $deprecated = '' ) {
97 +
98 + $this->_curd = new LP_Course_CURD();
99 +
100 + if ( is_numeric( $the_course ) && $the_course > 0 ) {
101 + $this->set_id( $the_course );
102 + } elseif ( $the_course instanceof self ) {
103 + $this->set_id( absint( $the_course->get_id() ) );
104 + } elseif ( ! empty( $the_course->ID ) ) {
105 + $this->set_id( absint( $the_course->ID ) );
106 + }
107 +
108 + if ( $this->get_id() > 0 ) {
109 +
110 + }
111 + }
112 +
113 + public function refresh() {
114 + $this->_loaded = false;
115 + if ( $this->get_id() > 0 ) {
116 + $this->load();
117 + }
118 + }
119 +
120 + /**
121 + * Read course data.
122 + * - Curriculum: sections, items, etc...
123 + *
124 + * Todo: optimize this function
125 + *
126 + * @since 3.0.0
127 + * @editor tungnx
128 + * @version 1.0.1
129 + */
130 + public function load() {
131 + if ( $this->_loaded ) {
132 + return;
133 + }
134 +
135 + $this->load_data();
136 +
137 + /*$can_load_curriculum = false;
138 + // Check if edit course, single course, single item can be load
139 + if ( in_array( LP_Page_Controller::page_current(), array( LP_PAGE_SINGLE_COURSE, LP_PAGE_SINGLE_COURSE_CURRICULUM ) ) ) {
140 + $can_load_curriculum = true;
141 + } elseif ( is_admin() && is_callable( 'get_current_screen' ) ) {
142 + $current_screen = get_current_screen();
143 + if ( $current_screen && LP_COURSE_CPT === $current_screen->id ) {
144 + $can_load_curriculum = true;
145 + }
146 + } elseif ( defined( 'REST_REQUEST' ) && REST_REQUEST ) {
147 + if ( isset( $_REQUEST['sectionID'] ) || isset( $_REQUEST['sectionId'] ) ) {
148 + $can_load_curriculum = true;
149 + }
150 + }
151 +
152 + if ( $can_load_curriculum ) {
153 + $this->load_curriculum();
154 + }*/
155 +
156 + $this->_loaded = true;
157 + }
158 +
159 + public function load_data() {
160 + $id = $this->get_id();
161 + $post_object = get_post( $id );
162 +
163 + // Regular price
164 + $regular_price = get_post_meta( $id, '_lp_price', true ); // For LP version < 1.4.1.2
165 + if ( metadata_exists( 'post', $this->get_id(), '_lp_regular_price' ) ) {
166 + $regular_price = get_post_meta( $id, '_lp_regular_price', true );
167 + }
168 +
169 + $block_course_finished = get_post_meta(
170 + $id,
171 + '_lp_block_finished',
172 + true
173 + );
174 +
175 + $this->_set_data(
176 + array(
177 + 'status' => $post_object->post_status,
178 + 'no_required_enroll' => get_post_meta( $id, '_lp_no_required_enroll', true ),
179 + 'price' => get_post_meta( $id, '_lp_price', true ),
180 + 'regular_price' => $regular_price,
181 + 'sale_price' => get_post_meta( $id, '_lp_sale_price', true ),
182 + 'sale_start' => get_post_meta( $id, '_lp_sale_start', true ),
183 + 'sale_end' => get_post_meta( $id, '_lp_sale_end', true ),
184 + 'duration' => get_post_meta( $id, '_lp_duration', true ),
185 + 'max_students' => get_post_meta( $id, '_lp_max_students', true ),
186 + 'students' => false,
187 + 'fake_students' => get_post_meta( $id, '_lp_students', true ),
188 + 'retake_count' => get_post_meta( $id, '_lp_retake_count', true ),
189 + 'featured' => get_post_meta( $id, '_lp_featured', true ),
190 + 'course_result' => get_post_meta( $id, '_lp_course_result', true ),
191 + 'passing_condition' => get_post_meta( $id, '_lp_passing_condition', true ),
192 + 'final_quiz' => get_post_meta( $id, '_lp_final_quiz', true ),
193 + 'external_link' => get_post_meta( $id, '_lp_external_link_buy_course', true ),
194 + 'block_course_duration_expire' => get_post_meta(
195 + $id,
196 + '_lp_block_expire_duration',
197 + true
198 + ),
199 + 'block_course_finished' => $block_course_finished ? $block_course_finished : 'yes',
200 + 'allow_repurchase' => get_post_meta( $id, '_lp_allow_course_repurchase', true ),
201 + 'allow_repurchase_course_option' => get_post_meta( $id, '_lp_course_repurchase_option', true ),
202 + 'excerpt' => $post_object->post_excerpt,
203 + )
204 + );
205 + }
206 +
207 + /**
208 + * Load course curriculum.
209 + *
210 + * @deprecated 4.1.6.9
211 + */
212 + public function load_curriculum() {
213 + _deprecated_function( __FUNCTION__, '4.1.6.9' );
214 + /*$item_ids = array();
215 + $item_types = array();
216 + $item_by_types = array();
217 + $section_items = array();
218 +
219 + $items = $this->_curd->read_course_curriculum( $this->get_id() );
220 + if ( $items ) {
221 + foreach ( $items as $item ) {
222 + $item_ids[] = $item->id;
223 +
224 + // Group items by it type
225 + if ( empty( $item_types[ $item->type ] ) ) {
226 + $item_types[ $item->type ] = array();
227 + }
228 +
229 + $item_types[ $item->type ][] = $item->id;
230 +
231 + // Group items by it section
232 + if ( empty( $section_items[ $item->section_id ] ) ) {
233 + $section_items[ $item->section_id ] = array();
234 + }
235 +
236 + $section_items[ $item->section_id ][] = $item->id;
237 + $item_by_types[ $item->id ] = $item->type;
238 +
239 + }
240 + }
241 +
242 + LP_Course_Utils::set_course_items( $this->get_id(), $item_ids );
243 + LP_Course_Utils::set_course_item_types( $this->get_id(), $item_by_types );
244 + LP_Course_Utils::set_course_items_group_types( $this->get_id(), $item_types );
245 +
246 + foreach ( $section_items as $section_id => $its ) {
247 + LP_Course_Utils::set_section_items( $section_id, $its );
248 + }*/
249 + }
250 +
251 + /**
252 + * __isset function.
253 + *
254 + * @param mixed $key
255 + *
256 + * @return bool
257 + */
258 + public function __isset( $key ) {
259 + return metadata_exists( 'post', $this->get_id(), '_' . $key );
260 + }
261 +
262 + /**
263 + * __get function.
264 + *
265 + * @param string $key
266 + *
267 + * @return mixed
268 + */
269 + public function __get( $key ) {
270 + _deprecated_argument( __CLASS__ . '::' . $key, '3.0.11' );
271 +
272 + return false;
273 + }
274 +
275 + /**
276 + * Get course thumbnail, return placeholder if it does not exists
277 + *
278 + * @param string $size
279 + * @param array $attr
280 + *
281 + * @return string
282 + */
283 + public function get_image( string $size = 'course_thumbnail', array $attr = array() ): string {
284 + $image = LP_Thumbnail_Helper::instance()->get_course_image( $this->get_id(), $size, $attr );
285 +
286 + return apply_filters( 'learn-press/course/image', $image, $this->get_id(), $size, $attr );
287 + }
288 +
289 + /**
290 + * Get course thumbnail, return placeholder if it does not exists
291 + *
292 + * @param string $size
293 + *
294 + * @return string
295 + */
296 + public function get_image_url( $size = 'course_thumbnail' ) {
297 + $course_id = $this->get_id();
298 + $url = '';
299 + $parent_id = wp_get_post_parent_id( $course_id );
300 +
301 + if ( has_post_thumbnail( $course_id ) ) {
302 + $url = get_the_post_thumbnail_url( $course_id, $size );
303 + } elseif ( $parent_id && has_post_thumbnail( $parent_id ) ) {
304 + $url = get_the_post_thumbnail_url( $parent_id, $size );
305 + }
306 +
307 + if ( ! $url ) {
308 + $url = LearnPress::instance()->image( 'no-image.png' );
309 + }
310 +
311 + return apply_filters( 'learn-press/course-thumbnail-url', $url, $this->get_id(), $size );
312 + }
313 +
314 + /**
315 + * @return false|string
316 + */
317 + public function get_permalink() {
318 + return get_the_permalink( $this->get_id() );
319 + }
320 +
321 + /**
322 + * Course is exists if the post is not empty
323 + *
324 + * @return bool
325 + */
326 + public function exists() {
327 + return LP_COURSE_CPT === learn_press_get_post_type( $this->get_id() );
328 + }
329 +
330 + /**
331 + * @return bool
332 + */
333 + public function is_publish() {
334 + return 'publish' === get_post_status( $this->get_id() );
335 + }
336 +
337 + /**
338 + * Check this course is required enroll.
339 + *
340 + * @return bool
341 + */
342 + public function is_required_enroll(): bool {
343 + return ! $this->is_no_required_enroll();
344 + }
345 +
346 + /**
347 + * @deprecated 4.2.0
348 + */
349 + public function is_require_enrollment() {
350 + _deprecated_function( __METHOD__, '4.2.0' );
351 + return ! $this->is_no_required_enroll();
352 + }
353 +
354 + /**
355 + * Check if this course is required enroll or not.
356 + *
357 + * @author hungkv
358 + * @since 4.0.5
359 + * @return bool
360 + * @version 1.0.1
361 + */
362 + public function is_no_required_enroll(): bool {
363 + return $this->get_data( 'no_required_enroll', 'no' ) === 'yes' && ! is_user_logged_in();
364 + }
365 +
366 + /**
367 + * @deprecated 4.1.6.9
368 + */
369 + /*public function get_item_types( $group = false ) {
370 + $cache_key = $group ? 'course-item-group-types' : 'course-item-types';
371 + $items = LP_Object_Cache::get( 'course-' . $this->get_id(), "learn-press/{$cache_key}" );
372 + $items = false;
373 +
374 + if ( false === $items ) {
375 + $item_types = array();
376 + $items = array();
377 + $sections = array();
378 + $all_items = $this->_curd->read_course_items( $this->get_id() );
379 +
380 + if ( $all_items ) {
381 + foreach ( $all_items as $item ) {
382 + if ( empty( $item_types[ $item->type ] ) ) {
383 + $item_types[ $item->type ] = array();
384 + }
385 + $item_types[ $item->type ][] = $item->id;
386 + $items[ $item->id ] = $item->type;
387 +
388 + if ( empty( $sections[ $item->section_id ] ) ) {
389 + $sections[ $item->section_id ] = array();
390 + }
391 + $sections[ $item->section_id ][] = $item->id;
392 + }
393 + }
394 +
395 + LP_Object_Cache::set( 'course-' . $this->get_id(), $item_types, 'learn-press/course-item-group-types' );
396 + LP_Object_Cache::set( 'course-' . $this->get_id(), $items, 'learn-press/course-item-types' );
397 +
398 + foreach ( $sections as $section_id => $section_items ) {
399 + LP_Object_Cache::set( 'section-' . $section_id, $section_items, 'learn-press/section-items' );
400 + }
401 +
402 + $items = $group ? $item_types : $items;
403 + }
404 +
405 + return apply_filters( "learn-press/{$cache_key}", $items, $this->get_id() );
406 + }*/
407 +
408 + /**
409 + * Get all item's ids in a course.
410 + *
411 + * @param int $section_id
412 + *
413 + * @return array
414 + * @version 4.0.1
415 + * @modify 4.1.6.9 tungnx
416 + */
417 + public function get_item_ids( int $section_id = 0 ): array {
418 + $item_ids = array();
419 +
420 + $sections_items = $this->get_full_sections_and_items_course();
421 + foreach ( $sections_items as $section_items ) {
422 + foreach ( $section_items->items as $item ) {
423 + if ( $section_id ) {
424 + if ( $section_id == $section_items->id ) {
425 + $item_ids[] = $item->id;
426 + }
427 +
428 + continue;
429 + }
430 +
431 + $item_ids[] = $item->id;
432 + }
433 + }
434 +
435 + return apply_filters( 'learn-press/course-item-ids', $item_ids, $this->get_id() );
436 + }
437 +
438 + /**
439 + * Get item is viewing in single course.
440 + *
441 + * @return LP_Course_Item
442 + * @deprecated 4.1.6.9
443 + */
444 + public function get_viewing_item() {
445 + _deprecated_function( __FUNCTION__, '4.1.6.9' );
446 + //return apply_filters( 'learn-press/single-course-viewing-item', $this->_viewing_item, $this->get_id() );
447 + }
448 +
449 + /**
450 + * Get total students fake.
451 + *
452 + * @return int
453 + */
454 + public function get_fake_students(): int {
455 + return absint( $this->get_data( 'fake_students', 0 ) );
456 + }
457 +
458 + /**
459 + * Count the real users has enrolled
460 + *
461 + * @return int
462 + */
463 + public function get_users_enrolled() {
464 + $enrolled = $this->get_data( 'students' );
465 +
466 + if ( false === $enrolled ) {
467 + $enrolled = $this->count_students();
468 +
469 + $this->_set_data( 'students', $enrolled );
470 + }
471 +
472 + $enrolled = absint( $enrolled );
473 +
474 + return apply_filters( 'learn-press/course/users-enrolled', $enrolled, $this );
475 + }
476 +
477 + /**
478 + * @param string $field
479 + *
480 + * @return LP_User|mixed
481 + */
482 + public function get_instructor( $field = '' ) {
483 + $user = learn_press_get_user( get_post_field( 'post_author', $this->get_id() ) );
484 +
485 + return $field ? $user->get_data( $field ) : $user;
486 + }
487 +
488 + /**
489 + * @return mixed
490 + */
491 + public function get_instructor_name() {
492 + $instructor = $this->get_instructor();
493 + $name = '';
494 +
495 + if ( $instructor ) {
496 + if ( $instructor->get_data( 'display_name' ) ) {
497 + $name = $instructor->get_data( 'display_name' );
498 + } elseif ( $instructor->get_data( 'user_nicename' ) ) {
499 + $name = $instructor->get_data( 'user_nicename' );
500 + } elseif ( $instructor->get_data( 'user_login' ) ) {
501 + $name = $instructor->get_data( 'user_login' );
502 + }
503 + }
504 +
505 + return apply_filters( 'learn-press/course/instructor-name', $name, $this->get_id() );
506 + }
507 +
508 + /**
509 + * Get instructor html of course.
510 + *
511 + * @param int|bool $with_avatar
512 + * @param string $link_class
513 + *
514 + * @return string
515 + */
516 + public function get_instructor_html( $with_avatar = false, $link_class = '' ): string {
517 + $html = '';
518 +
519 + try {
520 + $instructor = $this->get_author();
521 + if ( ! $instructor ) {
522 + return '';
523 + }
524 +
525 + $singleInstructorTemplate = SingleInstructorTemplate::instance();
526 +
527 + $html = apply_filters(
528 + 'learn-press/course/instructor-html',
529 + sprintf(
530 + '<a href="%s"%s>%s %s</a>',
531 + $instructor->get_url_instructor(),
532 + $link_class ? sprintf( 'class="%s"', $link_class ) : '',
533 + $with_avatar ? $instructor->get_profile_picture() : '',
534 + $singleInstructorTemplate->html_display_name( $instructor )
535 + ),
536 + $instructor,
537 + $singleInstructorTemplate
538 + );
539 + } catch ( Throwable $e ) {
540 + error_log( $e->getMessage() );
541 + }
542 +
543 + return $html;
544 + }
545 +
546 + /**
547 + * Check if a course is Free
548 + *
549 + * @return bool
550 + */
551 + public function is_free(): bool {
552 + return apply_filters( 'learn-press/course/is-free', $this->get_price() == 0, $this->get_id() );
553 + }
554 +
555 + /**
556 + * Get the sale price of course. Check if sale price is set
557 + * and the dates are valid.
558 + *
559 + * @return string|float
560 + */
561 + public function get_sale_price() {
562 + $sale_price_value = $this->get_data( 'sale_price', '' );
563 +
564 + if ( '' !== $sale_price_value ) {
565 + return apply_filters( 'learn-press/course/sale-price', floatval( $sale_price_value ), $this->get_id() );
566 + }
567 +
568 + return $sale_price_value;
569 + }
570 +
571 + /**
572 + * Check course has 'sale price'
573 + *
574 + * @return mixed
575 + */
576 + public function has_sale_price() {
577 + $has_sale_price = false;
578 + $regular_price = $this->get_regular_price();
579 + $sale_price = $this->get_sale_price();
580 + $start_date = $this->get_data( 'sale_start', '' );
581 + $end_date = $this->get_data( 'sale_end', '' );
582 +
583 + if ( $regular_price > $sale_price && is_float( $sale_price ) ) {
584 + $has_sale_price = true;
585 + }
586 +
587 + // Check in days sale
588 + if ( $has_sale_price && '' !== $start_date && '' !== $end_date ) {
589 + $nowObj = new LP_Datetime();
590 + // Compare via timezone WP
591 + $nowStr = $nowObj->toSql( true );
592 + $now = strtotime( $nowStr );
593 + $end = strtotime( $end_date );
594 + $start = strtotime( $start_date );
595 +
596 + $has_sale_price = $now >= $start && $now <= $end;
597 + }
598 +
599 + return apply_filters( 'learn-press/course/has-sale-price', $has_sale_price, $this->get_id() );
600 + }
601 +
602 + /**
603 + * Get the regular price of course.
604 + *
605 + * @return float
606 + */
607 + public function get_regular_price(): float {
608 + $price = floatval( $this->get_data( 'regular_price', 0 ) );
609 +
610 + return apply_filters( 'learn-press/course/regular-price', $price, $this->get_id() );
611 + }
612 +
613 + /**
614 + * Get the regular price format of course.
615 + *
616 + * @since 4.1.5
617 + * @version 1.0.0
618 + * @author tungnx
619 + * @return mixed
620 + */
621 + public function get_regular_price_html() {
622 + $price = learn_press_format_price( $this->get_regular_price(), true );
623 +
624 + return apply_filters( 'learn-press/course/regular-price-html', $price, $this->get_id() );
625 + }
626 +
627 + /**
628 + * Get the price of course.
629 + *
630 + * @return mixed
631 + */
632 + public function get_price() {
633 + $key_cache = "{$this->get_id()}/price";
634 + $price = LP_Course_Cache::cache_load_first( 'get', $key_cache );
635 +
636 + if ( false === $price ) {
637 + if ( $this->has_sale_price() ) {
638 + $price = $this->get_sale_price();
639 + // Add key _lp_course_is_sale for query - Todo: Check performance, need write function get all courses, and set on Admin, on background
640 + //update_post_meta( $this->get_id(), '_lp_course_is_sale', 1 );
641 + } else {
642 + // Delete key _lp_course_is_sale
643 + //delete_post_meta( $this->get_id(), '_lp_course_is_sale' );
644 + $price = $this->get_regular_price();
645 + }
646 +
647 + // Save price only on page Single Course
648 + /*if ( LP_PAGE_SINGLE_COURSE === LP_Page_Controller::page_current() ) {
649 + update_post_meta( $this->get_id(), '_lp_price', $price );
650 + }*/
651 +
652 + LP_Course_Cache::cache_load_first( 'set', $key_cache, $price );
653 + }
654 +
655 + return apply_filters( 'learn-press/course/price', $price, $this->get_id() );
656 + }
657 +
658 + /**
659 + * Get html course price
660 + *
661 + * @author tungnx
662 + * @since 4.1.5
663 + * @version 1.0.2
664 + * @return string
665 + */
666 + public function get_course_price_html(): string {
667 + $price_html = '';
668 +
669 + if ( $this->is_free() ) {
670 + if ( is_float( $this->get_sale_price() ) ) {
671 + $price_html .= sprintf( '<span class="origin-price">%s</span>', $this->get_regular_price_html() );
672 + }
673 +
674 + $price_html .= sprintf( '<span class="free">%s</span>', esc_html__( 'Free', 'learnpress' ) );
675 + $price_html = apply_filters( 'learn_press_course_price_html_free', $price_html, $this );
676 + } elseif ( $this->get_data( 'no_required_enroll', 'no' ) === 'yes' ) {
677 + $price_html .= '';
678 + } else {
679 + if ( $this->has_sale_price() ) {
680 + $price_html .= sprintf( '<span class="origin-price">%s</span>', $this->get_regular_price_html() );
681 + }
682 +
683 + $price_html .= sprintf( '<span class="price">%s</span>', learn_press_format_price( $this->get_price(), true ) );
684 + $course = CourseModel::find( $this->get_id(), true );
685 + $singleCourseTemplate = SingleCourseTemplate::instance();
686 + $price_html = sprintf(
687 + '%1$s %2$s %3$s',
688 + $singleCourseTemplate->html_price_prefix( $course ),
689 + $price_html,
690 + $singleCourseTemplate->html_price_suffix( $course )
691 + );
692 + $price_html = apply_filters( 'learn_press_course_price_html', $price_html, $this->has_sale_price(), $this->get_id() );
693 + }
694 +
695 + return sprintf( '<span class="course-item-price">%s</span>', $price_html );
696 + }
697 +
698 + /**
699 + * Get the price of course with html
700 + *
701 + * @editor tungnx
702 + * @modify 4.1.5
703 + * @version 1.0.1
704 + * @return mixed
705 + * @deprecated 4.1.5
706 + */
707 + public function get_price_html() {
708 + $price_html = '';
709 +
710 + if ( $this->is_free() ) {
711 + $price_html = apply_filters(
712 + 'learn_press_course_price_html_free',
713 + esc_html__( 'Free', 'learnpress' ),
714 + $this
715 + );
716 + } else {
717 + $price_html .= learn_press_format_price( $this->get_price() );
718 + $price_html = apply_filters( 'learn_press_course_price_html', $price_html, $this->has_sale_price(), $this->get_id() );
719 + }
720 +
721 + return $price_html;
722 + }
723 +
724 + /**
725 + * Get the origin price of course
726 + *
727 + * @return float
728 + * @deprecated 4.1.5
729 + */
730 + public function get_origin_price() {
731 + return $this->get_regular_price();
732 + }
733 +
734 + /**
735 + * Get the price of course with html
736 + *
737 + * @return mixed
738 + * @deprecated 4.1.5
739 + */
740 + public function get_origin_price_html() {
741 + return $this->get_regular_price_html();
742 + }
743 +
744 + /**
745 + * @param bool $item_id
746 + *
747 + * @return bool|mixed
748 + */
749 + public function is_viewing_item( $item_id = false ) {
750 + $item = LP_Global::course_item();
751 +
752 + if ( empty( $item ) ) {
753 + return false;
754 + }
755 +
756 + return apply_filters(
757 + 'learn-press/is-viewing-item',
758 + false !== $item_id ? $item_id == $item->get_id() : $item->get_id(),
759 + $item_id,
760 + $this->get_id()
761 + );
762 + }
763 +
764 + /**
765 + * @param $item_id
766 + *
767 + * @return bool|mixed
768 + */
769 + public function is_current_item( $item_id ) {
770 + return $this->is_viewing_item( $item_id );
771 + }
772 +
773 + /**
774 + * Check if the course has 'feature'
775 + * This function call to a function with prefix 'has'
776 + *
777 + * @param string
778 + *
779 + * @return mixed
780 + * @throws Exception
781 + */
782 + public function has( $tag ) {
783 + _deprecated_function( __FUNCTION__, '3.0.8' );
784 +
785 + $args = func_get_args();
786 + unset( $args[0] );
787 + $method = 'has_' . preg_replace( '!-!', '_', $tag );
788 + $callback = array( $this, $method );
789 +
790 + if ( is_callable( $callback ) ) {
791 + return call_user_func_array( $callback, $args );
792 + } else {
793 + throw new Exception( sprintf( __( 'The function %s doesn\'t exist', 'learnpress' ), $tag ) );
794 + }
795 + }
796 +
797 + /**
798 + * @param string
799 + *
800 + * @return mixed
801 + * @throws Exception
802 + */
803 + public function is( $tag ) {
804 + _deprecated_function( __FUNCTION__, '3.0.8' );
805 + $args = func_get_args();
806 + unset( $args[0] );
807 + $method = 'is_' . preg_replace( '!-!', '_', $tag );
808 + $callback = array( $this, $method );
809 +
810 + if ( is_callable( $callback ) ) {
811 + return call_user_func_array( $callback, $args );
812 + } else {
813 + throw new Exception( sprintf( __( 'The function %s doesn\'t exist', 'learnpress' ), $tag ) );
814 + }
815 + }
816 +
817 + /**
818 + * Return true if this course can be purchasable.
819 + * Required enroll.
820 + * Status is publish.
821 + *
822 + * @return mixed
823 + */
824 + public function is_purchasable() {
825 + $is_purchasable = $this->exists() && ! $this->is_no_required_enroll() && get_post_status( $this->get_id() ) == 'publish';
826 +
827 + return apply_filters( 'learn-press/is-purchasable', $is_purchasable, $this->get_id() );
828 + }
829 +
830 + /**
831 + * Check if students have purchased course is reached.
832 + * For case check course can purchase.
833 + *
834 + * @return mixed
835 + * @since 3.0.0
836 + * @version 1.0.1
837 + */
838 + public function is_in_stock() {
839 + $in_stock = true;
840 + $max_allowed = $this->get_max_students();
841 +
842 + if ( $max_allowed ) {
843 + $in_stock = $max_allowed > $this->get_total_user_enrolled_or_purchased();
844 + }
845 +
846 + return apply_filters( 'learn-press/is-in-stock', $in_stock, $this->get_id() );
847 + }
848 +
849 + /**
850 + * Check max student can enroll.
851 + * For case check course can enroll.
852 + *
853 + * @return mixed
854 + * @since 4.2.5.7
855 + * @version 1.0.0
856 + */
857 + public function is_in_stock_enroll() {
858 + $in_stock = true;
859 + $max_allowed = $this->get_max_students();
860 +
861 + if ( $max_allowed ) {
862 + $in_stock = $max_allowed > $this->get_total_user_enrolled_or_purchased();
863 + }
864 +
865 + return apply_filters( 'learn-press/is-in-stock', $in_stock, $this->get_id() );
866 + }
867 +
868 + /**
869 + * Get max students can enroll to course.
870 + *
871 + * @return int
872 + *
873 + * @since 3.0.0
874 + */
875 + public function get_max_students() {
876 + return apply_filters(
877 + 'learn-press/max-students',
878 + absint( $this->get_data( 'max_students' ) ),
879 + $this->get_id()
880 + );
881 + }
882 +
883 + /**
884 + * Count number of students enrolled course.
885 + * Check global settings `enrolled_students_number`
886 + * and add the fake value if both are set.
887 + *
888 + * @return int
889 + * @editor tungnx
890 + * @version 1.0.1
891 + * @since 3.0.0
892 + * @Todo: view and rewrite this function
893 + */
894 + public function count_students(): int {
895 + $total = $this->get_total_user_enrolled_or_purchased();
896 + $total += $this->get_fake_students();
897 +
898 + return $total;
899 + }
900 +
901 + /**
902 + * Get total user enrolled
903 + *
904 + * @since 4.1.4
905 + * @version 1.0.1
906 + * @return int
907 + */
908 + public function get_total_user_enrolled(): int {
909 + $total = 0;
910 + $lp_course_cache = new LP_Course_Cache( true );
911 +
912 + try {
913 + $total = $lp_course_cache->get_total_students_enrolled( $this->get_id() );
914 + if ( false !== $total ) {
915 + return $total;
916 + }
917 +
918 + $lp_course_db = LP_Course_DB::getInstance();
919 + $total = $lp_course_db->get_total_user_enrolled( $this->get_id() );
920 + $lp_course_cache->set_total_students_enrolled( $this->get_id(), $total );
921 + } catch ( Throwable $e ) {
922 + error_log( $e->getMessage() );
923 + }
924 +
925 + return $total;
926 + }
927 +
928 + /**
929 + * Get total user enrolled include fake
930 + * @since 4.2.2
931 + * @return int
932 + */
933 + /*public function get_total_user_enrolled_include_fake() {
934 + $total_user_enrolled = 0;
935 + $key_cache = "{$this->get_id()}/total-students-include-fake";
936 +
937 + try {
938 + $total_user_enrolled = LP_Course_Cache::cache_load_first( 'get', $key_cache );
939 +
940 + if ( false === $total_user_enrolled ) {
941 + $lp_course_db = LP_Course_DB::getInstance();
942 + $total_user_enrolled = $lp_course_db->get_total_user_enrolled( $this->get_id() );
943 + $total_user_enrolled += $this->get_fake_students();
944 +
945 + LP_Course_Cache::cache_load_first( 'set', $key_cache, $total_user_enrolled );
946 + }
947 + } catch ( Throwable $e ) {
948 + error_log( $e->getMessage() );
949 + }
950 +
951 + return $total_user_enrolled;
952 + }*/
953 +
954 + /**
955 + * Get total user enrolled, purchased or finished
956 + *
957 + * @since 4.1.4
958 + * @version 1.0.1
959 + * @return int
960 + */
961 + public function get_total_user_enrolled_or_purchased(): int {
962 + $total = 0;
963 + $lp_course_cache = new LP_Course_Cache( true );
964 +
965 + try {
966 + $total = $lp_course_cache->get_total_students_enrolled_or_purchased( $this->get_id() );
967 + if ( false !== $total ) {
968 + return $total;
969 + }
970 +
971 + $lp_course_db = LP_Course_DB::getInstance();
972 + $total = $lp_course_db->get_total_user_enrolled_or_purchased( $this->get_id() );
973 + $lp_course_cache->set_total_students_enrolled_or_purchased( $this->get_id(), $total );
974 + } catch ( Throwable $e ) {
975 + error_log( $e->getMessage() );
976 + }
977 +
978 + return $total;
979 + }
980 +
981 + /**
982 + * @param string|array $statuses
983 + *
984 + * @return mixed
985 + */
986 + public function count_in_order( $statuses = 'completed' ) {
987 + settype( $statuses, 'array' );
988 + $count = 0;
989 +
990 + foreach ( $statuses as $status ) {
991 + $orders = get_post_meta( $this->get_id(), 'order-' . $status, true );
992 +
993 + if ( $orders ) {
994 + $count += sizeof( $orders );
995 + }
996 + }
997 +
998 + return $count;
999 + }
1000 +
1001 + /**
1002 + * @editor tungnx
1003 + * @modify 4.1.4 - comment - not use
1004 + */
1005 + /*public function count_completed_orders() {
1006 + $orders = $this->get_meta( 'order-completed' );
1007 +
1008 + if ( $orders ) {
1009 + $count = sizeof( $orders );
1010 + } else {
1011 + $count = 0;
1012 + }
1013 +
1014 + return $count;
1015 + }*/
1016 +
1017 + /**
1018 + * Check if course contain an item in curriculum.
1019 + * Actually, find the item in each section inside curriculum.
1020 + *
1021 + * @param $item_id
1022 + *
1023 + * @return bool
1024 + */
1025 + public function has_item( $item_id ) {
1026 + $found = false;
1027 + $items = $this->get_item_ids();
1028 +
1029 + if ( $items ) {
1030 + $found = in_array( $item_id, $items );
1031 + }
1032 +
1033 + return apply_filters( 'learn-press/course-has-item', $found, $item_id, $this->get_id() );
1034 + }
1035 +
1036 + /**
1037 + * Get course's item (lesson/quiz/etc...).
1038 + *
1039 + * @param int $item_id Course's item Id.
1040 + *
1041 + * @return LP_Lesson|LP_Quiz|boolean
1042 + */
1043 + public function get_item( $item_id ) {
1044 + $item = LP_Course_Item::get_item( $item_id );
1045 + if ( $item instanceof LP_Course_Item ) {
1046 + $item->set_course( $this->get_id() );
1047 + }
1048 +
1049 + return apply_filters( 'learn-press/course-item', $item, $item_id, $this->get_id() );
1050 + }
1051 +
1052 + /**
1053 + * Get course passing condition value.
1054 + *
1055 + * @param bool $format
1056 + * @param string $context
1057 + *
1058 + * @return array|mixed|string
1059 + */
1060 + public function get_passing_condition( $format = false ) {
1061 + $value = absint( $this->get_data( 'passing_condition' ) );
1062 +
1063 + if ( $format ) {
1064 + $value = "{$value}%";
1065 + }
1066 +
1067 + return apply_filters(
1068 + 'learn-press/course-passing-condition',
1069 + $value,
1070 + $format,
1071 + $this->get_id()
1072 + );
1073 + }
1074 +
1075 + /**
1076 + * Get item's link
1077 + *
1078 + * @param int $item_id
1079 + *
1080 + * @editor tungnx
1081 + * @since 3.0.0
1082 + * @version 1.0.1
1083 + * @return string
1084 + */
1085 + public function get_item_link( int $item_id ): string {
1086 + $item_link = '';
1087 + $item_type = get_post_type( $item_id );
1088 +
1089 + $course_permalink = trailingslashit( $this->get_permalink() );
1090 + $item_slug = get_post_field( 'post_name', $item_id );
1091 +
1092 + $slug_prefixes = apply_filters(
1093 + 'learn-press/course/custom-item-prefixes',
1094 + array(
1095 + LP_QUIZ_CPT => sanitize_title_with_dashes( LP_Settings::get_option( 'quiz_slug', 'quizzes' ) ),
1096 + LP_LESSON_CPT => sanitize_title_with_dashes( LP_Settings::get_option( 'lesson_slug', 'lessons' ) ),
1097 + ),
1098 + $this->get_id()
1099 + );
1100 +
1101 + $slug_prefix = trailingslashit( $slug_prefixes[ $item_type ] ?? '' );
1102 + $item_link = trailingslashit( $course_permalink . $slug_prefix . $item_slug );
1103 +
1104 + return apply_filters( 'learn-press/course/item-link', $item_link, $item_id, $this );
1105 + }
1106 +
1107 + /**
1108 + * Get course's item at a position.
1109 + *
1110 + * @param int $at
1111 + *
1112 + * @return bool|mixed
1113 + * @editor tungnx
1114 + * @modify 4.1.3 - comment - not use
1115 + */
1116 + /*public function get_item_at( $at ) {
1117 + $items = $this->get_items();
1118 +
1119 + if ( ! $items ) {
1120 + return false;
1121 + }
1122 +
1123 + return ! empty( $items[ $at ] ) ? $items[ $at ] : false;
1124 + }*/
1125 +
1126 + /**
1127 + * Get position of an item in course curriculum.
1128 + *
1129 + * @param LP_Course_Item|LP_User_Item|int $item
1130 + *
1131 + * @return mixed
1132 + * @deprecated 4.1.6.9
1133 + */
1134 + /*public function get_item_position( $item ) {
1135 + $items = $this->get_items();
1136 +
1137 + if ( ! $items ) {
1138 + return false;
1139 + }
1140 +
1141 + $item_id = is_a( $item, 'LP_User_Item' ) || is_a(
1142 + $item,
1143 + 'LP_Course_Item'
1144 + ) ? $item->get_id() : absint( $item );
1145 +
1146 + return array_search( $item_id, $items );
1147 + }*/
1148 +
1149 + /**
1150 + * @return bool|mixed
1151 + */
1152 + public function get_current_item() {
1153 + return $this->is_viewing_item();
1154 + }
1155 +
1156 + /**
1157 + * Get item standing after the item is viewing.
1158 + *
1159 + * @param array $args
1160 + *
1161 + * @return int
1162 + */
1163 + public function get_next_item( $args = null ) {
1164 + $item_nav = $this->get_item_nav();
1165 +
1166 + return apply_filters( 'learn-press/course/next-item', $item_nav ? $item_nav[2] : 0, $this->get_id(), $args );
1167 + }
1168 +
1169 + /**
1170 + * Get item standing before the item is viewing.
1171 + *
1172 + * @param array $args
1173 + *
1174 + * @return int
1175 + */
1176 + public function get_prev_item( $args = null ) {
1177 + $item_nav = $this->get_item_nav();
1178 + if ( ! is_array( $item_nav ) || empty( $item_nav ) ) {
1179 + return 0;
1180 + }
1181 +
1182 + return apply_filters( 'learn-press/course/prev-item', $item_nav[0], $this->get_id(), $args );
1183 + }
1184 +
1185 + /**
1186 + * Get item standing before and after an item.
1187 + * If the item is not passed consider it is item viewing.
1188 + *
1189 + * @param bool $current_item
1190 + * @param bool $viewable - Optional. TRUE will get next item is viewable.
1191 + *
1192 + * @return array|bool
1193 + * @since 3.1.0
1194 + */
1195 + public function get_item_nav( $current_item = false, $viewable = false ) {
1196 + if ( false === $current_item ) {
1197 + $current_item = $this->get_current_item();
1198 + }
1199 +
1200 + if ( false === $current_item ) {
1201 + return false;
1202 + }
1203 +
1204 + $prev_id = $next_id = 0;
1205 + $item_ids = $this->get_item_ids();
1206 +
1207 + if ( $item_ids ) {
1208 + $pos = array_search( $current_item, $item_ids );
1209 +
1210 + if ( false !== $pos ) {
1211 + $max = sizeof( $item_ids ) - 1;
1212 + $user = learn_press_get_current_user();
1213 + $pos_tmp = $pos;
1214 +
1215 + while ( $pos_tmp < $max ) {
1216 + ++$pos_tmp;
1217 +
1218 + if ( ! $viewable ) {
1219 + $next_id = $item_ids[ $pos_tmp ];
1220 +
1221 + break;
1222 + }
1223 + }
1224 +
1225 + $pos_tmp = $pos;
1226 +
1227 + while ( $pos_tmp > 0 ) {
1228 + --$pos_tmp;
1229 +
1230 + if ( ! $viewable ) {
1231 + $prev_id = $item_ids[ $pos_tmp ];
1232 +
1233 + break;
1234 + }
1235 + }
1236 + }
1237 + }
1238 +
1239 + return array( $prev_id, $current_item, $next_id );
1240 + }
1241 +
1242 + /**
1243 + * Get link of item is standing after the item is viewing.
1244 + *
1245 + * @param array $args
1246 + *
1247 + * @return string
1248 + */
1249 + public function get_next_item_html( $args = null ) {
1250 + $args = wp_parse_args(
1251 + $args,
1252 + array(
1253 + 'current_item' => false,
1254 + 'viewable' => null,
1255 + 'dir' => 'next',
1256 + )
1257 + );
1258 +
1259 + $next_item = $this->get_next_item( $args );
1260 +
1261 + if ( $next_item ) {
1262 + ob_start();
1263 +
1264 + learn_press_get_template(
1265 + 'content-lesson/next-button.php',
1266 + array(
1267 + 'item' => $next_item,
1268 + 'course' => $this,
1269 + )
1270 + );
1271 +
1272 + return ob_get_clean();
1273 + }
1274 +
1275 + return false;
1276 + }
1277 +
1278 + public function get_prev_item_html( $args = null ) {
1279 + }
1280 +
1281 + public function get_preview_items() {
1282 + return LP_Object_Cache::get( 'course-' . $this->get_id(), 'learn-press/course-preview-items' );
1283 + }
1284 +
1285 + /**
1286 + * Check a quiz is a final quiz in this course
1287 + *
1288 + * @param $quiz_id
1289 + *
1290 + * @return mixed
1291 + */
1292 + public function is_final_quiz( $quiz_id ) {
1293 + return apply_filters( 'learn_press_is_final_quiz', $this->get_final_quiz() == $quiz_id, $quiz_id, $this->get_id() );
1294 + }
1295 +
1296 + public function get_final_quiz() {
1297 + $final_quiz = $this->get_data( 'final_quiz' );
1298 +
1299 + return apply_filters( 'learn-press/course-final-quiz', $final_quiz, $this->get_id() );
1300 + }
1301 +
1302 + public function set_final_quiz( $id ) {
1303 + $this->_set_data( 'final_quiz', $id );
1304 + }
1305 +
1306 + /**
1307 + * Get course duration in seconds
1308 + *
1309 + * @return int
1310 + */
1311 + public function get_duration() {
1312 + /**
1313 + * Duration is in string such as 10 week, 4 hour, etc...
1314 + * So we can use strtotime('+10 week') to convert it to seconds
1315 + */
1316 + return strtotime( '+' . $this->get_data( 'duration' ), 0 );
1317 + }
1318 +
1319 +
1320 + /**
1321 + * Get course remaining time message
1322 + *
1323 + * @param $user_id
1324 + *
1325 + * @return string
1326 + */
1327 + public function get_user_duration_html( $user_id = 0 ) {
1328 + if ( ! $user_id ) {
1329 + $user_id = get_current_user_id();
1330 + }
1331 +
1332 + $duration = $this->get_duration();
1333 + $user = learn_press_get_user( $user_id );
1334 + $course_info = $user->get_course_info( $this->get_id() );
1335 + $html = '';
1336 +
1337 + if ( $course_info ) {
1338 + $now = current_time( 'timestamp' );
1339 + $start_time = intval( strtotime( $course_info['start'] ) );
1340 +
1341 + if ( $start_time + $duration > $now ) {
1342 + $remain = $start_time + $duration - $now;
1343 + $remain = learn_press_seconds_to_weeks( $remain );
1344 + $html = sprintf( __( 'This course will end within the next %s', 'learnpress' ), $remain );
1345 + }
1346 + }
1347 +
1348 + return $html;
1349 + }
1350 +
1351 + /**
1352 + * Output params for single course page
1353 + *
1354 + * @param null $args
1355 + *
1356 + * @return mixed
1357 + */
1358 + public function output_args( $args = null ) {
1359 + return array();
1360 + }
1361 +
1362 + /**
1363 + * Get external link of "Buy this course" button
1364 + *
1365 + * @return mixed
1366 + */
1367 + public function get_external_link() {
1368 + return apply_filters( 'learn-press/course-external-link', $this->get_data( 'external_link', '' ), $this->get_id() );
1369 + }
1370 +
1371 + public function get_external_link_text() {
1372 + return apply_filters( 'learn-press/course-external-link-text', _x( 'More Info', 'External Link button text', 'learnpress' ), $this->get_id() );
1373 + }
1374 +
1375 + /**
1376 + * Get main author of course.
1377 + *
1378 + * @param string $field
1379 + *
1380 + * @return LP_User|int
1381 + */
1382 + public function get_author( string $field = '' ) {
1383 + $author_id = absint( get_post_field( 'post_author', $this->get_id() ) );
1384 +
1385 + return strtolower( $field ) === 'id' ? $author_id : learn_press_get_user( $author_id );
1386 + }
1387 +
1388 + /**
1389 + * Get author's display name
1390 + *
1391 + * @return string
1392 + * @since 3.0.9
1393 + */
1394 + public function get_author_display_name() {
1395 + $display_name = '';
1396 + $user = $this->get_author();
1397 +
1398 + if ( $user ) {
1399 + $display_name = $user->get_display_name();
1400 + }
1401 +
1402 + return $display_name;
1403 + }
1404 +
1405 + /**
1406 + * @return mixed
1407 + */
1408 + public function get_tags() {
1409 + return apply_filters( 'learn-press/course-tags', get_the_term_list( $this->get_id(), 'course_tag', __( 'Tags: ', 'learnpress' ), ' ', '' ) );
1410 + }
1411 +
1412 + /**
1413 + * Get extra info of course.
1414 + * Target Audience, Key Features, Requirements, etc...
1415 + *
1416 + * @param string $type [target_audience, key_features, requirements]
1417 + *
1418 + * @return string|array
1419 + * @since 3.x.x
1420 + */
1421 + public function get_extra_info( $type ) {
1422 + $extra_info_meta = get_post_meta( $this->get_id(), '_lp_' . $type, true );
1423 +
1424 + return apply_filters( 'learn-press/course-extra-info', $extra_info_meta, $type, $this->get_id() );
1425 + }
1426 +
1427 + /**
1428 + * Get FAQs in Tab metabox.
1429 + *
1430 + * @return array
1431 + * @since 4.0.0
1432 + */
1433 + public function get_faqs(): array {
1434 + $faqs = array();
1435 + $data = get_post_meta( $this->get_id(), '_lp_faqs', true );
1436 +
1437 + if ( $data ) {
1438 + foreach ( $data as $faq ) {
1439 + $faqs[] = array(
1440 + 'question' => $faq[0],
1441 + 'answer' => $faq[1],
1442 + );
1443 + }
1444 + }
1445 +
1446 + return apply_filters( 'learn-press/course-faqs', $faqs, $this->get_id() );
1447 + }
1448 +
1449 + /**
1450 + * Get course is set featured
1451 + *
1452 + * @return bool
1453 + */
1454 + public function is_featured(): bool {
1455 + return apply_filters(
1456 + 'learn-press/course-is-featured',
1457 + get_post_meta( $this->get_id(), '_lp_featured', true ) === 'yes',
1458 + $this->get_id(),
1459 + $this
1460 + );
1461 + }
1462 +
1463 + /**
1464 + * [get_downloadable_material get all material files of this course and lesson of this course]
1465 + * @return [array] [array of material files or empty array]
1466 + * @deprecated 4.2.6.4
1467 + */
1468 + /*public function get_downloadable_material(): array {
1469 + $material = LP_Material_Files_DB::getInstance();
1470 + $materials = $material->get_material_by_item_id( $this->get_id(), 1 );
1471 + return apply_filters( 'learn-press/course-materials', $materials, $this->get_id() );
1472 + }*/
1473 + }
1474 +}