PluginProbe ʕ •ᴥ•ʔ
Tutor LMS – eLearning and online course solution / 2.1.6
Tutor LMS – eLearning and online course solution v2.1.6
3.9.14 3.9.13 3.9.12 3.9.11 trunk 1.0.0 1.0.0-alpha 1.0.1 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9 1.1.0 1.1.1 1.2.0 1.2.1 1.2.11 1.2.12 1.2.13 1.2.20 1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 1.3.5 1.3.6 1.3.7 1.3.8 1.3.9 1.4.0 1.4.1 1.4.2 1.4.3 1.4.4 1.4.5 1.4.6 1.4.7 1.4.8 1.4.9 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 1.5.5 1.5.6 1.5.7 1.5.8 1.5.9 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 1.6.5 1.6.6 1.6.7 1.6.8 1.6.9 1.7.0 1.7.1 1.7.2 1.7.3 1.7.4 1.7.5 1.7.6 1.7.7 1.7.8 1.7.9 1.8.0 1.8.1 1.8.10 1.8.2 1.8.3 1.8.4 1.8.5 1.8.6 1.8.7 1.8.8 1.8.9 1.9.0 1.9.1 1.9.10 1.9.11 1.9.12 1.9.13 1.9.14 1.9.15 1.9.16 1.9.2 1.9.3 1.9.4 1.9.5 1.9.6 1.9.7 1.9.8 1.9.9 2.0.0 2.0.1 2.0.10 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7 2.0.8 2.0.9 2.1.0 2.1.1 2.1.10 2.1.2 2.1.3 2.1.4 2.1.5 2.1.6 2.1.7 2.1.8 2.1.9 2.2.0 2.2.1 2.2.2 2.2.3 2.2.4 2.3.0 2.4.0 2.5.0 2.6.0 2.6.1 2.6.2 2.7.0 2.7.1 2.7.2 2.7.3 2.7.4 2.7.5 2.7.6 2.7.7 3.0.0 3.0.1 3.0.2 3.1.0 3.2.0 3.2.1 3.2.2 3.2.3 3.3.0 3.3.1 3.4.0 3.4.1 3.4.2 3.5.0 3.6.0 3.6.1 3.6.2 3.6.3 3.6.4 3.7.0 3.7.1 3.7.2 3.7.3 3.7.4 3.8.0 3.8.1 3.8.2 3.8.3 3.9.0 3.9.1 3.9.10 3.9.2 3.9.3 3.9.4 3.9.5 3.9.6 3.9.7 3.9.8 3.9.9
tutor / classes / Course.php
tutor / classes Last commit date
Addons.php 3 years ago Admin.php 3 years ago Ajax.php 3 years ago Announcements.php 3 years ago Assets.php 3 years ago Backend_Page_Trait.php 3 years ago Course.php 3 years ago Course_Embed.php 3 years ago Course_Filter.php 3 years ago Course_List.php 3 years ago Course_Settings_Tabs.php 3 years ago Course_Widget.php 3 years ago Custom_Validation.php 3 years ago Dashboard.php 3 years ago FormHandler.php 3 years ago Frontend.php 3 years ago Gutenberg.php 3 years ago Input.php 3 years ago Instructor.php 3 years ago Instructors_List.php 3 years ago Lesson.php 3 years ago Options_V2.php 3 years ago Post_types.php 3 years ago Private_Course_Access.php 3 years ago Q_and_A.php 3 years ago Question_Answers_List.php 3 years ago Quiz.php 3 years ago Quiz_Attempts_List.php 3 years ago RestAPI.php 3 years ago Reviews.php 3 years ago Rewrite_Rules.php 3 years ago Shortcode.php 3 years ago Student.php 3 years ago Students_List.php 3 years ago Taxonomies.php 3 years ago Template.php 3 years ago Theme_Compatibility.php 3 years ago Tools.php 3 years ago Tools_V2.php 3 years ago Tutor.php 3 years ago TutorEDD.php 3 years ago Tutor_Base.php 3 years ago Tutor_Setup.php 3 years ago Upgrader.php 3 years ago User.php 3 years ago Utils.php 3 years ago Video_Stream.php 3 years ago Withdraw.php 3 years ago Withdraw_Requests_List.php 3 years ago WooCommerce.php 3 years ago
Course.php
1626 lines
1 <?php
2 /**
3 * Manage Course Related Logic
4 *
5 * @package Tutor
6 * @author Themeum <support@themeum.com>
7 * @link https://themeum.com
8 * @since 1.0.0
9 */
10
11 namespace TUTOR;
12
13 if ( ! defined( 'ABSPATH' ) ) {
14 exit;
15 }
16
17 use TUTOR\Input;
18 use Tutor\Models\CourseModel;
19
20 /**
21 * Course Class
22 *
23 * @since 1.0.0
24 */
25 class Course extends Tutor_Base {
26
27 /**
28 * Additional course meta info
29 *
30 * @var array
31 */
32 private $additional_meta = array(
33 '_tutor_enable_qa',
34 '_tutor_is_public_course',
35 );
36
37 /**
38 * Constructor
39 *
40 * @since 1.0.0
41 * @return void
42 */
43 public function __construct() {
44 parent::__construct();
45
46 add_action( 'add_meta_boxes', array( $this, 'register_meta_box' ) );
47 add_action( 'save_post_' . $this->course_post_type, array( $this, 'save_course_meta' ), 10, 2 );
48 add_action( 'wp_ajax_tutor_save_topic', array( $this, 'tutor_save_topic' ) );
49
50 /**
51 * Add Column
52 */
53 add_filter( "manage_{$this->course_post_type}_posts_columns", array( $this, 'add_column' ), 10, 1 );
54 add_action( "manage_{$this->course_post_type}_posts_custom_column", array( $this, 'custom_lesson_column' ), 10, 2 );
55
56 add_action( 'wp_ajax_tutor_delete_topic', array( $this, 'tutor_delete_topic' ) );
57 add_action( 'admin_action_tutor_delete_announcement', array( $this, 'tutor_delete_announcement' ) );
58
59 /**
60 * Frontend Action
61 */
62 add_action( 'template_redirect', array( $this, 'enroll_now' ) );
63 add_action( 'init', array( $this, 'mark_course_complete' ) );
64
65 /**
66 * Frontend Dashboard
67 */
68 add_action( 'wp_ajax_tutor_delete_dashboard_course', array( $this, 'tutor_delete_dashboard_course' ) );
69
70 /**
71 * Gutenberg author support
72 */
73 add_filter( 'wp_insert_post_data', array( $this, 'tutor_add_gutenberg_author' ), '99', 2 );
74
75 /**
76 * Frontend metabox supports for course builder
77 *
78 * @since v.1.3.4
79 */
80 add_action( 'tutor/dashboard_course_builder_form_field_after', array( $this, 'register_meta_box_in_frontend' ) );
81
82 /**
83 * Do Stuff for the course save from frontend
84 */
85 add_action( 'save_tutor_course', array( $this, 'attach_product_with_course' ), 10, 2 );
86
87 /**
88 * Add course level to course settings
89 *
90 * @since v.1.4.1
91 */
92 add_filter( 'tutor_course_settings_tabs', array( $this, 'add_course_level_to_settings' ) );
93
94 /**
95 * Enable Disable Course Details Page Feature
96 *
97 * @since v.1.4.8
98 */
99 $this->course_elements_enable_disable();
100
101 /**
102 * Check if course starting, set meta if starting
103 *
104 * @since v.1.4.8
105 */
106 add_action( 'tutor_lesson_load_before', array( $this, 'tutor_lesson_load_before' ) );
107
108 /**
109 * Filter product in shop page
110 *
111 * @since v.1.4.9
112 */
113 $this->filter_product_in_shop_page();
114
115 /**
116 * Remove the course price if enrolled
117 *
118 * @since 1.5.8
119 */
120 add_filter( 'tutor_course_price', array( $this, 'remove_price_if_enrolled' ) );
121
122 /**
123 * Remove course complete button if course completion is strict mode
124 *
125 * @since v.1.6.1
126 */
127 add_filter( 'tutor_course/single/complete_form', array( $this, 'tutor_lms_hide_course_complete_btn' ) );
128 add_filter( 'get_gradebook_generate_form_html', array( $this, 'get_generate_greadbook' ) );
129
130 /**
131 * Add social share content in header
132 *
133 * @since v.1.6.3
134 */
135 add_action( 'wp_head', array( $this, 'social_share_content' ) );
136
137 /**
138 * Delete course data after deleted course
139 *
140 * @since v.1.6.6
141 */
142 add_action( 'deleted_post', array( new CourseModel(), 'delete_course_data' ) );
143
144 /**
145 * Delete course data after deleted course
146 *
147 * @since v.1.8.2
148 */
149 add_action( 'before_delete_post', array( $this, 'delete_associated_enrollment' ) );
150
151 /**
152 * Show only own uploads in media library if user is instructor
153 *
154 * @since v1.8.9
155 */
156 add_filter( 'posts_where', array( $this, 'restrict_media' ) );
157
158 /**
159 * Restrict new enrol/purchase button if course member limit reached
160 *
161 * @since v1.9.0
162 */
163 add_filter( 'tutor_course_restrict_new_entry', array( $this, 'restrict_new_student_entry' ) );
164
165 /**
166 * Reset course progress on retake
167 *
168 * @since v1.9.5
169 */
170 add_action( 'wp_ajax_tutor_reset_course_progress', array( $this, 'tutor_reset_course_progress' ) );
171
172 /**
173 * Popup for review
174 *
175 * @since v1.9.7
176 */
177 add_action( 'wp_footer', array( $this, 'popup_review_form' ) );
178
179 /**
180 * Do enroll after login if guest take enroll attempt
181 *
182 * @since 1.9.8
183 */
184 add_action( 'tutor_do_enroll_after_login_if_attempt', array( $this, 'enroll_after_login_if_attempt' ), 10, 2 );
185
186 add_action( 'wp_ajax_tutor_update_course_content_order', array( $this, 'tutor_update_course_content_order' ) );
187
188 add_action( 'wp_ajax_tutor_get_wc_product', array( $this, 'tutor_get_wc_product' ) );
189
190 add_action( 'wp_ajax_tutor_course_enrollment', array( $this, 'course_enrollment' ) );
191 }
192
193 /**
194 * Get course associate WC product info by Ajax request
195 *
196 * @since 2.0.7
197 * @return void
198 */
199 public function tutor_get_wc_product() {
200 tutor_utils()->checking_nonce();
201 $product_id = Input::post( 'product_id' );
202 $product = wc_get_product( $product_id );
203 $course_id = Input::post( 'course_id', 0, Input::TYPE_INT );
204
205 $is_linked_with_course = tutor_utils()->product_belongs_with_course( $product_id );
206 /**
207 * If selected product is already linked with
208 * a course & it is not the current course the
209 * return error
210 *
211 * @since v2.1.0
212 */
213 if ( is_object( $is_linked_with_course ) && $is_linked_with_course->post_id != $course_id ) {
214 wp_send_json_error(
215 __( 'One product can not be added to multiple course!', 'tutor' )
216 );
217 }
218
219 if ( $product ) {
220 $data = array(
221 'name' => $product->get_name(),
222 'regular_price' => $product->get_regular_price(),
223 'sale_price' => $product->get_sale_price(),
224 );
225 wp_send_json_success( $data );
226 } else {
227 wp_send_json_error( __( 'Product not found', 'tutor' ) );
228 }
229 }
230
231 /**
232 * Update course content order
233 *
234 * @since 1.0.0
235 * @return void
236 */
237 public function tutor_update_course_content_order() {
238 tutor_utils()->checking_nonce();
239
240 if ( Input::has( 'content_parent' ) ) {
241 $content_parent = Input::post( 'content_parent', array(), Input::TYPE_ARRAY );
242 $topic_id = tutor_utils()->array_get( 'parent_topic_id', $content_parent );
243 $content_id = tutor_utils()->array_get( 'content_id', $content_parent );
244
245 if ( ! tutor_utils()->can_user_manage( 'topic', $topic_id ) ) {
246 wp_send_json_success( array( 'message' => __( 'Access Denied!', 'tutor' ) ) );
247 exit;
248 }
249
250 // Update the parent topic id of the content.
251 global $wpdb;
252 $wpdb->update( $wpdb->posts, array( 'post_parent' => $topic_id ), array( 'ID' => $content_id ) );
253 }
254
255 // Save course content order.
256 $this->save_course_content_order();
257
258 wp_send_json_success();
259 }
260
261 /**
262 * Restrict new student entry
263 *
264 * @since 1.0.0
265 * @param mixed $content content.
266 * @return mixed
267 */
268 public function restrict_new_student_entry( $content ) {
269
270 if ( ! tutor_utils()->is_course_fully_booked() ) {
271 // No restriction if not fully booked.
272 return $content;
273 }
274
275 return '<div class="list-item-booking booking-full tutor-d-flex tutor-align-center"><div class="booking-progress tutor-d-flex"><span class="tutor-mr-8 tutor-color-warning tutor-icon-circle-info"></span></div><div class="tutor-fs-7 tutor-fw-medium">Fully Booked</div></div>';
276 }
277
278 /**
279 * Restrict media
280 *
281 * @since 1.0.0
282 * @param string $where where clause.
283 * @return string
284 */
285 public function restrict_media( $where ) {
286 $action = Input::post( 'action' );
287 if ( 'query-attachments' === $action && tutor_utils()->is_instructor() ) {
288 if ( ! tutor_utils()->has_user_role( array( 'administrator', 'editor' ) ) ) {
289 $where .= ' AND post_author=' . get_current_user_id();
290 }
291 }
292
293 return $where;
294 }
295
296 /**
297 * Registering metabox
298 *
299 * @since 1.0.0
300 * @return void
301 */
302 public function register_meta_box() {
303 $course_post_type = tutor()->course_post_type;
304
305 tutor_meta_box_wrapper( 'tutor-course-topics', __( 'Course Builder', 'tutor' ), array( $this, 'course_meta_box' ), $course_post_type, 'advanced', 'default', 'tutor-admin-post-meta' );
306
307 tutor_meta_box_wrapper( 'tutor-course-additional-data', __( 'Additional Data', 'tutor' ), array( $this, 'course_additional_data_meta_box' ), $course_post_type, 'advanced', 'default', 'tutor-admin-post-meta' );
308
309 tutor_meta_box_wrapper( 'tutor-course-videos', __( 'Video', 'tutor' ), array( $this, 'video_metabox' ), $course_post_type, 'advanced', 'default', 'tutor-admin-post-meta' );
310 }
311
312 /**
313 * Course meta box (Topics)
314 *
315 * @since 1.0.0
316 * @param boolean $echo display or not.
317 * @return string
318 */
319 public function course_meta_box( $echo = true ) {
320 $file_path = tutor()->path . 'views/metabox/course-topics.php';
321
322 if ( $echo ) {
323 /**
324 * Use echo raise WPCS security issue
325 * Helper wp_kses_post break content.
326 */
327 include $file_path;
328 } else {
329 ob_start();
330 include $file_path;
331 return ob_get_clean();
332 }
333 }
334
335 /**
336 * Additional data meta box
337 *
338 * @since 1.0.0
339 * @param boolean $echo print data or return.
340 * @return string
341 */
342 public function course_additional_data_meta_box( $echo = true ) {
343 $file_path = tutor()->path . 'views/metabox/course-additional-data.php';
344
345 if ( $echo ) {
346 /**
347 * Use echo raise WPCS security issue
348 * Helper wp_kses_post break content.
349 */
350 include $file_path;
351 } else {
352 ob_start();
353 include $file_path;
354 return ob_get_clean();
355 }
356 }
357
358 /**
359 * Video meta box
360 *
361 * @since 1.0.0
362 * @param boolean $echo print data or return.
363 * @return string
364 */
365 public function video_metabox( $echo = true ) {
366 $file_path = tutor()->path . 'views/metabox/video-metabox.php';
367
368 if ( $echo ) {
369 /**
370 * Use echo raise WPCS security issue
371 * Helper wp_kses_post break content.
372 */
373 include $file_path;
374 } else {
375 ob_start();
376 include $file_path;
377 return ob_get_clean();
378 }
379 }
380
381 /**
382 * Register metabox in course builder tutor
383 *
384 * @since 1.3.4
385 * @return void
386 */
387 public function register_meta_box_in_frontend() {
388 global $post;
389
390 do_action( 'tutor_course_builder_metabox_before', get_the_ID() );
391
392 course_builder_section_wrap( $this->video_metabox( false ), __( 'Video', 'tutor' ) );
393 do_action( 'tutor/frontend_course_edit/after/video', $post );
394
395 course_builder_section_wrap( $this->course_meta_box( false ), __( 'Course Builder', 'tutor' ) );
396 do_action( 'tutor/frontend_course_edit/after/course_builder', $post );
397
398 course_builder_section_wrap( $this->course_additional_data_meta_box( false ), __( 'Additional Data', 'tutor' ) );
399 do_action( 'tutor/frontend_course_edit/after/additional_data', $post );
400
401 do_action( 'tutor_course_builder_metabox_after', get_the_ID() );
402 }
403
404 /**
405 * Save course content order
406 *
407 * @since 1.0.0
408 * @return void
409 */
410 private function save_course_content_order() {
411 global $wpdb;
412
413 $new_order = Input::post( 'tutor_topics_lessons_sorting' );
414 if ( ! empty( $new_order ) ) {
415 $order = json_decode( $new_order, true );
416
417 if ( is_array( $order ) && count( $order ) ) {
418 $i = 0;
419 foreach ( $order as $topic ) {
420 $i++;
421 $wpdb->update(
422 $wpdb->posts,
423 array( 'menu_order' => $i ),
424 array( 'ID' => $topic['topic_id'] )
425 );
426
427 /**
428 * Removing All lesson with topic
429 */
430
431 $wpdb->update(
432 $wpdb->posts,
433 array( 'post_parent' => 0 ),
434 array( 'post_parent' => $topic['topic_id'] )
435 );
436
437 /**
438 * Lesson Attaching with topic ID
439 * Sorting lesson
440 */
441 if ( isset( $topic['lesson_ids'] ) ) {
442 $lesson_ids = $topic['lesson_ids'];
443 } else {
444 $lesson_ids = array();
445 }
446 if ( count( $lesson_ids ) ) {
447 foreach ( $lesson_ids as $lesson_key => $lesson_id ) {
448 $wpdb->update(
449 $wpdb->posts,
450 array(
451 'post_parent' => $topic['topic_id'],
452 'menu_order' => $lesson_key,
453 ),
454 array( 'ID' => $lesson_id )
455 );
456 }
457 }
458 }
459 }
460 }
461 }
462
463 /**
464 * Insert Topic and attached it with Course
465 *
466 * @since 1.0.0
467 *
468 * @param integer $post_ID post ID.
469 * @param object $post post object.
470 *
471 * @return void
472 */
473 public function save_course_meta( $post_ID, $post ) {
474 global $wpdb;
475
476 do_action( 'tutor_save_course', $post_ID, $post );
477
478 /**
479 * Save course price type
480 */
481 $price_type = Input::post( 'tutor_course_price_type' );
482 if ( $price_type ) {
483 update_post_meta( $post_ID, '_tutor_course_price_type', $price_type );
484 }
485
486 //phpcs:disable WordPress.Security.NonceVerification.Missing
487 // Course Duration.
488 if ( ! empty( $_POST['course_duration'] ) ) {
489 $video = Input::post( 'course_duration', array(), Input::TYPE_ARRAY );
490 update_post_meta( $post_ID, '_course_duration', $video );
491 }
492
493 if ( ! empty( $_POST['_tutor_course_level'] ) ) {
494 $course_level = Input::post( '_tutor_course_level' );
495 update_post_meta( $post_ID, '_tutor_course_level', $course_level );
496 }
497
498 $additional_data_edit = Input::post( '_tutor_course_additional_data_edit' );
499 if ( $additional_data_edit ) {
500 if ( ! empty( $_POST['course_benefits'] ) ) {
501 $course_benefits = Input::post( 'course_benefits', '', Input::TYPE_KSES_POST );
502 update_post_meta( $post_ID, '_tutor_course_benefits', $course_benefits );
503 } else {
504 delete_post_meta( $post_ID, '_tutor_course_benefits' );
505 }
506
507 if ( ! empty( $_POST['course_requirements'] ) ) {
508 $requirements = Input::post( 'course_requirements', '', Input::TYPE_KSES_POST );
509 update_post_meta( $post_ID, '_tutor_course_requirements', $requirements );
510 } else {
511 delete_post_meta( $post_ID, '_tutor_course_requirements' );
512 }
513
514 if ( ! empty( $_POST['course_target_audience'] ) ) {
515 $target_audience = Input::post( 'course_target_audience', '', Input::TYPE_KSES_POST );
516 update_post_meta( $post_ID, '_tutor_course_target_audience', $target_audience );
517 } else {
518 delete_post_meta( $post_ID, '_tutor_course_target_audience' );
519 }
520
521 if ( ! empty( $_POST['course_material_includes'] ) ) {
522 $material_includes = Input::post( 'course_material_includes', '', Input::TYPE_KSES_POST );
523 update_post_meta( $post_ID, '_tutor_course_material_includes', $material_includes );
524 } else {
525 delete_post_meta( $post_ID, '_tutor_course_material_includes' );
526 }
527 //phpcs:enable WordPress.Security.NonceVerification.Missing
528 }
529
530 /**
531 * Sorting Topics and lesson
532 */
533 $this->save_course_content_order();
534
535 // Additional data like course intro video.
536 if ( $additional_data_edit ) {
537 // Sanitize data through helper method.
538 $video = Input::sanitize_array(
539 $_POST['video'] ?? array(), //phpcs:ignore
540 array(
541 'source_external_url' => 'esc_url',
542 'source_embedded' => 'wp_kses_post',
543 ),
544 true
545 );
546 $video_source = tutor_utils()->array_get( 'source', $video );
547 if ( -1 !== $video_source ) {
548 update_post_meta( $post_ID, '_video', $video );
549 } else {
550 delete_post_meta( $post_ID, '_video' );
551 }
552 }
553
554 /**
555 * Adding author to instructor automatically
556 */
557
558 // Override post author id.
559 $author_id = isset( $_POST['post_author_override'] ) ? $_POST['post_author_override'] : $post->post_author; //phpcs:ignore
560 $attached = (int) $wpdb->get_var(
561 $wpdb->prepare(
562 "SELECT COUNT(umeta_id) FROM {$wpdb->usermeta}
563 WHERE user_id = %d
564 AND meta_key = '_tutor_instructor_course_id'
565 AND meta_value = %d ",
566 $author_id,
567 $post_ID
568 )
569 );
570
571 if ( ! $attached ) {
572 add_user_meta( $author_id, '_tutor_instructor_course_id', $post_ID );
573 }
574
575 /**
576 * Disable question and answer for this course
577 *
578 * @since 1.7.0
579 */
580 if ( $additional_data_edit ) {
581 foreach ( $this->additional_meta as $key ) {
582 //phpcs:ignore WordPress.Security.NonceVerification.Missing
583 update_post_meta( $post_ID, $key, ( isset( $_POST[ $key ] ) ? 'yes' : 'no' ) );
584 }
585 }
586
587 do_action( 'tutor_save_course_after', $post_ID, $post );
588 }
589
590 /**
591 * Save course topic
592 *
593 * @since 1.0.0
594 * @return void
595 */
596 public function tutor_save_topic() {
597 tutor_utils()->checking_nonce();
598
599 // Check required fields.
600 if ( empty( Input::post( 'topic_title' ) ) ) {
601 wp_send_json_error( array( 'message' => __( 'Topic title is required!', 'tutor' ) ) );
602 }
603
604 // Gather parameters.
605 $course_id = Input::post( 'topic_course_id', 0, Input::TYPE_INT );
606 $topic_id = Input::post( 'topic_id', 0, Input::TYPE_INT );
607 $topic_title = Input::post( 'topic_title' );
608 $topic_summery = Input::post( 'topic_summery', '', Input::TYPE_KSES_POST );
609 $next_topic_order_id = tutor_utils()->get_next_topic_order_id( $course_id, $topic_id );
610
611 // Validate if user can manage the topic.
612 if ( ! tutor_utils()->can_user_manage( 'course', $course_id ) || ( $topic_id && ! tutor_utils()->can_user_manage( 'topic', $topic_id ) ) ) {
613 wp_send_json_error( array( 'message' => __( 'Access Denied', 'tutor' ) ) );
614 }
615
616 // Create payload to create/update the topic.
617 $post_arr = array(
618 'post_type' => 'topics',
619 'post_title' => $topic_title,
620 'post_content' => $topic_summery,
621 'post_status' => 'publish',
622 'post_author' => get_current_user_id(),
623 'post_parent' => $course_id,
624 'menu_order' => $next_topic_order_id,
625 );
626 $topic_id ? $post_arr['ID'] = $topic_id : 0;
627 $current_topic_id = wp_insert_post( $post_arr );
628
629 ob_start();
630 include tutor()->path . 'views/metabox/course-contents.php';
631
632 wp_send_json_success(
633 array(
634 'topic_title' => $topic_title,
635 'course_contents' => ob_get_clean(),
636 )
637 );
638 }
639
640 /**
641 * Add columns to course row in default WP list table
642 *
643 * @since 1.0.0
644 *
645 * @param array $columns column list.
646 * @return mixed
647 */
648 public function add_column( $columns ) {
649 $date_col = $columns['date'];
650 unset( $columns['date'] );
651 $columns['lessons'] = __( 'Lessons', 'tutor' );
652 $columns['students'] = __( 'Students', 'tutor' );
653 $columns['price'] = __( 'Price', 'tutor' );
654 $columns['date'] = $date_col;
655
656 return $columns;
657 }
658
659 /**
660 * Add data to custom column
661 *
662 * @since 1.0.0
663 *
664 * @param string $column column name.
665 * @param integer $post_id post ID.
666 *
667 * @return void
668 */
669 public function custom_lesson_column( $column, $post_id ) {
670 if ( 'lessons' === $column ) {
671 echo esc_html( tutor_utils()->get_lesson_count_by_course( $post_id ) );
672 }
673
674 if ( 'students' === $column ) {
675 echo esc_html( tutor_utils()->count_enrolled_users_by_course( $post_id ) );
676 }
677
678 if ( 'price' === $column ) {
679 $price = tutor_utils()->get_course_price( $post_id );
680 if ( $price ) {
681 $monetize_by = tutils()->get_option( 'monetize_by' );
682 if ( function_exists( 'wc_price' ) && 'wc' === $monetize_by ) {
683 echo wp_kses(
684 '<span class="tutor-label-success">' . wc_price( $price ) . '</span>',
685 array(
686 'span' => array( 'class' => true ),
687 )
688 );
689 } else {
690 echo wp_kses(
691 '<span class="tutor-label-success">' . $price . '</span>',
692 array( 'span' => array( 'class' => true ) )
693 );
694 }
695 } else {
696 echo esc_html( apply_filters( 'tutor-loop-default-price', __( 'free', 'tutor' ) ) );
697 }
698 }
699 }
700
701
702 /**
703 * Delete a course topic
704 *
705 * @since 1.0.0
706 * @return void
707 */
708 public function tutor_delete_topic() {
709
710 tutor_utils()->checking_nonce();
711
712 global $wpdb;
713 $topic_id = Input::post( 'topic_id', '' );
714
715 if ( ! $topic_id || ! is_numeric( $topic_id ) || ! tutor_utils()->can_user_manage( 'topic', $topic_id ) ) {
716 wp_send_json_error( array( 'message' => 'Access Forbidden' ) );
717 }
718
719 // Assign course ID to orphan content IDs since the topic will be deleted.
720 $course_id = tutor_utils()->get_course_id_by( 'topic', $topic_id );
721 $content_ids = tutor_utils()->get_course_content_ids_by( null, 'topic', $topic_id );
722 foreach ( $content_ids as $content_id ) {
723 update_post_meta( $content_id, '_tutor_course_id_for_lesson', $course_id );
724 // Actually all kind of contents.
725 // This keyword '_tutor_course_id_for_lesson' used just to support backward compatibillity.
726 }
727
728 // Set contents under the topic orphan.
729 $wpdb->update( $wpdb->posts, array( 'post_parent' => 0 ), array( 'post_parent' => $topic_id ) );
730
731 // Then delete the topic from database.
732 $wpdb->delete( $wpdb->postmeta, array( 'post_id' => $topic_id ) );
733 wp_delete_post( $topic_id );
734
735 wp_send_json_success();
736 }
737
738 /**
739 * Delete announcement
740 *
741 * @since 1.0.0
742 * @return void
743 */
744 public function tutor_delete_announcement() {
745 tutor_utils()->checking_nonce( 'get' );
746
747 $announcement_id = Input::get( 'topic_id', 0, Input::TYPE_INT );
748
749 wp_delete_post( $announcement_id );
750 wp_safe_redirect( wp_get_referer() );
751 }
752
753 /**
754 * Handle enroll now action
755 *
756 * @since 1.0.0
757 * @return void
758 */
759 public function enroll_now() {
760
761 // Checking if action comes from Enroll form.
762 // phpcs:ignore WordPress.Security.NonceVerification.Missing
763 if ( tutor_utils()->array_get( 'tutor_course_action', tutor_sanitize_data( $_POST ) ) !== '_tutor_course_enroll_now' || ! isset( $_POST['tutor_course_id'] ) ) {
764 return;
765 }
766
767 // Checking Nonce.
768 tutor_utils()->checking_nonce();
769
770 $user_id = get_current_user_id();
771 if ( ! $user_id ) {
772 exit( esc_html__( 'Please Sign In first', 'tutor' ) );
773 }
774
775 $course_id = Input::post( 'tutor_course_id', 0, Input::TYPE_INT );
776 $user_id = get_current_user_id();
777
778 /**
779 * TODO: need to check purchase information
780 */
781
782 $is_purchasable = tutor_utils()->is_course_purchasable( $course_id );
783
784 /**
785 * If is is not purchasable, it's free, and enroll right now
786 * If purchasable, then process purchase.
787 *
788 * @since: v.1.0.0
789 */
790 if ( $is_purchasable ) { //phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedIf
791 // Process purchase.
792
793 } else {
794 // Free enroll.
795 tutor_utils()->do_enroll( $course_id );
796 }
797
798 $referer_url = wp_get_referer();
799 wp_redirect( $referer_url );
800 }
801
802 /**
803 * Mark complete completed
804 *
805 * @since 1.0.0
806 * @return void
807 */
808 public function mark_course_complete() {
809 $tutor_action = Input::post( 'tutor_action' );
810 $course_id = Input::post( 'course_id', 0, Input::TYPE_INT );
811 if ( 'tutor_complete_course' !== $tutor_action || ! $course_id ) {
812 return;
813 }
814
815 // Checking nonce.
816 tutor_utils()->checking_nonce();
817
818 $user_id = get_current_user_id();
819
820 // TODO: need to show view if not signed_in.
821 if ( ! $user_id ) {
822 die( esc_html__( 'Please Sign-In', 'tutor' ) );
823 }
824
825 CourseModel::mark_course_as_completed( $course_id, $user_id );
826
827 $permalink = get_the_permalink( $course_id );
828
829 // Set temporary identifier to show review pop up.
830 if ( get_tutor_option( 'enable_course_review' ) ) {
831 $rating = tutor_utils()->get_course_rating_by_user( $course_id, $user_id );
832 if ( ! $rating || ( empty( $rating->rating ) && empty( $rating->review ) ) ) {
833 update_option(
834 'tutor_course_complete_popup_' . $user_id,
835 array(
836 'course_id' => $course_id,
837 'course_url' => $permalink,
838 'expires' => time() + 10,
839 )
840 );
841 }
842 }
843
844 wp_redirect( $permalink );
845 exit;
846 }
847
848 /**
849 * Popup review form
850 *
851 * @since 1.0.0
852 * @return void
853 */
854 public function popup_review_form() {
855 if ( is_user_logged_in() ) {
856 $key = 'tutor_course_complete_popup_' . get_current_user_id();
857 $popup = get_option( $key );
858
859 if ( is_array( $popup ) ) {
860
861 if ( $popup['expires'] > time() ) {
862 $course_id = $popup['course_id'];
863 include tutor()->path . 'views/modal/review.php';
864 }
865
866 delete_option( $key );
867 }
868 }
869 }
870
871 /**
872 * Delete course delete from frontend dashboard
873 *
874 * @since 2.0.0
875 * @return void
876 */
877 public function tutor_delete_dashboard_course() {
878 tutor_utils()->checking_nonce();
879
880 $course_id = Input::post( 'course_id', 0, Input::TYPE_INT );
881 if ( ! tutor_utils()->can_user_manage( 'course', $course_id ) ) {
882 wp_send_json_error( array( 'message' => __( 'Access Denied', 'tutor' ) ) );
883 }
884
885 /**
886 * Co-instructor can not delete a course
887 * @since 2.1.6
888 */
889 if ( false === CourseModel::is_main_instructor( $course_id ) ) {
890 wp_send_json_error( array( 'message' => __( 'Only main instructor can delete this course', 'tutor' ) ) );
891 }
892
893 CourseModel::delete_course( $course_id );
894 wp_send_json_success();
895 }
896
897 /**
898 * Main author change from gutenberg editor
899 *
900 * @since 2.0.0
901 *
902 * @param array $data data.
903 * @param array $postarr post array.
904 *
905 * @return mixed
906 */
907 public function tutor_add_gutenberg_author( $data, $postarr ) {
908 $gutenberg_enabled = tutor_utils()->get_option( 'enable_gutenberg_course_edit' );
909 $post_type = $postarr['post_type'];
910 $courses_post_type = tutor()->course_post_type;
911
912 if ( false === $gutenberg_enabled && $post_type !== $courses_post_type ) {
913 return $data;
914 }
915
916 /**
917 * Only admin can change main author
918 */
919 if ( $courses_post_type === $post_type && ! current_user_can( 'administrator' ) ) {
920 global $wpdb;
921 $post_ID = (int) tutor_utils()->avalue_dot( 'ID', $postarr );
922 $post_author = (int) $wpdb->get_var( $wpdb->prepare( "SELECT post_author FROM {$wpdb->posts} WHERE ID = %d ", $post_ID ) );
923
924 if ( $post_author > 0 ) {
925 $data['post_author'] = $post_author;
926 } else {
927 $data['post_author'] = get_current_user_id();
928 }
929 }
930
931 return $data;
932 }
933
934
935 /**
936 * Attach product with course when course save from frontend or backend.
937 *
938 * @since 1.3.4
939 *
940 * @param integer $post_ID course ID.
941 * @param array $post_data cretaed course post details.
942 *
943 * @return void
944 */
945 public function attach_product_with_course( $post_ID, $post_data ) {
946
947 $monetize_by = tutor_utils()->get_option( 'monetize_by' );
948
949 /**
950 * The function is_admin will check only loaded page from WP admin.
951 * It does not check any role
952 */
953 $is_admin_panel = is_admin();
954 // From backend course select box.
955 $product_id = Input::post( '_tutor_course_product_id', 0, Input::TYPE_INT );
956
957 /**
958 * From Admin Panel, Free user can only select product from dropdown
959 */
960 if ( $is_admin_panel && 'wc' === $monetize_by && tutor()->has_pro === false ) {
961 if ( $product_id > 0 ) {
962 update_post_meta( $post_ID, '_tutor_course_product_id', $product_id );
963 } elseif ( -1 === $product_id ) {
964 delete_post_meta( $post_ID, '_tutor_course_product_id' );
965 }
966
967 return;
968 }
969
970 $attached_product_id = tutor_utils()->get_course_product_id( $post_ID );
971 $course_price = Input::post( 'course_price', 0, Input::TYPE_NUMERIC );
972 $sale_price = Input::post( 'course_sale_price', 0, Input::TYPE_NUMERIC );
973
974 if ( ! $course_price || $sale_price >= $course_price ) {
975 return;
976 }
977
978 $course = get_post( $post_ID );
979
980 if ( 'wc' === $monetize_by ) {
981
982 $is_update = false;
983 if ( $attached_product_id ) {
984 $wc_product = get_post_meta( $attached_product_id, '_product_version', true );
985 if ( $wc_product ) {
986 $is_update = true;
987 }
988 }
989
990 if ( $is_update || ( $product_id > 0 && $is_admin_panel ) ) {
991 // Added in @since 2.0.7.
992 if ( $product_id > 0 && $is_admin_panel ) {
993 $attached_product_id = $product_id;
994 update_post_meta( $post_ID, '_tutor_course_product_id', $product_id );
995 }
996
997 $product_obj = wc_get_product( $attached_product_id );
998 $product_obj->set_price( $course_price ); // Set product price.
999 $product_obj->set_regular_price( $course_price ); // Set product regular price.
1000
1001 if ( $sale_price > 0 ) {
1002 $product_obj->set_sale_price( $sale_price );
1003 } else {
1004 // When use remove sale price ( discounted price ).
1005 $product_obj->set_sale_price( null );
1006 }
1007
1008 $product_obj->set_sold_individually( true );
1009 $product_id = $product_obj->save();
1010 if ( $product_obj->is_type( 'subscription' ) ) {
1011 update_post_meta( $attached_product_id, '_subscription_price', $course_price );
1012 }
1013 } else {
1014 $product_obj = new \WC_Product();
1015 $product_obj->set_name( $course->post_title );
1016 $product_obj->set_status( 'publish' );
1017 $product_obj->set_price( $course_price ); // Set product price.
1018 $product_obj->set_regular_price( $course_price ); // Set product regular price.
1019
1020 if ( $sale_price > 0 ) {
1021 $product_obj->set_sale_price( $sale_price );
1022 }
1023
1024 $product_obj->set_sold_individually( true );
1025
1026 $product_id = $product_obj->save();
1027 if ( $product_id ) {
1028 update_post_meta( $post_ID, '_tutor_course_product_id', $product_id );
1029 // Mark product for woocommerce.
1030 update_post_meta( $product_id, '_virtual', 'yes' );
1031 update_post_meta( $product_id, '_tutor_product', 'yes' );
1032
1033 $course_post_thumbnail = get_post_meta( $post_ID, '_thumbnail_id', true );
1034 if ( $course_post_thumbnail ) {
1035 set_post_thumbnail( $product_id, $course_post_thumbnail );
1036 }
1037 }
1038 }
1039 } elseif ( 'edd' === $monetize_by ) {
1040
1041 $is_update = false;
1042
1043 if ( $attached_product_id ) {
1044 $edd_price = get_post_meta( $attached_product_id, 'edd_price', true );
1045 if ( $edd_price ) {
1046 $is_update = true;
1047 }
1048 }
1049
1050 if ( $is_update ) {
1051 // Update the product.
1052 update_post_meta( $attached_product_id, 'edd_price', $course_price );
1053 } else {
1054 // Create new product.
1055
1056 $post_arr = array(
1057 'post_type' => 'download',
1058 'post_title' => $course->post_title,
1059 'post_status' => 'publish',
1060 'post_author' => get_current_user_id(),
1061 );
1062 $download_id = wp_insert_post( $post_arr );
1063 if ( $download_id ) {
1064 // EDD edd_price.
1065 update_post_meta( $download_id, 'edd_price', $course_price );
1066
1067 update_post_meta( $post_ID, '_tutor_course_product_id', $download_id );
1068 // Mark product for EDD.
1069 update_post_meta( $download_id, '_tutor_product', 'yes' );
1070
1071 $course_post_thumbnail = get_post_meta( $post_ID, '_thumbnail_id', true );
1072 if ( $course_post_thumbnail ) {
1073 set_post_thumbnail( $download_id, $course_post_thumbnail );
1074 }
1075 }
1076 }
1077 }
1078 }
1079
1080 /**
1081 * Add Course level to course settings
1082 *
1083 * @since 1.4.1
1084 *
1085 * @param array $args arguments.
1086 * @return array
1087 */
1088 public function add_course_level_to_settings( $args ) {
1089 $course_id = get_the_ID();
1090 $levels = tutor_utils()->course_levels();
1091 $course_level = get_post_meta( $course_id, '_tutor_course_level', true );
1092
1093 $args['general']['fields']['_tutor_course_level'] = array(
1094 'type' => 'select',
1095 'label' => __( 'Difficulty Level', 'tutor' ),
1096 'label_title' => __( 'Enable', 'tutor' ),
1097 'options' => $levels,
1098 'value' => $course_level ? $course_level : 'intermediate',
1099 'desc' => __( 'Course difficulty level', 'tutor' ),
1100 );
1101
1102 return $args;
1103 }
1104
1105 /**
1106 * Check if course starting
1107 *
1108 * @since 1.4.8
1109 * @return void
1110 */
1111 public function tutor_lesson_load_before() {
1112 $course_id = tutor_utils()->get_course_id_by_content( get_the_ID() );
1113 $completed_lessons = tutor_utils()->get_completed_lesson_count_by_course( $course_id );
1114 if ( is_user_logged_in() ) {
1115 $is_course_started = get_post_meta( $course_id, '_tutor_course_started', true );
1116 if ( ! $completed_lessons && ! $is_course_started ) {
1117 update_post_meta( $course_id, '_tutor_course_started', tutor_time() );
1118 do_action( 'tutor/course/started', $course_id );
1119 }
1120 }
1121 }
1122
1123 /**
1124 * Add Course level to course settings
1125 *
1126 * @since 1.4.8
1127 * @return void
1128 */
1129 public function course_elements_enable_disable() {
1130 add_filter( 'tutor_course/single/completing-progress-bar', array( $this, 'enable_disable_course_progress_bar' ) );
1131 add_filter( 'tutor_course/single/material_includes', array( $this, 'enable_disable_material_includes' ) );
1132 add_filter( 'tutor_course/single/content', array( $this, 'enable_disable_course_content' ) );
1133 add_filter( 'tutor_course/single/benefits_html', array( $this, 'enable_disable_course_benefits' ) );
1134 add_filter( 'tutor_course/single/requirements_html', array( $this, 'enable_disable_course_requirements' ) );
1135 add_filter( 'tutor_course/single/audience_html', array( $this, 'enable_disable_course_target_audience' ) );
1136 add_filter( 'tutor_course/single/nav_items', array( $this, 'enable_disable_course_nav_items' ), 999, 2 );
1137 }
1138
1139 /**
1140 * Enable disable course progress bar
1141 *
1142 * @since 1.4.8
1143 *
1144 * @param string $html HTML string.
1145 * @return string
1146 */
1147 public function enable_disable_course_progress_bar( $html ) {
1148 $disable_option = ! (bool) tutor_utils()->get_option( 'enable_course_progress_bar', true, true );
1149 if ( $disable_option ) {
1150 return '';
1151 }
1152 return $html;
1153 }
1154
1155 /**
1156 * Enable disable material includes
1157 *
1158 * @since 1.4.8
1159 *
1160 * @param string $html HTML string.
1161 * @return string
1162 */
1163 public function enable_disable_material_includes( $html ) {
1164 $disable_option = ! (bool) get_tutor_option( 'enable_course_material', true, true );
1165 if ( $disable_option ) {
1166 return '';
1167 }
1168 return $html;
1169 }
1170
1171 /**
1172 * Enable disable course content
1173 *
1174 * @since 1.4.8
1175 *
1176 * @param string $html HTML string.
1177 * @return string
1178 */
1179 public function enable_disable_course_content( $html ) {
1180 $disable_option = ! (bool) tutor_utils()->get_option( 'enable_course_description', true, true );
1181 if ( $disable_option ) {
1182 return '';
1183 }
1184 return $html;
1185 }
1186
1187 /**
1188 * Enable disable course benefits
1189 *
1190 * @since 1.4.8
1191 *
1192 * @param string $html HTML string.
1193 * @return string
1194 */
1195 public function enable_disable_course_benefits( $html ) {
1196 $disable_option = ! (bool) tutor_utils()->get_option( 'enable_course_benefits', true, true );
1197 if ( $disable_option ) {
1198 return '';
1199 }
1200 return $html;
1201 }
1202
1203 /**
1204 * Enable disable course requirements
1205 *
1206 * @since 1.4.8
1207 *
1208 * @param string $html HTML string.
1209 * @return string
1210 */
1211 public function enable_disable_course_requirements( $html ) {
1212 $disable_option = ! (bool) tutor_utils()->get_option( 'enable_course_requirements', true, true );
1213 if ( $disable_option ) {
1214 return '';
1215 }
1216 return $html;
1217 }
1218
1219 /**
1220 * Enable disable course target audience
1221 *
1222 * @since 1.4.8
1223 *
1224 * @param string $html HTML string.
1225 * @return string
1226 */
1227 public function enable_disable_course_target_audience( $html ) {
1228 $disable_option = ! (bool) tutor_utils()->get_option( 'enable_course_target_audience', true, true );
1229 if ( $disable_option ) {
1230 return '';
1231 }
1232 return $html;
1233 }
1234
1235 /**
1236 * Enable disable course nav items
1237 *
1238 * @since 1.4.8
1239 *
1240 * @param array $items item list.
1241 * @param integer $course_id course ID.
1242 *
1243 * @return array
1244 */
1245 public function enable_disable_course_nav_items( $items, $course_id ) {
1246 global $wp_query, $post;
1247 $enable_q_and_a_on_course = (bool) get_tutor_option( 'enable_q_and_a_on_course' );
1248 $disable_course_announcements = ! (bool) tutor_utils()->get_option( 'enable_course_announcements', true, true );
1249 $disable_qa_for_this_course = ( $wp_query->is_single && ! empty( $post ) ) ? get_post_meta( $post->ID, '_tutor_enable_qa', true ) != 'yes' : false;
1250
1251 // Whether Q&A enabled.
1252 if ( ! $enable_q_and_a_on_course || $disable_qa_for_this_course ) {
1253 if ( tutor_utils()->array_get( 'questions', $items ) ) {
1254 unset( $items['questions'] );
1255 }
1256 }
1257
1258 // Whether announcment enabled.
1259 if ( $disable_course_announcements ) {
1260 if ( tutor_utils()->array_get( 'announcements', $items ) ) {
1261 unset( $items['announcements'] );
1262 }
1263 }
1264
1265 // Hide review section if disabled.
1266 if ( ! get_tutor_option( 'enable_course_review' ) ) {
1267 unset( $items['reviews'] );
1268 }
1269
1270 // Whether enrollment require.
1271 $is_enrolled = tutor_utils()->is_enrolled();
1272
1273 return array_filter(
1274 $items,
1275 function( $item ) use ( $is_enrolled ) {
1276 if ( isset( $item['require_enrolment'] ) && $item['require_enrolment'] ) {
1277 return $is_enrolled;
1278 }
1279 return true;
1280 }
1281 );
1282 }
1283
1284 /**
1285 * Filter product in shop page
1286 *
1287 * @since 1.4.9
1288 * @return void|null
1289 */
1290 public function filter_product_in_shop_page() {
1291 $hide_course_from_shop_page = (bool) get_tutor_option( 'hide_course_from_shop_page' );
1292 if ( ! $hide_course_from_shop_page ) {
1293 return;
1294 }
1295 add_action( 'woocommerce_product_query', array( $this, 'filter_woocommerce_product_query' ) );
1296 add_filter( 'edd_downloads_query', array( $this, 'filter_edd_downloads_query' ), 10, 2 );
1297 add_action( 'pre_get_posts', array( $this, 'filter_archive_meta_query' ), 1 );
1298 }
1299
1300 /**
1301 * Tutor product meta query
1302 *
1303 * @since 1.4.9
1304 * @return array
1305 */
1306 public function tutor_product_meta_query() {
1307 $meta_query = array(
1308 'key' => '_tutor_product',
1309 'compare' => 'NOT EXISTS',
1310 );
1311 return $meta_query;
1312 }
1313
1314 /**
1315 * Filter product in woocommerce shop page
1316 *
1317 * @since 1.4.9
1318 *
1319 * @param \WP_Query $wp_query WP Query instance.
1320 * @return \WP_Query
1321 */
1322 public function filter_woocommerce_product_query( $wp_query ) {
1323 $wp_query->set( 'meta_query', array( $this->tutor_product_meta_query() ) );
1324 return $wp_query;
1325 }
1326
1327 /**
1328 * Filter product in edd downloads shortcode page
1329 *
1330 * @since 1.4.9
1331 *
1332 * @param \WP_Query $query WP Query instance.
1333 * @return \WP_Query
1334 */
1335 public function filter_edd_downloads_query( $query ) {
1336 $query['meta_query'][] = $this->tutor_product_meta_query();
1337 return $query;
1338 }
1339
1340 /**
1341 * Filter product in edd downloads archive page
1342 *
1343 * @since 1.4.9
1344 *
1345 * @param \WP_Query $wp_query WP Query instance.
1346 * @return \WP_Query
1347 */
1348 public function filter_archive_meta_query( $wp_query ) {
1349 if ( ! is_admin() && $wp_query->is_archive && $wp_query->get( 'post_type' ) === 'download' ) {
1350 $wp_query->set( 'meta_query', array( $this->tutor_product_meta_query() ) );
1351 }
1352 return $wp_query;
1353 }
1354
1355 /**
1356 * Removed course price if already enrolled at single course
1357 *
1358 * @since 1.5.8
1359 *
1360 * @param string $html HTML string.
1361 * @return string
1362 */
1363 public function remove_price_if_enrolled( $html ) {
1364 $should_removed = apply_filters( 'should_remove_price_if_enrolled', true );
1365
1366 if ( $should_removed ) {
1367 $course_id = get_the_ID();
1368 $enrolled = tutor_utils()->is_enrolled( $course_id );
1369 if ( $enrolled ) {
1370 $html = '';
1371 }
1372 }
1373 return $html;
1374 }
1375
1376 /**
1377 * Check if all lessons and quizzes done before mark course complete.
1378 *
1379 * @since 1.5.8
1380 *
1381 * @param string $html HTML string.
1382 * @return string
1383 */
1384 public function tutor_lms_hide_course_complete_btn( $html ) {
1385
1386 $completion_mode = tutor_utils()->get_option( 'course_completion_process' );
1387 if ( 'strict' !== $completion_mode ) {
1388 return $html;
1389 }
1390
1391 $completed_lesson = tutor_utils()->get_completed_lesson_count_by_course();
1392 $lesson_count = tutor_utils()->get_lesson_count_by_course();
1393
1394 if ( $completed_lesson < $lesson_count ) {
1395 return '<div class="tutor-alert tutor-warning tutor-mt-28">
1396 <div class="tutor-alert-text">
1397 <span class="tutor-alert-icon tutor-fs-4 tutor-icon-circle-info tutor-mr-12"></span>
1398 <span>' . __( 'Complete all lessons to mark this course as complete', 'tutor' ) . '</span>
1399 </div>
1400 </div>';
1401 }
1402
1403 $quizzes = array();
1404 $assignments = array();
1405
1406 $course_contents = tutor_utils()->get_course_contents_by_id();
1407 if ( tutor_utils()->count( $course_contents ) ) {
1408 foreach ( $course_contents as $content ) {
1409 if ( 'tutor_quiz' === $content->post_type ) {
1410 $quizzes[] = $content;
1411 }
1412 if ( 'tutor_assignments' === $content->post_type ) {
1413 $assignments[] = $content;
1414 }
1415 }
1416 }
1417
1418 $required_assignment_pass = 0;
1419
1420 foreach ( $assignments as $row ) {
1421
1422 $submitted_assignment = tutor_utils()->is_assignment_submitted( $row->ID );
1423 $is_reviewed_by_instructor = null === $submitted_assignment
1424 ? false
1425 : get_comment_meta( $submitted_assignment->comment_ID, 'evaluate_time', true );
1426
1427 if ( $submitted_assignment && $is_reviewed_by_instructor ) {
1428 $pass_mark = tutor_utils()->get_assignment_option( $submitted_assignment->comment_post_ID, 'pass_mark' );
1429 $given_mark = get_comment_meta( $submitted_assignment->comment_ID, 'assignment_mark', true );
1430
1431 if ( $given_mark < $pass_mark ) {
1432 $required_assignment_pass++;
1433 }
1434 } else {
1435 $required_assignment_pass++;
1436 }
1437 }
1438
1439 $is_quiz_pass = true;
1440 $required_quiz_pass = 0;
1441
1442 if ( tutor_utils()->count( $quizzes ) ) {
1443 foreach ( $quizzes as $quiz ) {
1444
1445 $attempt = tutor_utils()->get_quiz_attempt( $quiz->ID );
1446 if ( $attempt ) {
1447 $passing_grade = tutor_utils()->get_quiz_option( $quiz->ID, 'passing_grade', 0 );
1448 $earned_percentage = $attempt->earned_marks > 0 ? ( number_format( ( $attempt->earned_marks * 100 ) / $attempt->total_marks ) ) : 0;
1449
1450 if ( $earned_percentage < $passing_grade ) {
1451 $required_quiz_pass++;
1452 $is_quiz_pass = false;
1453 }
1454 } else {
1455 $required_quiz_pass++;
1456 $is_quiz_pass = false;
1457 }
1458 }
1459 }
1460
1461 if ( ! $is_quiz_pass || $required_assignment_pass > 0 ) {
1462 $_msg = '';
1463 $quiz_str = _n( 'quiz', 'quizzes', $required_quiz_pass, 'tutor' );
1464 $assignment_str = _n( 'assignment', 'assignments', $required_assignment_pass, 'tutor' );
1465
1466 if ( ! $is_quiz_pass && 0 == $required_assignment_pass ) {
1467 /* translators: %s: number of quiz pass required */
1468 $_msg = sprintf( __( 'You have to pass %1$s %2$s to complete this course.', 'tutor' ), $required_quiz_pass, $quiz_str );
1469 }
1470 if ( $is_quiz_pass && $required_assignment_pass > 0 ) {
1471 /* translators: %s: number of assignment pass required */
1472 $_msg = sprintf( __( 'You have to pass %1$s %2$s to complete this course.', 'tutor' ), $required_assignment_pass, $assignment_str );
1473 }
1474 if ( ! $is_quiz_pass && $required_assignment_pass > 0 ) {
1475 /* translators: %s: number of quiz pass required */
1476 $_msg = sprintf( __( 'You have to pass %1$s %2$s and %3$s %4$s to complete this course.', 'tutor' ), $required_quiz_pass, $quiz_str, $required_assignment_pass, $assignment_str );
1477 }
1478
1479 return '<div class="tutor-alert tutor-warning tutor-mt-28">
1480 <div class="tutor-alert-text">
1481 <span class="tutor-alert-icon tutor-fs-4 tutor-icon-circle-info tutor-mr-12"></span>
1482 <span>' . $_msg . '</span>
1483 </div>
1484 </div>';
1485 }
1486
1487 return $html;
1488 }
1489
1490 /**
1491 * Generate Gradebook
1492 *
1493 * @since 1.5.8
1494 *
1495 * @param string $html HTML string.
1496 * @return string
1497 */
1498 public function get_generate_greadbook( $html ) {
1499 if ( ! tutor_utils()->is_completed_course() ) {
1500 return '';
1501 }
1502 return $html;
1503 }
1504
1505 /**
1506 * Add social share content in header
1507 *
1508 * @since 1.6.3
1509 * @return void
1510 */
1511 public function social_share_content() {
1512 global $wp_query, $post;
1513 if ( $wp_query->is_single && ! empty( $wp_query->query_vars['post_type'] ) && $wp_query->query_vars['post_type'] === $this->course_post_type ) { ?>
1514 <!--Facebook-->
1515 <meta property="og:type" content="website"/>
1516 <meta property="og:image" content="<?php echo esc_url( get_tutor_course_thumbnail_src() ); ?>" />
1517 <meta property="og:description" content="<?php echo esc_html( $post->post_content ); ?>" />
1518 <!--Twitter-->
1519 <meta name="twitter:image" content="<?php echo esc_url( get_tutor_course_thumbnail_src() ); ?>">
1520 <meta name="twitter:description" content="<?php echo esc_html( $post->post_content ); ?>">
1521 <!--Google+-->
1522 <meta itemprop="image" content="<?php echo esc_url( get_tutor_course_thumbnail_src() ); ?>">
1523 <meta itemprop="description" content="<?php echo esc_html( $post->post_content ); ?>">
1524 <?php
1525 }
1526 }
1527
1528 /**
1529 * Delete associated enrollment
1530 *
1531 * @since 1.8.2
1532 *
1533 * @param integer $post_id post ID.
1534 * @return void
1535 */
1536 public function delete_associated_enrollment( $post_id ) {
1537 global $wpdb;
1538
1539 $enroll_id = $wpdb->get_var(
1540 $wpdb->prepare(
1541 "SELECT
1542 post_id
1543 FROM
1544 {$wpdb->postmeta}
1545 WHERE
1546 meta_key='_tutor_enrolled_by_order_id'
1547 AND meta_value = %d
1548 ",
1549 $post_id
1550 )
1551 );
1552
1553 if ( is_numeric( $enroll_id ) && $enroll_id > 0 ) {
1554
1555 $course_id = get_post_field( 'post_parent', $enroll_id );
1556 $user_id = get_post_field( 'post_author', $enroll_id );
1557
1558 tutor_utils()->cancel_course_enrol( $course_id, $user_id );
1559 }
1560 }
1561
1562 /**
1563 * Reset course progress.
1564 *
1565 * @since 1.5.8
1566 * @return void
1567 */
1568 public function tutor_reset_course_progress() {
1569 tutor_utils()->checking_nonce();
1570 $course_id = Input::post( 'course_id' );
1571
1572 if ( ! $course_id || ! is_numeric( $course_id ) || ! tutor_utils()->is_enrolled( $course_id ) ) {
1573 wp_send_json_error( array( 'message' => __( 'Invalid Course ID or Access Denied.', 'tutor' ) ) );
1574 return;
1575 }
1576
1577 tutor_utils()->delete_course_progress( $course_id );
1578 wp_send_json_success( array( 'redirect_to' => tutor_utils()->get_course_first_lesson( $course_id ) ) );
1579 }
1580
1581 /**
1582 * Do enroll if guest attempt to enroll and course is free
1583 *
1584 * @since 1.9.8
1585 *
1586 * @param integer $course_id course ID.
1587 * @param integer $user_id user ID.
1588
1589 * @return void
1590 */
1591 public function enroll_after_login_if_attempt( int $course_id, int $user_id ) {
1592 $course_id = sanitize_text_field( $course_id );
1593 if ( $course_id ) {
1594 $is_purchasable = tutor_utils()->is_course_purchasable( $course_id );
1595 if ( ! $is_purchasable ) {
1596 tutor_utils()->do_enroll( $course_id, $order_id = 0, $user_id );
1597 do_action( 'guest_attempt_after_enrollment', $course_id );
1598 }
1599 }
1600 }
1601
1602 /**
1603 * Handle course enrollment
1604 *
1605 * @since 2.1.0
1606 * @return void
1607 */
1608 public function course_enrollment() {
1609 tutor_utils()->checking_nonce();
1610
1611 $course_id = Input::post( 'course_id', 0, Input::TYPE_INT );
1612 $user_id = get_current_user_id();
1613
1614 if ( $course_id ) {
1615 $enroll = tutor_utils()->do_enroll( $course_id, 0, $user_id );
1616 if ( $enroll ) {
1617 wp_send_json_success( __( 'Enrollment successfully done!', 'tutor' ) );
1618 } else {
1619 wp_send_json_error( __( 'Enrollment failed, please try again!', 'tutor' ) );
1620 }
1621 } else {
1622 wp_send_json_error( __( 'Invalid course ID', 'tutor' ) );
1623 }
1624 }
1625 }
1626