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