PluginProbe
Tutor LMS – eLearning and online course solution / 2.0.1
Tutor LMS – eLearning and online course solution v2.0.1
4.0.7 4.0.6 4.0.5 4.0.4 4.0.3 4.0.2 4.0.1 4.0.0 3.9.15 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 All 191 releases
tutor / classes / Course.php

Course.php in Tutor LMS – eLearning and online course solution 2.0.1, at classes/Course.php

1,306 lines 38.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 namespace TUTOR;
3
4 if ( ! defined( 'ABSPATH' ) ) {
5 exit;
6 }
7
8 class Course extends Tutor_Base {
9
10 private $additional_meta=array(
11 '_tutor_enable_qa',
12 '_tutor_is_public_course'
13 );
14
15 public function __construct() {
16 parent::__construct();
17
18 add_action('add_meta_boxes', array($this, 'register_meta_box') );
19 add_action('save_post_'.$this->course_post_type, array($this, 'save_course_meta'), 10, 2);
20 add_action('wp_ajax_tutor_save_topic', array($this, 'tutor_save_topic'));
21
22 //Add Column
23 add_filter( "manage_{$this->course_post_type}_posts_columns", array($this, 'add_column'), 10,1 );
24 add_action( "manage_{$this->course_post_type}_posts_custom_column" , array($this, 'custom_lesson_column'), 10, 2 );
25
26 add_action('wp_ajax_tutor_delete_topic', array($this, 'tutor_delete_topic'));
27 add_action('admin_action_tutor_delete_announcement', array($this, 'tutor_delete_announcement'));
28
29 // Frontend Action
30 add_action( 'template_redirect', array( $this, 'enroll_now' ) );
31 add_action( 'init', array( $this, 'mark_course_complete' ) );
32
33 /**
34 * Frontend Dashboard
35 */
36 add_action( 'wp_ajax_tutor_delete_dashboard_course', array( $this, 'tutor_delete_dashboard_course' ) );
37
38 /**
39 * Gutenberg author support
40 */
41 add_filter( 'wp_insert_post_data', array( $this, 'tutor_add_gutenberg_author' ), '99', 2 );
42
43 /**
44 * Frontend metabox supports for course builder
45 *
46 * @since v.1.3.4
47 */
48 add_action( 'tutor/dashboard_course_builder_form_field_after', array( $this, 'register_meta_box_in_frontend' ) );
49
50 /**
51 * Do Stuff for the course save from frontend
52 */
53 add_action( 'save_tutor_course', array( $this, 'attach_product_with_course' ), 10, 2 );
54
55 /**
56 * Add course level to course settings
57 *
58 * @since v.1.4.1
59 */
60 add_filter( 'tutor_course_settings_tabs', array($this, 'add_course_level_to_settings'));
61
62 /**
63 * Enable Disable Course Details Page Feature
64 *
65 * @since v.1.4.8
66 */
67 $this->course_elements_enable_disable();
68
69 /**
70 * @since v.1.4.8
71 * Check if course starting, set meta if starting
72 */
73 add_action( 'tutor_lesson_load_before', array( $this, 'tutor_lesson_load_before' ) );
74
75 /**
76 * @since v.1.4.9
77 * Filter product in shop page
78 */
79 $this->filter_product_in_shop_page();
80
81 /**
82 * Remove the course price if enrolled
83 * @since 1.5.8
84 */
85 add_filter('tutor_course_price', array($this, 'remove_price_if_enrolled'));
86
87 /**
88 * Remove course complete button if course completion is strict mode
89 * @since v.1.6.1
90 */
91 add_filter('tutor_course/single/complete_form', array($this, 'tutor_lms_hide_course_complete_btn'));
92 add_filter('get_gradebook_generate_form_html', array($this, 'get_generate_greadbook'));
93
94 /**
95 * Add social share content in header
96 * @since v.1.6.3
97 */
98 add_action('wp_head', array($this, 'social_share_content'));
99
100 /**
101 * Delete course data after deleted course
102 * @since v.1.6.6
103 */
104 add_action('deleted_post', array($this, 'delete_tutor_course_data'));
105
106 /**
107 * Delete course data after deleted course
108 *
109 * @since v.1.8.2
110 */
111 add_action( 'before_delete_post', array( $this, 'delete_associated_enrollment' ) );
112
113 /**
114 * Show only own uploads in media library if user is instructor
115 *
116 * @since v1.8.9
117 */
118 add_filter( 'posts_where', array( $this, 'restrict_media' ) );
119
120 /**
121 * Restrict new enrol/purchase button if course member limit reached
122 *
123 * @since v1.9.0
124 */
125 add_filter( 'tutor_course_restrict_new_entry', array( $this, 'restrict_new_student_entry' ) );
126
127 /**
128 * Reset course progress on retake
129 *
130 * @since v1.9.5
131 */
132 add_action( 'wp_ajax_tutor_reset_course_progress', array( $this, 'tutor_reset_course_progress' ) );
133
134 /**
135 * Popup for review
136 *
137 * @since v1.9.7
138 */
139 add_action( 'wp_footer', array( $this, 'popup_review_form' ) );
140
141 /**
142 * Do enroll after login if guest take enroll attempt
143 *
144 * @since 1.9.8
145 */
146 add_action( 'tutor_do_enroll_after_login_if_attempt', array( $this, 'enroll_after_login_if_attempt' ), 10, 2 );
147
148 add_action( 'wp_ajax_tutor_update_course_content_order', array($this, 'tutor_update_course_content_order') );
149 }
150
151 public function tutor_update_course_content_order() {
152 tutor_utils()->checking_nonce();
153
154 if(isset($_POST['content_parent'])) {
155 $topic_id = (int)tutor_utils()->array_get('parent_topic_id', $_POST['content_parent']);
156 $content_id = (int)tutor_utils()->array_get('content_id', $_POST['content_parent']);
157
158 if(!tutor_utils()->can_user_manage('topic', $topic_id)) {
159 wp_send_json_success(array('message' => __('Access Denied!', 'tutor')));
160 exit;
161 }
162
163 // Update the parent topic id of the content
164 global $wpdb;
165 $wpdb->update($wpdb->posts, array( 'post_parent' => $topic_id ), array( 'ID' => $content_id ));
166 }
167
168 // Save course content order
169 $this->save_course_content_order();
170
171 wp_send_json_success();
172 }
173
174 public function restrict_new_student_entry($content) {
175
176 if(!tutor_utils()->is_course_fully_booked()) {
177 // No restriction if not fully booked
178 return $content;
179 }
180
181 return '<div class="list-item-booking booking-full tutor-d-flex tutor-align-items-center"><div class="booking-progress tutor-d-flex"><span class="tutor-icon-24 tutor-mr-4 tutor-color-design-warning tutor-icon-circle-outline-info-filled"></span></div><div class="tutor-fs-7 tutor-fw-medium tutor-color-black">Fully Booked</div></div>';
182 }
183
184 function restrict_media( $where ) {
185
186 if ( isset( $_POST['action'] ) && $_POST['action'] == 'query-attachments' && tutor_utils()->is_instructor() ) {
187 if ( ! tutor_utils()->has_user_role( array( 'administrator', 'editor' ) ) ) {
188 $where .= ' AND post_author=' . get_current_user_id();
189 }
190 }
191
192 return $where;
193 }
194
195 /**
196 * Registering metabox
197 */
198 public function register_meta_box(){
199 $coursePostType = tutor()->course_post_type;
200
201 add_meta_box( 'tutor-course-topics', __( 'Course Builder', 'tutor' ), array($this, 'course_meta_box'), $coursePostType );
202 add_meta_box( 'tutor-course-additional-data', __( 'Additional Data', 'tutor' ), array($this, 'course_additional_data_meta_box'), $coursePostType );
203 add_meta_box( 'tutor-course-videos', __( 'Video', 'tutor' ), array($this, 'video_metabox'), $coursePostType );
204 }
205
206 public function course_meta_box( $echo = true ) {
207 ob_start();
208 include tutor()->path . 'views/metabox/course-topics.php';
209 $content = ob_get_clean();
210
211 if ( $echo ) {
212 // echo tutor_kses_html( $content ); Doesn't support SVG. It is restored in version 2 and we've got rid of SVG and used icon font instead.
213 echo $content;
214 } else {
215 return $content;
216 }
217 }
218
219 public function course_additional_data_meta_box( $echo = true ) {
220
221 ob_start();
222 include tutor()->path . 'views/metabox/course-additional-data.php';
223 $content = ob_get_clean();
224
225 if ( $echo ) {
226 echo tutor_kses_html( $content );
227 } else {
228 return $content;
229 }
230 }
231
232 public function video_metabox( $echo = true ) {
233 ob_start();
234 include tutor()->path . 'views/metabox/video-metabox.php';
235 $content = ob_get_clean();
236
237 if ( $echo ) {
238 echo tutor_kses_html( $content ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
239 } else {
240 return $content;
241 }
242 }
243
244 /**
245 * Register metabox in course builder tutor
246 *
247 * @since v.1.3.4
248 */
249 public function register_meta_box_in_frontend(){
250 global $post;
251
252 do_action('tutor_course_builder_metabox_before', get_the_ID());
253
254 course_builder_section_wrap($this->video_metabox($echo = false), __( 'Video', 'tutor' ) );
255 do_action('tutor/frontend_course_edit/after/video', $post);
256
257 course_builder_section_wrap($this->course_meta_box($echo = false), __( 'Course Builder', 'tutor' ) );
258 do_action('tutor/frontend_course_edit/after/course_builder', $post);
259
260 course_builder_section_wrap($this->course_additional_data_meta_box($echo = false), __( 'Additional Data', 'tutor' ) );
261 do_action('tutor/frontend_course_edit/after/additional_data', $post);
262
263 do_action('tutor_course_builder_metabox_after', get_the_ID());
264 }
265
266 private function save_course_content_order(){
267 global $wpdb;
268
269 if ( ! empty( $_POST['tutor_topics_lessons_sorting'] ) ) {
270 $new_order = sanitize_text_field( stripslashes( $_POST['tutor_topics_lessons_sorting'] ) );
271 $order = json_decode( $new_order, true );
272
273 if ( is_array( $order ) && count( $order ) ) {
274 $i = 0;
275 foreach ( $order as $topic ) {
276 $i++;
277 $wpdb->update(
278 $wpdb->posts,
279 array( 'menu_order' => $i ),
280 array( 'ID' => $topic['topic_id'] )
281 );
282
283 /**
284 * Removing All lesson with topic
285 */
286
287 $wpdb->update(
288 $wpdb->posts,
289 array( 'post_parent' => 0 ),
290 array( 'post_parent' => $topic['topic_id'] )
291 );
292
293 /**
294 * Lesson Attaching with topic ID
295 * sorting lesson
296 */
297 if ( isset( $topic['lesson_ids'] ) ) {
298 $lesson_ids = $topic['lesson_ids'];
299 } else {
300 $lesson_ids = array();
301 }
302 if ( count( $lesson_ids ) ) {
303 foreach ( $lesson_ids as $lesson_key => $lesson_id ) {
304 $wpdb->update(
305 $wpdb->posts,
306 array(
307 'post_parent' => $topic['topic_id'],
308 'menu_order' => $lesson_key,
309 ),
310 array( 'ID' => $lesson_id )
311 );
312 }
313 }
314 }
315 }
316 }
317 }
318
319 /**
320 * @param $post_ID
321 *
322 * Insert Topic and attached it with Course
323 */
324 public function save_course_meta( $post_ID, $post ) {
325 global $wpdb;
326
327 do_action( 'tutor_save_course', $post_ID, $post );
328
329 /**
330 * Save course price type
331 */
332 $price_type = tutor_utils()->array_get('tutor_course_price_type', $_POST);
333 if ($price_type){
334 update_post_meta($post_ID, '_tutor_course_price_type', $price_type);
335 }
336
337 //Course Duration
338 if ( ! empty($_POST['course_duration'])){
339 $video = tutor_utils()->sanitize_array($_POST['course_duration']);
340 update_post_meta($post_ID, '_course_duration', $video);
341 }
342
343 if ( ! empty($_POST['_tutor_course_level'])){
344 $course_level = sanitize_text_field($_POST['_tutor_course_level']);
345 update_post_meta($post_ID, '_tutor_course_level', $course_level);
346 }
347
348 $additional_data_edit = tutor_utils()->avalue_dot('_tutor_course_additional_data_edit', $_POST);
349 if ($additional_data_edit) {
350 if (!empty($_POST['course_benefits'])) {
351 $course_benefits = wp_kses_post($_POST['course_benefits']);
352 update_post_meta($post_ID, '_tutor_course_benefits', $course_benefits);
353 } else {
354 delete_post_meta( $post_ID, '_tutor_course_benefits' );
355 }
356
357 if ( ! empty( $_POST['course_requirements'] ) ) {
358 $requirements = wp_kses_post( $_POST['course_requirements'] );
359 update_post_meta( $post_ID, '_tutor_course_requirements', $requirements );
360 } else {
361 delete_post_meta( $post_ID, '_tutor_course_requirements' );
362 }
363
364 if ( ! empty( $_POST['course_target_audience'] ) ) {
365 $target_audience = wp_kses_post( $_POST['course_target_audience'] );
366 update_post_meta( $post_ID, '_tutor_course_target_audience', $target_audience );
367 } else {
368 delete_post_meta( $post_ID, '_tutor_course_target_audience' );
369 }
370
371 if ( ! empty( $_POST['course_material_includes'] ) ) {
372 $material_includes = wp_kses_post( $_POST['course_material_includes'] );
373 update_post_meta( $post_ID, '_tutor_course_material_includes', $material_includes );
374 } else {
375 delete_post_meta( $post_ID, '_tutor_course_material_includes' );
376 }
377 }
378
379 /**
380 * Sorting Topics and lesson
381 */
382 $this->save_course_content_order();
383
384 // Additional data like course intro video
385 if ( $additional_data_edit ) {
386 if ( ! empty( $_POST['video']['source'] ) ) { // Video
387 $video = tutor_utils()->array_get( 'video', $_POST );
388 update_post_meta( $post_ID, '_video', $video );
389 } else {
390 delete_post_meta( $post_ID, '_video' );
391 }
392 }
393
394 /**
395 * Adding author to instructor automatically
396 */
397
398 $author_id = $post->post_author;
399 $attached = (int) $wpdb->get_var(
400 $wpdb->prepare(
401 "SELECT COUNT(umeta_id) FROM {$wpdb->usermeta}
402 WHERE user_id = %d AND meta_key = '_tutor_instructor_course_id' AND meta_value = %d ",
403 $author_id,
404 $post_ID
405 )
406 );
407
408 if ( ! $attached ) {
409 add_user_meta( $author_id, '_tutor_instructor_course_id', $post_ID );
410 }
411
412 /**
413 * Disable question and answer for this course
414 *
415 * @since 1.7.0
416 */
417 if ( $additional_data_edit ) {
418 foreach ( $this->additional_meta as $key ) {
419 update_post_meta( $post_ID, $key, ( isset( $_POST[ $key ] ) ? 'yes' : 'no' ) );
420 }
421 }
422
423 do_action( 'tutor_save_course_after', $post_ID, $post );
424 }
425
426 /**
427 * Tutor add course topic
428 */
429 public function tutor_save_topic(){
430 tutor_utils()->checking_nonce();
431
432 // Check required fields
433 if (empty($_POST['topic_title']) ) {
434 wp_send_json_error(array('message' => __('Topic title is required!', 'tutor')));
435 }
436
437 // Gather parameters
438 $course_id = (int) tutor_utils()->avalue_dot('topic_course_id', $_POST);
439 $topic_id = (int) tutor_utils()->avalue_dot('topic_id', $_POST);
440 $topic_title = sanitize_text_field( $_POST['topic_title'] );
441 $topic_summery = wp_kses_post( $_POST['topic_summery'] );
442 $next_topic_order_id = tutor_utils()->get_next_topic_order_id($course_id);
443
444 // Validate if user can manage the topic
445 if(!tutor_utils()->can_user_manage('course', $course_id) || ($topic_id && !tutor_utils()->can_user_manage('topic', $topic_id))) {
446 wp_send_json_error( array('message'=>__('Access Denied', 'tutor')) );
447 }
448
449 // Create payload to create/update the topic
450 $post_arr = array(
451 'post_type' => 'topics',
452 'post_title' => $topic_title,
453 'post_content' => $topic_summery,
454 'post_status' => 'publish',
455 'post_author' => get_current_user_id(),
456 'post_parent' => $course_id,
457 'menu_order' => $next_topic_order_id,
458 );
459 $topic_id ? $post_arr['ID']=$topic_id : 0;
460 $current_topic_id = wp_insert_post( $post_arr );
461
462 ob_start();
463 include tutor()->path.'views/metabox/course-contents.php';
464
465 wp_send_json_success(array(
466 'topic_title' => $topic_title,
467 'course_contents' => ob_get_clean()
468 ));
469 }
470
471 /**
472 * @param $columns
473 *
474 * @return mixed
475 *
476 * Add Lesson column
477 */
478 public function add_column( $columns ) {
479 $date_col = $columns['date'];
480 unset( $columns['date'] );
481 $columns['lessons'] = __( 'Lessons', 'tutor' );
482 $columns['students'] = __( 'Students', 'tutor' );
483 $columns['price'] = __( 'Price', 'tutor' );
484 $columns['date'] = $date_col;
485
486 return $columns;
487 }
488
489 /**
490 * @param $column
491 * @param $post_id
492 */
493 public function custom_lesson_column( $column, $post_id ) {
494 if ( $column === 'lessons' ) {
495 echo tutor_utils()->get_lesson_count_by_course( $post_id );
496 }
497
498 if ( $column === 'students' ) {
499 echo tutor_utils()->count_enrolled_users_by_course( $post_id );
500 }
501
502 if ( $column === 'price' ) {
503 $price = tutor_utils()->get_course_price( $post_id );
504 if ( $price ) {
505 $monetize_by = tutils()->get_option( 'monetize_by' );
506 if ( function_exists( 'wc_price' ) && $monetize_by === 'wc' ) {
507 echo '<span class="tutor-label-success">' . wc_price( $price ) . '</span>';
508 } else {
509 echo '<span class="tutor-label-success">' . $price . '</span>';
510 }
511 } else {
512 echo apply_filters( 'tutor-loop-default-price', __( 'free', 'tutor' ) );
513 }
514 }
515 }
516
517
518 public function tutor_delete_topic() {
519
520 tutor_utils()->checking_nonce();
521
522 global $wpdb;
523 $topic_id = sanitize_text_field(!empty($_POST['topic_id']) ? $_POST['topic_id'] : '');
524
525 if(!$topic_id || !is_numeric($topic_id) || !tutor_utils()->can_user_manage('topic', $topic_id)) {
526 wp_send_json_error(array('message' => 'Access Forbidden'));
527 }
528
529 // Assign course ID to orphan content IDs since the topic will be deleted.
530 $course_id = tutor_utils()->get_course_id_by('topic', $topic_id);
531 $content_ids = tutor_utils()->get_course_content_ids_by(null, 'topic', $topic_id);
532 foreach($content_ids as $content_id) {
533 update_post_meta( $content_id, '_tutor_course_id_for_lesson', $course_id );
534 // Actually all kind of contents.
535 // This keyword '_tutor_course_id_for_lesson' used just to support backward compatibillity
536 }
537
538 // Set contents under the topic orphan
539 $wpdb->update($wpdb->posts, array('post_parent' => 0), array('post_parent' => $topic_id));
540
541 // Then delete the topic from database
542 $wpdb->delete($wpdb->postmeta, array('post_id' => $topic_id));
543 wp_delete_post($topic_id);
544
545 wp_send_json_success();
546 }
547
548 public function tutor_delete_announcement() {
549 tutor_utils()->checking_nonce( 'get' );
550
551 $announcement_id = (int) $_GET['topic_id'];
552
553 wp_delete_post( $announcement_id );
554 wp_safe_redirect( wp_get_referer() );
555 }
556
557 public function enroll_now() {
558
559 // Checking if action comes from Enroll form
560 if ( tutor_utils()->array_get( 'tutor_course_action', tutor_sanitize_data($_POST) ) !== '_tutor_course_enroll_now' || ! isset( $_POST['tutor_course_id'] ) ) {
561 return;
562 }
563
564 //Checking Nonce
565 tutor_utils()->checking_nonce();
566
567 $user_id = get_current_user_id();
568 if ( ! $user_id ) {
569 exit( __( 'Please Sign In first', 'tutor' ) );
570 }
571
572 $course_id = (int) $_POST['tutor_course_id'];
573 $user_id = get_current_user_id();
574
575 /**
576 * TODO: need to check purchase information
577 */
578
579 $is_purchasable = tutor_utils()->is_course_purchasable( $course_id );
580
581 /**
582 * If is is not purchasable, it's free, and enroll right now
583 *
584 * if purchasable, then process purchase.
585 *
586 * @since: v.1.0.0
587 */
588 if ( $is_purchasable ) {
589 // process purchase
590
591 } else {
592 // Free enroll
593 tutor_utils()->do_enroll( $course_id );
594 }
595
596 $referer_url = wp_get_referer();
597 wp_redirect( $referer_url );
598 }
599
600 /**
601 *
602 * Mark complete completed
603 *
604 * @since v.1.0.0
605 */
606 public function mark_course_complete() {
607 if ( ! isset( $_POST['tutor_action'] ) || $_POST['tutor_action'] !== 'tutor_complete_course' ) {
608 return;
609 }
610 // Checking nonce
611 tutor_utils()->checking_nonce();
612
613 $user_id = get_current_user_id();
614
615 // TODO: need to show view if not signed_in
616 if ( ! $user_id ) {
617 die( __( 'Please Sign-In', 'tutor' ) );
618 }
619
620 $course_id = (int) $_POST['course_id'];
621
622 do_action( 'tutor_course_complete_before', $course_id );
623 /**
624 * Marking course completed at Comment
625 */
626
627 global $wpdb;
628
629 $date = date( 'Y-m-d H:i:s', tutor_time() );
630
631 // Making sure that, hash is unique
632 do {
633 $hash = substr( md5( wp_generate_password( 32 ) . $date . $course_id . $user_id ), 0, 16 );
634 $hasHash = (int) $wpdb->get_var(
635 $wpdb->prepare(
636 "SELECT COUNT(comment_ID) from {$wpdb->comments}
637 WHERE comment_agent = 'TutorLMSPlugin' AND comment_type = 'course_completed' AND comment_content = %s ",
638 $hash
639 )
640 );
641
642 } while ( $hasHash > 0 );
643
644 $data = array(
645 'comment_post_ID' => $course_id,
646 'comment_author' => $user_id,
647 'comment_date' => $date,
648 'comment_date_gmt' => get_gmt_from_date( $date ),
649 'comment_content' => $hash, // Identification Hash
650 'comment_approved' => 'approved',
651 'comment_agent' => 'TutorLMSPlugin',
652 'comment_type' => 'course_completed',
653 'user_id' => $user_id,
654 );
655
656 $wpdb->insert( $wpdb->comments, $data );
657
658 do_action( 'tutor_course_complete_after', $course_id, $user_id );
659
660 $permalink = get_the_permalink( $course_id );
661
662 // Set temporary identifier to show review pop up
663 if(get_tutor_option( 'enable_course_review' )) {
664 $rating = tutor_utils()->get_course_rating_by_user($course_id, $user_id);
665 if(!$rating || (empty($rating->rating) && empty($rating->review))) {
666 update_option( 'tutor_course_complete_popup_'.$user_id, array(
667 'course_id' => $course_id,
668 'course_url' => $permalink,
669 'expires' => time()+10
670 ));
671 }
672 }
673
674 wp_redirect( $permalink );
675 exit;
676 }
677
678 public function popup_review_form() {
679 if ( is_user_logged_in() ) {
680 $key = 'tutor_course_complete_popup_' . get_current_user_id();
681 $popup = get_option( $key );
682
683 if ( is_array( $popup ) ) {
684
685 if ( $popup['expires'] > time() ) {
686 $course_id = $popup['course_id'];
687 include tutor()->path . 'views/modal/review.php';
688 }
689
690 delete_option( $key );
691 }
692 }
693 }
694
695 public function tutor_delete_dashboard_course(){
696 tutor_utils()->checking_nonce();
697
698 $course_id = intval(sanitize_text_field($_POST['course_id']));
699 if(!tutor_utils()->can_user_manage('course', $course_id)) {
700 wp_send_json_error( array('message'=>__('Access Denied', 'tutor')) );
701 }
702
703 wp_delete_post($course_id, true);
704 wp_send_json_success();
705 }
706
707
708 public function tutor_add_gutenberg_author( $data, $postarr ) {
709 global $wpdb;
710
711 $courses_post_type = tutor()->course_post_type;
712 $post_type = tutor_utils()->array_get('post_type', $postarr);
713
714 if ( $courses_post_type === $post_type ) {
715 $post_ID = (int) tutor_utils()->avalue_dot( 'ID', $postarr );
716 $post_author = (int) $wpdb->get_var( $wpdb->prepare( "SELECT post_author FROM {$wpdb->posts} WHERE ID = %d ", $post_ID ) );
717
718 if ( $post_author > 0 ) {
719 $data['post_author'] = $post_author;
720 } else {
721 $data['post_author'] = get_current_user_id();
722 }
723 }
724
725 return $data;
726 }
727
728
729 /**
730 * @param $post_ID
731 * @param $postData
732 *
733 * Attach product during save course from the frontend course dashboard.
734 *
735 * @return string
736 *
737 * @since v.1.3.4
738 */
739 public function attach_product_with_course( $post_ID, $postData ) {
740 $attached_product_id = tutor_utils()->get_course_product_id( $post_ID );
741 $course_price = sanitize_text_field( tutor_utils()->array_get( 'course_price', $_POST ) );
742
743 if ( ! $course_price){
744 // Return if price not set or 0
745 return;
746 }
747
748 $monetize_by = tutor_utils()->get_option( 'monetize_by' );
749 $course = get_post( $post_ID );
750
751 if ( $monetize_by === 'wc' ) {
752
753 $is_update = false;
754 if ( $attached_product_id ) {
755 $wc_product = get_post_meta( $attached_product_id, '_product_version', true );
756 if ( $wc_product ) {
757 $is_update = true;
758 }
759 }
760
761 if ( $is_update ) {
762 $productObj = wc_get_product( $attached_product_id );
763 $productObj->set_price( $course_price ); // set product price
764 $productObj->set_regular_price( $course_price ); // set product regular price
765 $productObj->set_sold_individually( true );
766 $product_id = $productObj->save();
767 if ( $productObj->is_type( 'subscription' ) ) {
768 update_post_meta( $attached_product_id, '_subscription_price', $course_price );
769 }
770 } else {
771 $productObj = new \WC_Product();
772 $productObj->set_name( $course->post_title );
773 $productObj->set_status( 'publish' );
774 $productObj->set_price( $course_price ); // set product price
775 $productObj->set_regular_price( $course_price ); // set product regular price
776 $productObj->set_sold_individually( true );
777
778 $product_id = $productObj->save();
779 if ( $product_id ) {
780 update_post_meta( $post_ID, '_tutor_course_product_id', $product_id );
781 // Mark product for woocommerce
782 update_post_meta( $product_id, '_virtual', 'yes' );
783 update_post_meta( $product_id, '_tutor_product', 'yes' );
784
785 $coursePostThumbnail = get_post_meta( $post_ID, '_thumbnail_id', true );
786 if ( $coursePostThumbnail ) {
787 set_post_thumbnail( $product_id, $coursePostThumbnail );
788 }
789 }
790 }
791 } elseif ( $monetize_by === 'edd' ) {
792
793 $is_update = false;
794
795 if ( $attached_product_id ) {
796 $edd_price = get_post_meta( $attached_product_id, 'edd_price', true );
797 if ( $edd_price ) {
798 $is_update = true;
799 }
800 }
801
802 if ( $is_update ) {
803 // Update the product
804 update_post_meta( $attached_product_id, 'edd_price', $course_price );
805 } else {
806 // Create new product
807
808 $post_arr = array(
809 'post_type' => 'download',
810 'post_title' => $course->post_title,
811 'post_status' => 'publish',
812 'post_author' => get_current_user_id(),
813 );
814 $download_id = wp_insert_post( $post_arr );
815 if ( $download_id ) {
816 // edd_price
817 update_post_meta( $download_id, 'edd_price', $course_price );
818
819 update_post_meta( $post_ID, '_tutor_course_product_id', $download_id );
820 // Mark product for EDD
821 update_post_meta( $download_id, '_tutor_product', 'yes' );
822
823 $coursePostThumbnail = get_post_meta( $post_ID, '_thumbnail_id', true );
824 if ( $coursePostThumbnail ) {
825 set_post_thumbnail( $download_id, $coursePostThumbnail );
826 }
827 }
828 }
829 }
830 }
831
832 /**
833 * Add Course level to course settings
834 *
835 * @since v.1.4.1
836 */
837 public function add_course_level_to_settings($args){
838 $course_id = get_the_ID();
839 $levels = tutor_utils()->course_levels();
840 $course_level = get_post_meta($course_id, '_tutor_course_level', true);
841
842 $args['general']['fields']['_tutor_course_level'] = array(
843 'type' => 'select',
844 'label' => __('Difficulty Level', 'tutor'),
845 'label_title'=> __('Enable', 'tutor'),
846 'options' => $levels,
847 'value' => $course_level ? $course_level : 'intermediate',
848 'desc' => __('Course difficulty level', 'tutor'),
849 );
850
851 return $args;
852 }
853
854 /**
855 * Check if course starting
856 *
857 * @since v.1.4.8
858 */
859 public function tutor_lesson_load_before(){
860 $course_id = tutor_utils()->get_course_id_by_content(get_the_ID());
861 $completed_lessons = tutor_utils()->get_completed_lesson_count_by_course($course_id);
862 if (is_user_logged_in()){
863 $is_course_started = get_post_meta($course_id, '_tutor_course_started', true);
864 if ( ! $completed_lessons && ! $is_course_started){
865 update_post_meta($course_id, '_tutor_course_started', tutor_time());
866 do_action('tutor/course/started', $course_id);
867 }
868 }
869 }
870
871 /**
872 * Add Course level to course settings
873 *
874 * @since v.1.4.8
875 */
876 public function course_elements_enable_disable(){
877 add_filter('tutor_course/single/completing-progress-bar', array($this, 'enable_disable_course_progress_bar') );
878 add_filter('tutor_course/single/material_includes', array($this, 'enable_disable_material_includes') );
879 add_filter('tutor_course/single/content', array($this, 'enable_disable_course_content') );
880 add_filter('tutor_course/single/benefits_html', array($this, 'enable_disable_course_benefits') );
881 add_filter('tutor_course/single/requirements_html', array($this, 'enable_disable_course_requirements') );
882 add_filter('tutor_course/single/audience_html', array($this, 'enable_disable_course_target_audience') );
883 add_filter('tutor_course/single/nav_items', array($this, 'enable_disable_course_nav_items'), 999, 2 );
884 }
885
886 /**
887 * Enable disable course progress bar
888 *
889 * @since v.1.4.8
890 */
891 public function enable_disable_course_progress_bar($html){
892 $disable_option = !(bool)tutor_utils()->get_option('enable_course_progress_bar', true, true);
893 if($disable_option){
894 return '';
895 }
896 return $html;
897 }
898
899 /**
900 * Enable disable material includes
901 *
902 * @since v.1.4.8
903 */
904 public function enable_disable_material_includes($html){
905 $disable_option = !(bool)get_tutor_option('enable_course_material', true, true);
906 if($disable_option){
907 return '';
908 }
909 return $html;
910 }
911
912 /**
913 * Enable disable course content
914 *
915 * @since v.1.4.8
916 */
917 public function enable_disable_course_content($html){
918 $disable_option = !(bool)tutor_utils()->get_option('enable_course_description', true, true);
919 if($disable_option){
920 return '';
921 }
922 return $html;
923 }
924
925 /**
926 * Enable disable course benefits
927 *
928 * @since v.1.4.8
929 */
930 public function enable_disable_course_benefits($html){
931 $disable_option = !(bool) tutor_utils()->get_option('enable_course_benefits', true, true);
932 if($disable_option){
933 return '';
934 }
935 return $html;
936 }
937
938 /**
939 * Enable disable course requirements
940 *
941 * @since v.1.4.8
942 */
943 public function enable_disable_course_requirements($html){
944 $disable_option = !(bool) tutor_utils()->get_option('enable_course_requirements', true, true);
945 if($disable_option){
946 return '';
947 }
948 return $html;
949 }
950
951 /**
952 * Enable disable course target audience
953 *
954 * @since v.1.4.8
955 */
956 public function enable_disable_course_target_audience($html){
957 $disable_option = !(bool) tutor_utils()->get_option('enable_course_target_audience', true, true);
958 if($disable_option){
959 return '';
960 }
961 return $html;
962 }
963
964 /**
965 * Enable disable course nav items
966 *
967 * @since v.1.4.8
968 */
969 public function enable_disable_course_nav_items($items, $course_id){
970 global $wp_query, $post;
971 $enable_q_and_a_on_course = (bool) get_tutor_option('enable_q_and_a_on_course');
972 $disable_course_announcements = !(bool) tutor_utils()->get_option('enable_course_announcements', true, true);
973 $disable_qa_for_this_course = ($wp_query->is_single && !empty($post)) ? get_post_meta($post->ID, '_tutor_enable_qa', true)!='yes' : false;
974
975 // Whether Q&A enabled
976 if(!$enable_q_and_a_on_course || $disable_qa_for_this_course) {
977 if(tutor_utils()->array_get('questions', $items)) {
978 unset($items['questions']);
979 }
980 }
981
982 // Whether announcment enabled
983 if($disable_course_announcements){
984 if(tutor_utils()->array_get('announcements', $items)) {
985 unset($items['announcements']);
986 }
987 }
988
989 // Hide review section if disabled
990 if(!get_tutor_option('enable_course_review')) {
991 unset($items['reviews']);
992 }
993
994 // Whether enrolment require
995 $is_enrolled = tutor_utils()->is_enrolled();
996
997 return array_filter($items, function($item) use($is_enrolled) {
998 if(isset($item['require_enrolment']) && $item['require_enrolment']) {
999 return $is_enrolled;
1000 }
1001 return true;
1002 });
1003 }
1004
1005 /**
1006 * Filter product in shop page
1007 *
1008 * @since v.1.4.9
1009 */
1010 public function filter_product_in_shop_page() {
1011 $hide_course_from_shop_page = (bool) get_tutor_option( 'hide_course_from_shop_page' );
1012 if ( ! $hide_course_from_shop_page ) {
1013 return;
1014 }
1015 add_action( 'woocommerce_product_query', array( $this, 'filter_woocommerce_product_query' ) );
1016 add_filter( 'edd_downloads_query', array( $this, 'filter_edd_downloads_query' ), 10, 2 );
1017 add_action( 'pre_get_posts', array( $this, 'filter_archive_meta_query' ), 1 );
1018 }
1019
1020 /**
1021 * Tutor product meta query
1022 *
1023 * @since v.1.4.9
1024 */
1025 public function tutor_product_meta_query() {
1026 $meta_query = array(
1027 'key' => '_tutor_product',
1028 'compare' => 'NOT EXISTS',
1029 );
1030 return $meta_query;
1031 }
1032
1033 /**
1034 * Filter product in woocommerce shop page
1035 *
1036 * @since v.1.4.9
1037 */
1038 public function filter_woocommerce_product_query( $wp_query ) {
1039 $wp_query->set( 'meta_query', array( $this->tutor_product_meta_query() ) );
1040 return $wp_query;
1041 }
1042
1043 /**
1044 * Filter product in edd downloads shortcode page
1045 *
1046 * @since v.1.4.9
1047 */
1048 public function filter_edd_downloads_query( $query ) {
1049 $query['meta_query'][] = $this->tutor_product_meta_query();
1050 return $query;
1051 }
1052
1053 /**
1054 * Filter product in edd downloads archive page
1055 *
1056 * @since v.1.4.9
1057 */
1058 public function filter_archive_meta_query( $wp_query ) {
1059 if ( ! is_admin() && $wp_query->is_archive && $wp_query->get( 'post_type' ) === 'download' ) {
1060 $wp_query->set( 'meta_query', array( $this->tutor_product_meta_query() ) );
1061 }
1062 return $wp_query;
1063 }
1064
1065 /**
1066 * @param $html
1067 * @return string
1068 *
1069 * Removed course price if already enrolled at single course
1070 *
1071 * @since v.1.5.8
1072 */
1073 public function remove_price_if_enrolled($html){
1074 $should_removed = apply_filters('should_remove_price_if_enrolled', true);
1075
1076 if ($should_removed){
1077 $course_id = get_the_ID();
1078 $enrolled = tutor_utils()->is_enrolled($course_id);
1079 if ($enrolled){
1080 $html = '';
1081 }
1082 }
1083 return $html;
1084 }
1085
1086 /**
1087 * @param $html
1088 * @return string
1089 *
1090 * Check if all lessons and quizzes done before mark course complete.
1091 */
1092 function tutor_lms_hide_course_complete_btn($html){
1093
1094 $completion_mode = tutor_utils()->get_option('course_completion_process');
1095 if ($completion_mode !== 'strict'){
1096 return $html;
1097 }
1098
1099 $completed_lesson = tutor_utils()->get_completed_lesson_count_by_course();
1100 $lesson_count = tutor_utils()->get_lesson_count_by_course();
1101
1102 if ($completed_lesson < $lesson_count){
1103 return '<div class="tutor-alert tutor-warning tutor-mt-28">
1104 <div class="tutor-alert-text">
1105 <span class="tutor-alert-icon tutor-icon-34 tutor-icon-circle-outline-info-filled tutor-mr-12"></span>
1106 <span>'.__('Complete all lessons to mark this course as complete', 'tutor').'</span>
1107 </div>
1108 </div>';
1109 }
1110
1111 $quizzes = array();
1112
1113 $course_contents = tutor_utils()->get_course_contents_by_id();
1114 if (tutor_utils()->count($course_contents)){
1115 foreach ($course_contents as $content){
1116 if ($content->post_type === 'tutor_quiz'){
1117 $quizzes[] = $content;
1118 }
1119 }
1120 }
1121
1122 $is_pass = true;
1123 $required_quiz_pass = 0;
1124
1125 if (tutor_utils()->count($quizzes)){
1126 foreach ($quizzes as $quiz){
1127
1128 $attempt = tutor_utils()->get_quiz_attempt($quiz->ID);
1129 if ($attempt) {
1130 $passing_grade = tutor_utils()->get_quiz_option($quiz->ID, 'passing_grade', 0);
1131 $earned_percentage = $attempt->earned_marks > 0 ? (number_format(($attempt->earned_marks * 100) / $attempt->total_marks)) : 0;
1132
1133 if ($earned_percentage < $passing_grade) {
1134 $required_quiz_pass++;
1135 $is_pass = false;
1136 }
1137 }else{
1138 $required_quiz_pass++;
1139 $is_pass = false;
1140 }
1141 }
1142 }
1143
1144 if ( ! $is_pass){
1145 return '<div class="tutor-alert tutor-warning tutor-mt-28">
1146 <div class="tutor-alert-text">
1147 <span class="tutor-alert-icon tutor-icon-34 tutor-icon-circle-outline-info-filled tutor-mr-12"></span>
1148 <span>'.sprintf(__('You have to pass %s quizzes to complete this course.', 'tutor'), $required_quiz_pass).'</span>
1149 </div>
1150 </div>';
1151 }
1152
1153 return $html;
1154 }
1155
1156 public function get_generate_greadbook($html){
1157 if ( ! tutor_utils()->is_completed_course()){
1158 return '';
1159 }
1160 return $html;
1161 }
1162
1163 /**
1164 * Add social share content in header
1165 *
1166 * @since v.1.6.3
1167 */
1168 public function social_share_content() {
1169 global $wp_query, $post;
1170 if ( $wp_query->is_single && ! empty( $wp_query->query_vars['post_type'] ) && $wp_query->query_vars['post_type'] === $this->course_post_type ) { ?>
1171 <!--Facebook-->
1172 <meta property="og:type" content="website"/>
1173 <meta property="og:image" content="<?php echo esc_url( get_tutor_course_thumbnail_src() ); ?>" />
1174 <meta property="og:description" content="<?php echo esc_html( $post->post_content ); ?>" />
1175 <!--Twitter-->
1176 <meta name="twitter:image" content="<?php echo esc_url( get_tutor_course_thumbnail_src() ); ?>">
1177 <meta name="twitter:description" content="<?php echo esc_html( $post->post_content ); ?>">
1178 <!--Google+-->
1179 <meta itemprop="image" content="<?php echo esc_url( get_tutor_course_thumbnail_src() ); ?>">
1180 <meta itemprop="description" content="<?php echo esc_html( $post->post_content ); ?>">
1181 <?php
1182 }
1183 }
1184
1185 /**
1186 * Get posts by type and parent
1187 *
1188 * @since v.1.6.6
1189 */
1190 public function tutor_get_post_ids( $post_type, $post_parent ) {
1191 $args = array(
1192 'fields' => 'ids',
1193 'post_type' => $post_type,
1194 'post_parent' => $post_parent,
1195 'post_status' => 'any',
1196 'posts_per_page' => -1,
1197 );
1198 return get_posts( $args );
1199 }
1200
1201 /**
1202 * Delete course data when permanently deleting a course.
1203 *
1204 * @since v.1.6.6
1205 */
1206 function delete_tutor_course_data( $post_id ) {
1207 $course_post_type = tutor()->course_post_type;
1208 $lesson_post_type = tutor()->lesson_post_type;
1209
1210 if ( get_post_type( $post_id ) == $course_post_type ) {
1211 global $wpdb;
1212 $topic_ids = $this->tutor_get_post_ids( 'topics', $post_id );
1213 if ( ! empty( $topic_ids ) ) {
1214 foreach ( $topic_ids as $topic_id ) {
1215 $content_post_type = apply_filters( 'tutor_course_contents_post_types', array( $lesson_post_type, 'tutor_quiz' ) );
1216 $topic_content_ids = $this->tutor_get_post_ids( $content_post_type, $topic_id );
1217
1218 foreach ( $topic_content_ids as $content_id ) {
1219 if ( get_post_type( $content_id ) == 'tutor_quiz' ) {
1220 $wpdb->delete( $wpdb->prefix . 'tutor_quiz_attempts', array( 'quiz_id' => $content_id ) );
1221 $wpdb->delete( $wpdb->prefix . 'tutor_quiz_attempt_answers', array( 'quiz_id' => $content_id ) );
1222
1223 $questions_ids = $wpdb->get_col( $wpdb->prepare( "SELECT question_id FROM {$wpdb->prefix}tutor_quiz_questions WHERE quiz_id = %d ", $content_id ) );
1224 if ( is_array( $questions_ids ) && count( $questions_ids ) ) {
1225 $in_question_ids = "'" . implode( "','", $questions_ids ) . "'";
1226 $wpdb->query( "DELETE FROM {$wpdb->prefix}tutor_quiz_question_answers WHERE belongs_question_id IN({$in_question_ids}) " );
1227 }
1228 $wpdb->delete( $wpdb->prefix . 'tutor_quiz_questions', array( 'quiz_id' => $content_id ) );
1229 }
1230 wp_delete_post( $content_id, true );
1231 }
1232 wp_delete_post( $topic_id, true );
1233 }
1234 }
1235 $child_post_ids = $this->tutor_get_post_ids( array( 'tutor_announcements', 'tutor_enrolled' ), $post_id );
1236 if ( ! empty( $child_post_ids ) ) {
1237 foreach ( $child_post_ids as $child_post_id ) {
1238 wp_delete_post( $child_post_id, true );
1239 }
1240 }
1241 }
1242 }
1243
1244 /**
1245 * Delete associated enrollment
1246 *
1247 * @since v.1.8.2
1248 */
1249 public function delete_associated_enrollment( $post_id ) {
1250 global $wpdb;
1251
1252 $enroll_id = $wpdb->get_var(
1253 $wpdb->prepare(
1254 "SELECT
1255 post_id
1256 FROM
1257 {$wpdb->postmeta}
1258 WHERE
1259 meta_key='_tutor_enrolled_by_order_id'
1260 AND meta_value = %d
1261 ",
1262 $post_id
1263 )
1264 );
1265
1266 if ( is_numeric( $enroll_id ) && $enroll_id > 0 ) {
1267
1268 $course_id = get_post_field( 'post_parent', $enroll_id );
1269 $user_id = get_post_field( 'post_author', $enroll_id );
1270
1271 tutor_utils()->cancel_course_enrol($course_id, $user_id);
1272 }
1273 }
1274
1275 public function tutor_reset_course_progress() {
1276 tutor_utils()->checking_nonce();
1277 $course_id = tutor_utils()->array_get('course_id', $_POST);
1278
1279 if ( ! $course_id || ! is_numeric( $course_id ) || ! tutor_utils()->is_enrolled( $course_id ) ) {
1280 wp_send_json_error( array( 'message' => __( 'Invalid Course ID or Access Denied.', 'tutor' ) ) );
1281 return;
1282 }
1283
1284 tutor_utils()->delete_course_progress( $course_id );
1285 wp_send_json_success( array( 'redirect_to' => tutor_utils()->get_course_first_lesson( $course_id ) ) );
1286 }
1287
1288 /**
1289 * Do enroll if guest attempt to enroll and course is free
1290 *
1291 * @param $course_id
1292 *
1293 * @since 1.9.8
1294 */
1295 public function enroll_after_login_if_attempt( int $course_id, int $user_id ) {
1296 $course_id = sanitize_text_field( $course_id );
1297 if ( $course_id ) {
1298 $is_purchasable = tutor_utils()->is_course_purchasable( $course_id );
1299 if ( ! $is_purchasable ) {
1300 tutor_utils()->do_enroll( $course_id, $order_id = 0, $user_id );
1301 do_action( 'guest_attempt_after_enrollment', $course_id );
1302 }
1303 }
1304 }
1305 }
1306