PluginProbe ʕ •ᴥ•ʔ
Tutor LMS – eLearning and online course solution / 2.1.8
Tutor LMS – eLearning and online course solution v2.1.8
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
1655 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_redirect( $referer_url );
807 }
808
809 /**
810 * Mark complete completed
811 *
812 * @since 1.0.0
813 * @return void
814 */
815 public function mark_course_complete() {
816 $tutor_action = Input::post( 'tutor_action' );
817 $course_id = Input::post( 'course_id', 0, Input::TYPE_INT );
818 if ( 'tutor_complete_course' !== $tutor_action || ! $course_id ) {
819 return;
820 }
821
822 // Checking nonce.
823 tutor_utils()->checking_nonce();
824
825 $user_id = get_current_user_id();
826
827 // TODO: need to show view if not signed_in.
828 if ( ! $user_id ) {
829 die( esc_html__( 'Please Sign-In', 'tutor' ) );
830 }
831
832 CourseModel::mark_course_as_completed( $course_id, $user_id );
833
834 $permalink = get_the_permalink( $course_id );
835
836 // Set temporary identifier to show review pop up.
837 if ( get_tutor_option( 'enable_course_review' ) ) {
838 $rating = tutor_utils()->get_course_rating_by_user( $course_id, $user_id );
839 if ( ! $rating || ( empty( $rating->rating ) && empty( $rating->review ) ) ) {
840 update_option(
841 'tutor_course_complete_popup_' . $user_id,
842 array(
843 'course_id' => $course_id,
844 'course_url' => $permalink,
845 'expires' => time() + 10,
846 )
847 );
848 }
849 }
850
851 wp_redirect( $permalink );
852 exit;
853 }
854
855 /**
856 * Popup review form
857 *
858 * @since 1.0.0
859 * @return void
860 */
861 public function popup_review_form() {
862 if ( is_user_logged_in() ) {
863 $key = 'tutor_course_complete_popup_' . get_current_user_id();
864 $popup = get_option( $key );
865
866 if ( is_array( $popup ) ) {
867
868 if ( $popup['expires'] > time() ) {
869 $course_id = $popup['course_id'];
870 include tutor()->path . 'views/modal/review.php';
871 }
872
873 delete_option( $key );
874 }
875 }
876 }
877
878 /**
879 * Delete course delete from frontend dashboard
880 *
881 * @since 2.0.0
882 * @return void
883 */
884 public function tutor_delete_dashboard_course() {
885 tutor_utils()->checking_nonce();
886
887 $course_id = Input::post( 'course_id', 0, Input::TYPE_INT );
888 if ( ! tutor_utils()->can_user_manage( 'course', $course_id ) ) {
889 wp_send_json_error( array( 'message' => __( 'Access Denied', 'tutor' ) ) );
890 }
891
892 /**
893 * Co-instructor can not delete a course
894 *
895 * @since 2.1.6
896 */
897 if ( false === CourseModel::is_main_instructor( $course_id ) ) {
898 wp_send_json_error( array( 'message' => __( 'Only main instructor can delete this course', 'tutor' ) ) );
899 }
900
901 CourseModel::delete_course( $course_id );
902 wp_send_json_success();
903 }
904
905 /**
906 * Main author change from gutenberg editor
907 *
908 * @since 2.0.0
909 *
910 * @param array $data data.
911 * @param array $postarr post array.
912 *
913 * @return mixed
914 */
915 public function tutor_add_gutenberg_author( $data, $postarr ) {
916 $gutenberg_enabled = tutor_utils()->get_option( 'enable_gutenberg_course_edit' );
917 $post_type = $postarr['post_type'];
918 $courses_post_type = tutor()->course_post_type;
919
920 if ( false === is_admin() || false === $gutenberg_enabled || $post_type !== $courses_post_type ) {
921 return $data;
922 }
923
924 /**
925 * Only admin can change main author
926 */
927 if ( $courses_post_type === $post_type && ! current_user_can( 'administrator' ) ) {
928 global $wpdb;
929 $post_ID = (int) tutor_utils()->avalue_dot( 'ID', $postarr );
930 $post_author = (int) $wpdb->get_var( $wpdb->prepare( "SELECT post_author FROM {$wpdb->posts} WHERE ID = %d ", $post_ID ) );
931
932 if ( $post_author > 0 ) {
933 $data['post_author'] = $post_author;
934 } else {
935 $data['post_author'] = get_current_user_id();
936 }
937 }
938
939 return $data;
940 }
941
942
943 /**
944 * Attach product with course when course save from frontend or backend.
945 *
946 * @since 1.3.4
947 *
948 * @param integer $post_ID course ID.
949 * @param array $post_data cretaed course post details.
950 *
951 * @return void
952 */
953 public function attach_product_with_course( $post_ID, $post_data ) {
954
955 $monetize_by = tutor_utils()->get_option( 'monetize_by' );
956
957 /**
958 * The function is_admin will check only loaded page from WP admin.
959 * It does not check any role
960 */
961 $is_admin_panel = is_admin();
962 // From backend course select box.
963 $product_id = Input::post( '_tutor_course_product_id', 0, Input::TYPE_INT );
964
965 /**
966 * From Admin Panel, Free user can only select product from dropdown
967 */
968 if ( $is_admin_panel && 'wc' === $monetize_by && tutor()->has_pro === false ) {
969 if ( $product_id > 0 ) {
970 update_post_meta( $post_ID, '_tutor_course_product_id', $product_id );
971 } elseif ( -1 === $product_id ) {
972 delete_post_meta( $post_ID, '_tutor_course_product_id' );
973 }
974
975 return;
976 }
977
978 $attached_product_id = tutor_utils()->get_course_product_id( $post_ID );
979 $course_price = Input::post( 'course_price', 0, Input::TYPE_NUMERIC );
980 $sale_price = Input::post( 'course_sale_price', 0, Input::TYPE_NUMERIC );
981
982 if ( ! $course_price || $sale_price >= $course_price ) {
983 return;
984 }
985
986 $course = get_post( $post_ID );
987
988 if ( 'wc' === $monetize_by ) {
989
990 $is_update = false;
991 if ( $attached_product_id ) {
992 $wc_product = get_post_meta( $attached_product_id, '_product_version', true );
993 if ( $wc_product ) {
994 $is_update = true;
995 }
996 }
997
998 if ( $is_update || ( $product_id > 0 && $is_admin_panel ) ) {
999 // Added in @since 2.0.7.
1000 if ( $product_id > 0 && $is_admin_panel ) {
1001 $attached_product_id = $product_id;
1002 update_post_meta( $post_ID, '_tutor_course_product_id', $product_id );
1003 }
1004
1005 $product_obj = wc_get_product( $attached_product_id );
1006 $product_obj->set_price( $course_price ); // Set product price.
1007 $product_obj->set_regular_price( $course_price ); // Set product regular price.
1008
1009 if ( $sale_price > 0 ) {
1010 $product_obj->set_sale_price( $sale_price );
1011 } else {
1012 // When use remove sale price ( discounted price ).
1013 $product_obj->set_sale_price( null );
1014 }
1015
1016 $product_obj->set_sold_individually( true );
1017 $product_id = $product_obj->save();
1018 if ( $product_obj->is_type( 'subscription' ) ) {
1019 update_post_meta( $attached_product_id, '_subscription_price', $course_price );
1020 }
1021 } else {
1022 $product_obj = new \WC_Product();
1023 $product_obj->set_name( $course->post_title );
1024 $product_obj->set_status( 'publish' );
1025 $product_obj->set_price( $course_price ); // Set product price.
1026 $product_obj->set_regular_price( $course_price ); // Set product regular price.
1027
1028 if ( $sale_price > 0 ) {
1029 $product_obj->set_sale_price( $sale_price );
1030 }
1031
1032 $product_obj->set_sold_individually( true );
1033
1034 $product_id = $product_obj->save();
1035 if ( $product_id ) {
1036 update_post_meta( $post_ID, '_tutor_course_product_id', $product_id );
1037 // Mark product for woocommerce.
1038 update_post_meta( $product_id, '_virtual', 'yes' );
1039 update_post_meta( $product_id, '_tutor_product', 'yes' );
1040
1041 $course_post_thumbnail = get_post_meta( $post_ID, '_thumbnail_id', true );
1042 if ( $course_post_thumbnail ) {
1043 set_post_thumbnail( $product_id, $course_post_thumbnail );
1044 }
1045 }
1046 }
1047 } elseif ( 'edd' === $monetize_by ) {
1048
1049 $is_update = false;
1050
1051 if ( $attached_product_id ) {
1052 $edd_price = get_post_meta( $attached_product_id, 'edd_price', true );
1053 if ( $edd_price ) {
1054 $is_update = true;
1055 }
1056 }
1057
1058 if ( $is_update ) {
1059 // Update the product.
1060 update_post_meta( $attached_product_id, 'edd_price', $course_price );
1061 } else {
1062 // Create new product.
1063
1064 $post_arr = array(
1065 'post_type' => 'download',
1066 'post_title' => $course->post_title,
1067 'post_status' => 'publish',
1068 'post_author' => get_current_user_id(),
1069 );
1070 $download_id = wp_insert_post( $post_arr );
1071 if ( $download_id ) {
1072 // EDD edd_price.
1073 update_post_meta( $download_id, 'edd_price', $course_price );
1074
1075 update_post_meta( $post_ID, '_tutor_course_product_id', $download_id );
1076 // Mark product for EDD.
1077 update_post_meta( $download_id, '_tutor_product', 'yes' );
1078
1079 $course_post_thumbnail = get_post_meta( $post_ID, '_thumbnail_id', true );
1080 if ( $course_post_thumbnail ) {
1081 set_post_thumbnail( $download_id, $course_post_thumbnail );
1082 }
1083 }
1084 }
1085 }
1086 }
1087
1088 /**
1089 * Add Course level to course settings
1090 *
1091 * @since 1.4.1
1092 *
1093 * @param array $args arguments.
1094 * @return array
1095 */
1096 public function add_course_level_to_settings( $args ) {
1097 $course_id = get_the_ID();
1098 $levels = tutor_utils()->course_levels();
1099 $course_level = get_post_meta( $course_id, '_tutor_course_level', true );
1100
1101 $args['general']['fields']['_tutor_course_level'] = array(
1102 'type' => 'select',
1103 'label' => __( 'Difficulty Level', 'tutor' ),
1104 'label_title' => __( 'Enable', 'tutor' ),
1105 'options' => $levels,
1106 'value' => $course_level ? $course_level : 'intermediate',
1107 'desc' => __( 'Course difficulty level', 'tutor' ),
1108 );
1109
1110 return $args;
1111 }
1112
1113 /**
1114 * Check if course starting
1115 *
1116 * @since 1.4.8
1117 * @return void
1118 */
1119 public function tutor_lesson_load_before() {
1120 $course_id = tutor_utils()->get_course_id_by_content( get_the_ID() );
1121 $completed_lessons = tutor_utils()->get_completed_lesson_count_by_course( $course_id );
1122 if ( is_user_logged_in() ) {
1123 $is_course_started = get_post_meta( $course_id, '_tutor_course_started', true );
1124 if ( ! $completed_lessons && ! $is_course_started ) {
1125 update_post_meta( $course_id, '_tutor_course_started', tutor_time() );
1126 do_action( 'tutor/course/started', $course_id );
1127 }
1128 }
1129 }
1130
1131 /**
1132 * Add Course level to course settings
1133 *
1134 * @since 1.4.8
1135 * @return void
1136 */
1137 public function course_elements_enable_disable() {
1138 add_filter( 'tutor_course/single/completing-progress-bar', array( $this, 'enable_disable_course_progress_bar' ) );
1139 add_filter( 'tutor_course/single/material_includes', array( $this, 'enable_disable_material_includes' ) );
1140 add_filter( 'tutor_course/single/content', array( $this, 'enable_disable_course_content' ) );
1141 add_filter( 'tutor_course/single/benefits_html', array( $this, 'enable_disable_course_benefits' ) );
1142 add_filter( 'tutor_course/single/requirements_html', array( $this, 'enable_disable_course_requirements' ) );
1143 add_filter( 'tutor_course/single/audience_html', array( $this, 'enable_disable_course_target_audience' ) );
1144 add_filter( 'tutor_course/single/nav_items', array( $this, 'enable_disable_course_nav_items' ), 999, 2 );
1145 }
1146
1147 /**
1148 * Enable disable course progress bar
1149 *
1150 * @since 1.4.8
1151 *
1152 * @param string $html HTML string.
1153 * @return string
1154 */
1155 public function enable_disable_course_progress_bar( $html ) {
1156 $disable_option = ! (bool) tutor_utils()->get_option( 'enable_course_progress_bar', true, true );
1157 if ( $disable_option ) {
1158 return '';
1159 }
1160 return $html;
1161 }
1162
1163 /**
1164 * Enable disable material includes
1165 *
1166 * @since 1.4.8
1167 *
1168 * @param string $html HTML string.
1169 * @return string
1170 */
1171 public function enable_disable_material_includes( $html ) {
1172 $disable_option = ! (bool) get_tutor_option( 'enable_course_material', true, true );
1173 if ( $disable_option ) {
1174 return '';
1175 }
1176 return $html;
1177 }
1178
1179 /**
1180 * Enable disable course content
1181 *
1182 * @since 1.4.8
1183 *
1184 * @param string $html HTML string.
1185 * @return string
1186 */
1187 public function enable_disable_course_content( $html ) {
1188 $disable_option = ! (bool) tutor_utils()->get_option( 'enable_course_description', true, true );
1189 if ( $disable_option ) {
1190 return '';
1191 }
1192 return $html;
1193 }
1194
1195 /**
1196 * Enable disable course benefits
1197 *
1198 * @since 1.4.8
1199 *
1200 * @param string $html HTML string.
1201 * @return string
1202 */
1203 public function enable_disable_course_benefits( $html ) {
1204 $disable_option = ! (bool) tutor_utils()->get_option( 'enable_course_benefits', true, true );
1205 if ( $disable_option ) {
1206 return '';
1207 }
1208 return $html;
1209 }
1210
1211 /**
1212 * Enable disable course requirements
1213 *
1214 * @since 1.4.8
1215 *
1216 * @param string $html HTML string.
1217 * @return string
1218 */
1219 public function enable_disable_course_requirements( $html ) {
1220 $disable_option = ! (bool) tutor_utils()->get_option( 'enable_course_requirements', true, true );
1221 if ( $disable_option ) {
1222 return '';
1223 }
1224 return $html;
1225 }
1226
1227 /**
1228 * Enable disable course target audience
1229 *
1230 * @since 1.4.8
1231 *
1232 * @param string $html HTML string.
1233 * @return string
1234 */
1235 public function enable_disable_course_target_audience( $html ) {
1236 $disable_option = ! (bool) tutor_utils()->get_option( 'enable_course_target_audience', true, true );
1237 if ( $disable_option ) {
1238 return '';
1239 }
1240 return $html;
1241 }
1242
1243 /**
1244 * Enable disable course nav items
1245 *
1246 * @since 1.4.8
1247 *
1248 * @param array $items item list.
1249 * @param integer $course_id course ID.
1250 *
1251 * @return array
1252 */
1253 public function enable_disable_course_nav_items( $items, $course_id ) {
1254 global $wp_query, $post;
1255 $enable_q_and_a_on_course = (bool) get_tutor_option( 'enable_q_and_a_on_course' );
1256 $disable_course_announcements = ! (bool) tutor_utils()->get_option( 'enable_course_announcements', true, true );
1257 $disable_qa_for_this_course = ( $wp_query->is_single && ! empty( $post ) ) ? get_post_meta( $post->ID, '_tutor_enable_qa', true ) != 'yes' : false;
1258
1259 // Whether Q&A enabled.
1260 if ( ! $enable_q_and_a_on_course || $disable_qa_for_this_course ) {
1261 if ( tutor_utils()->array_get( 'questions', $items ) ) {
1262 unset( $items['questions'] );
1263 }
1264 }
1265
1266 // Whether announcment enabled.
1267 if ( $disable_course_announcements ) {
1268 if ( tutor_utils()->array_get( 'announcements', $items ) ) {
1269 unset( $items['announcements'] );
1270 }
1271 }
1272
1273 // Hide review section if disabled.
1274 if ( ! get_tutor_option( 'enable_course_review' ) ) {
1275 unset( $items['reviews'] );
1276 }
1277
1278 // Whether enrollment require.
1279 $is_enrolled = tutor_utils()->is_enrolled();
1280
1281 return array_filter(
1282 $items,
1283 function( $item ) use ( $is_enrolled ) {
1284 if ( isset( $item['require_enrolment'] ) && $item['require_enrolment'] ) {
1285 return $is_enrolled;
1286 }
1287 return true;
1288 }
1289 );
1290 }
1291
1292 /**
1293 * Filter product in shop page
1294 *
1295 * @since 1.4.9
1296 * @return void|null
1297 */
1298 public function filter_product_in_shop_page() {
1299 $hide_course_from_shop_page = (bool) get_tutor_option( 'hide_course_from_shop_page' );
1300 if ( ! $hide_course_from_shop_page ) {
1301 return;
1302 }
1303 add_action( 'woocommerce_product_query', array( $this, 'filter_woocommerce_product_query' ) );
1304 add_filter( 'edd_downloads_query', array( $this, 'filter_edd_downloads_query' ), 10, 2 );
1305 add_action( 'pre_get_posts', array( $this, 'filter_archive_meta_query' ), 1 );
1306 }
1307
1308 /**
1309 * Tutor product meta query
1310 *
1311 * @since 1.4.9
1312 * @return array
1313 */
1314 public function tutor_product_meta_query() {
1315 $meta_query = array(
1316 'key' => '_tutor_product',
1317 'compare' => 'NOT EXISTS',
1318 );
1319 return $meta_query;
1320 }
1321
1322 /**
1323 * Filter product in woocommerce shop page
1324 *
1325 * @since 1.4.9
1326 *
1327 * @param \WP_Query $wp_query WP Query instance.
1328 * @return \WP_Query
1329 */
1330 public function filter_woocommerce_product_query( $wp_query ) {
1331 $wp_query->set( 'meta_query', array( $this->tutor_product_meta_query() ) );
1332 return $wp_query;
1333 }
1334
1335 /**
1336 * Filter product in edd downloads shortcode page
1337 *
1338 * @since 1.4.9
1339 *
1340 * @param \WP_Query $query WP Query instance.
1341 * @return \WP_Query
1342 */
1343 public function filter_edd_downloads_query( $query ) {
1344 $query['meta_query'][] = $this->tutor_product_meta_query();
1345 return $query;
1346 }
1347
1348 /**
1349 * Filter product in edd downloads archive page
1350 *
1351 * @since 1.4.9
1352 *
1353 * @param \WP_Query $wp_query WP Query instance.
1354 * @return \WP_Query
1355 */
1356 public function filter_archive_meta_query( $wp_query ) {
1357 if ( ! is_admin() && $wp_query->is_archive && $wp_query->get( 'post_type' ) === 'download' ) {
1358 $wp_query->set( 'meta_query', array( $this->tutor_product_meta_query() ) );
1359 }
1360 return $wp_query;
1361 }
1362
1363 /**
1364 * Removed course price if already enrolled at single course
1365 *
1366 * @since 1.5.8
1367 *
1368 * @param string $html HTML string.
1369 * @return string
1370 */
1371 public function remove_price_if_enrolled( $html ) {
1372 $should_removed = apply_filters( 'should_remove_price_if_enrolled', true );
1373
1374 if ( $should_removed ) {
1375 $course_id = get_the_ID();
1376 $enrolled = tutor_utils()->is_enrolled( $course_id );
1377 if ( $enrolled ) {
1378 $html = '';
1379 }
1380 }
1381 return $html;
1382 }
1383
1384 /**
1385 * Check if all lessons and quizzes done before mark course complete.
1386 *
1387 * @since 1.5.8
1388 *
1389 * @param string $html HTML string.
1390 * @return string
1391 */
1392 public function tutor_lms_hide_course_complete_btn( $html ) {
1393
1394 $completion_mode = tutor_utils()->get_option( 'course_completion_process' );
1395 if ( 'strict' !== $completion_mode ) {
1396 return $html;
1397 }
1398
1399 $completed_lesson = tutor_utils()->get_completed_lesson_count_by_course();
1400 $lesson_count = tutor_utils()->get_lesson_count_by_course();
1401
1402 if ( $completed_lesson < $lesson_count ) {
1403 return '<div class="tutor-alert tutor-warning tutor-mt-28">
1404 <div class="tutor-alert-text">
1405 <span class="tutor-alert-icon tutor-fs-4 tutor-icon-circle-info tutor-mr-12"></span>
1406 <span>' . __( 'Complete all lessons to mark this course as complete', 'tutor' ) . '</span>
1407 </div>
1408 </div>';
1409 }
1410
1411 $quizzes = array();
1412 $assignments = array();
1413
1414 $course_contents = tutor_utils()->get_course_contents_by_id();
1415 if ( tutor_utils()->count( $course_contents ) ) {
1416 foreach ( $course_contents as $content ) {
1417 if ( 'tutor_quiz' === $content->post_type ) {
1418 $quizzes[] = $content;
1419 }
1420 if ( 'tutor_assignments' === $content->post_type ) {
1421 $assignments[] = $content;
1422 }
1423 }
1424 }
1425
1426 $required_assignment_pass = 0;
1427
1428 foreach ( $assignments as $row ) {
1429
1430 $submitted_assignment = tutor_utils()->is_assignment_submitted( $row->ID );
1431 $is_reviewed_by_instructor = null === $submitted_assignment
1432 ? false
1433 : get_comment_meta( $submitted_assignment->comment_ID, 'evaluate_time', true );
1434
1435 if ( $submitted_assignment && $is_reviewed_by_instructor ) {
1436 $pass_mark = tutor_utils()->get_assignment_option( $submitted_assignment->comment_post_ID, 'pass_mark' );
1437 $given_mark = get_comment_meta( $submitted_assignment->comment_ID, 'assignment_mark', true );
1438
1439 if ( $given_mark < $pass_mark ) {
1440 $required_assignment_pass++;
1441 }
1442 } else {
1443 $required_assignment_pass++;
1444 }
1445 }
1446
1447 $is_quiz_pass = true;
1448 $required_quiz_pass = 0;
1449
1450 if ( tutor_utils()->count( $quizzes ) ) {
1451 foreach ( $quizzes as $quiz ) {
1452
1453 $attempt = tutor_utils()->get_quiz_attempt( $quiz->ID );
1454 if ( $attempt ) {
1455 $passing_grade = tutor_utils()->get_quiz_option( $quiz->ID, 'passing_grade', 0 );
1456 $earned_percentage = $attempt->earned_marks > 0 ? ( number_format( ( $attempt->earned_marks * 100 ) / $attempt->total_marks ) ) : 0;
1457
1458 if ( $earned_percentage < $passing_grade ) {
1459 $required_quiz_pass++;
1460 $is_quiz_pass = false;
1461 }
1462 } else {
1463 $required_quiz_pass++;
1464 $is_quiz_pass = false;
1465 }
1466 }
1467 }
1468
1469 if ( ! $is_quiz_pass || $required_assignment_pass > 0 ) {
1470 $_msg = '';
1471 $quiz_str = _n( 'quiz', 'quizzes', $required_quiz_pass, 'tutor' );
1472 $assignment_str = _n( 'assignment', 'assignments', $required_assignment_pass, 'tutor' );
1473
1474 if ( ! $is_quiz_pass && 0 == $required_assignment_pass ) {
1475 /* translators: %s: number of quiz pass required */
1476 $_msg = sprintf( __( 'You have to pass %1$s %2$s to complete this course.', 'tutor' ), $required_quiz_pass, $quiz_str );
1477 }
1478 if ( $is_quiz_pass && $required_assignment_pass > 0 ) {
1479 /* translators: %s: number of assignment pass required */
1480 $_msg = sprintf( __( 'You have to pass %1$s %2$s to complete this course.', 'tutor' ), $required_assignment_pass, $assignment_str );
1481 }
1482 if ( ! $is_quiz_pass && $required_assignment_pass > 0 ) {
1483 /* translators: %s: number of quiz pass required */
1484 $_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 );
1485 }
1486
1487 return '<div class="tutor-alert tutor-warning tutor-mt-28">
1488 <div class="tutor-alert-text">
1489 <span class="tutor-alert-icon tutor-fs-4 tutor-icon-circle-info tutor-mr-12"></span>
1490 <span>' . $_msg . '</span>
1491 </div>
1492 </div>';
1493 }
1494
1495 return $html;
1496 }
1497
1498 /**
1499 * Generate Gradebook
1500 *
1501 * @since 1.5.8
1502 *
1503 * @param string $html HTML string.
1504 * @return string
1505 */
1506 public function get_generate_greadbook( $html ) {
1507 if ( ! tutor_utils()->is_completed_course() ) {
1508 return '';
1509 }
1510 return $html;
1511 }
1512
1513 /**
1514 * Add social share content in header
1515 *
1516 * @since 1.6.3
1517 * @return void
1518 */
1519 public function social_share_content() {
1520 global $wp_query, $post;
1521 if ( $wp_query->is_single && ! empty( $wp_query->query_vars['post_type'] ) && $wp_query->query_vars['post_type'] === $this->course_post_type ) { ?>
1522 <!--Facebook-->
1523 <meta property="og:type" content="website"/>
1524 <meta property="og:image" content="<?php echo esc_url( get_tutor_course_thumbnail_src() ); ?>" />
1525 <meta property="og:description" content="<?php echo esc_html( $post->post_content ); ?>" />
1526 <!--Twitter-->
1527 <meta name="twitter:image" content="<?php echo esc_url( get_tutor_course_thumbnail_src() ); ?>">
1528 <meta name="twitter:description" content="<?php echo esc_html( $post->post_content ); ?>">
1529 <!--Google+-->
1530 <meta itemprop="image" content="<?php echo esc_url( get_tutor_course_thumbnail_src() ); ?>">
1531 <meta itemprop="description" content="<?php echo esc_html( $post->post_content ); ?>">
1532 <?php
1533 }
1534 }
1535
1536 /**
1537 * Delete associated enrollment
1538 *
1539 * @since 1.8.2
1540 *
1541 * @param integer $post_id post ID.
1542 * @return void
1543 */
1544 public function delete_associated_enrollment( $post_id ) {
1545 global $wpdb;
1546
1547 $enroll_id = $wpdb->get_var(
1548 $wpdb->prepare(
1549 "SELECT
1550 post_id
1551 FROM
1552 {$wpdb->postmeta}
1553 WHERE
1554 meta_key='_tutor_enrolled_by_order_id'
1555 AND meta_value = %d
1556 ",
1557 $post_id
1558 )
1559 );
1560
1561 if ( is_numeric( $enroll_id ) && $enroll_id > 0 ) {
1562
1563 $course_id = get_post_field( 'post_parent', $enroll_id );
1564 $user_id = get_post_field( 'post_author', $enroll_id );
1565
1566 tutor_utils()->cancel_course_enrol( $course_id, $user_id );
1567 }
1568 }
1569
1570 /**
1571 * Reset course progress.
1572 *
1573 * @since 1.5.8
1574 * @return void
1575 */
1576 public function tutor_reset_course_progress() {
1577 tutor_utils()->checking_nonce();
1578 $course_id = Input::post( 'course_id' );
1579
1580 if ( ! $course_id || ! is_numeric( $course_id ) || ! tutor_utils()->is_enrolled( $course_id ) ) {
1581 wp_send_json_error( array( 'message' => __( 'Invalid Course ID or Access Denied.', 'tutor' ) ) );
1582 return;
1583 }
1584
1585 tutor_utils()->delete_course_progress( $course_id );
1586 wp_send_json_success( array( 'redirect_to' => tutor_utils()->get_course_first_lesson( $course_id ) ) );
1587 }
1588
1589 /**
1590 * Do enroll if guest attempt to enroll and course is free
1591 *
1592 * @since 1.9.8
1593 *
1594 * @param integer $course_id course ID.
1595 * @param integer $user_id user ID.
1596
1597 * @return void
1598 */
1599 public function enroll_after_login_if_attempt( int $course_id, int $user_id ) {
1600 $course_id = sanitize_text_field( $course_id );
1601 if ( $course_id ) {
1602 $is_purchasable = tutor_utils()->is_course_purchasable( $course_id );
1603 if ( ! $is_purchasable ) {
1604 tutor_utils()->do_enroll( $course_id, $order_id = 0, $user_id );
1605 do_action( 'guest_attempt_after_enrollment', $course_id );
1606 }
1607 }
1608 }
1609
1610 /**
1611 * Handle course enrollment
1612 *
1613 * @since 2.1.0
1614 * @return void
1615 */
1616 public function course_enrollment() {
1617 tutor_utils()->checking_nonce();
1618
1619 $course_id = Input::post( 'course_id', 0, Input::TYPE_INT );
1620 $user_id = get_current_user_id();
1621
1622 if ( $course_id ) {
1623 $enroll = tutor_utils()->do_enroll( $course_id, 0, $user_id );
1624 if ( $enroll ) {
1625 wp_send_json_success( __( 'Enrollment successfully done!', 'tutor' ) );
1626 } else {
1627 wp_send_json_error( __( 'Enrollment failed, please try again!', 'tutor' ) );
1628 }
1629 } else {
1630 wp_send_json_error( __( 'Invalid course ID', 'tutor' ) );
1631 }
1632 }
1633
1634 /**
1635 * After trash a course direct to the course list page
1636 *
1637 * @since 2.1.7
1638 *
1639 * @param integer $post_id int course id.
1640 *
1641 * @return void
1642 */
1643 public static function redirect_to_course_list_page( int $post_id ): void {
1644 $post = get_post( $post_id );
1645 if ( tutor()->course_post_type === $post->post_type ) {
1646 $is_gutenberg_enabled = tutor_utils()->get_option( 'enable_gutenberg_course_edit' );
1647 if ( ! $is_gutenberg_enabled ) {
1648 wp_safe_redirect( admin_url( 'admin.php?page=tutor' ) );
1649 exit;
1650 }
1651 }
1652 }
1653
1654 }
1655