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