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