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