PluginProbe ʕ •ᴥ•ʔ
Tutor LMS – eLearning and online course solution / 1.8.3
Tutor LMS – eLearning and online course solution v1.8.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 5 years ago Admin.php 5 years ago Ajax.php 5 years ago Assets.php 5 years ago Course.php 5 years ago Course_Filter.php 5 years ago Course_Settings_Tabs.php 5 years ago Course_Widget.php 5 years ago Custom_Validation.php 5 years ago Dashboard.php 5 years ago Email.php 5 years ago FormHandler.php 5 years ago Frontend.php 5 years ago Gutenberg.php 5 years ago Instructor.php 5 years ago Instructors_List.php 5 years ago Lesson.php 5 years ago Options.php 5 years ago Post_types.php 5 years ago Private_Course_Access.php 5 years ago Q_and_A.php 5 years ago Question_Answers_List.php 5 years ago Quiz.php 5 years ago Quiz_Attempts_List.php 5 years ago RestAPI.php 5 years ago Rewrite_Rules.php 5 years ago Shortcode.php 5 years ago Student.php 5 years ago Students_List.php 5 years ago Taxonomies.php 5 years ago Template.php 5 years ago Theme_Compatibility.php 5 years ago Tools.php 5 years ago Tutor.php 5 years ago TutorEDD.php 5 years ago Tutor_Base.php 5 years ago Tutor_List_Table.php 5 years ago Tutor_Setup.php 5 years ago Upgrader.php 5 years ago User.php 5 years ago Utils.php 5 years ago Video_Stream.php 5 years ago Withdraw.php 5 years ago Withdraw_Requests_List.php 5 years ago WooCommerce.php 5 years ago
Course.php
1284 lines
1 <?php
2 namespace TUTOR;
3
4 if ( ! defined( 'ABSPATH' ) )
5 exit;
6
7 class Course extends Tutor_Base {
8
9 private $additional_meta=array(
10 '_tutor_disable_qa',
11 '_tutor_is_public_course'
12 );
13
14 public function __construct() {
15 parent::__construct();
16
17 add_action( 'add_meta_boxes', array($this, 'register_meta_box') );
18 add_action('save_post_'.$this->course_post_type, array($this, 'save_course_meta'), 10, 2);
19 add_action('wp_ajax_tutor_add_course_topic', array($this, 'tutor_add_course_topic'));
20 add_action('wp_ajax_tutor_update_topic', array($this, 'tutor_update_topic'));
21
22 //Add Column
23 add_filter( "manage_{$this->course_post_type}_posts_columns", array($this, 'add_column'), 10,1 );
24 add_action( "manage_{$this->course_post_type}_posts_custom_column" , array($this, 'custom_lesson_column'), 10, 2 );
25
26 add_action('admin_action_tutor_delete_topic', array($this, 'tutor_delete_topic'));
27 add_action('admin_action_tutor_delete_announcement', array($this, 'tutor_delete_announcement'));
28
29 //Frontend Action
30 add_action('template_redirect', array($this, 'enroll_now'));
31 add_action('template_redirect', array($this, 'mark_course_complete'));
32
33 //Modal Perform
34 add_action('wp_ajax_tutor_load_instructors_modal', array($this, 'tutor_load_instructors_modal'));
35 add_action('wp_ajax_tutor_add_instructors_to_course', array($this, 'tutor_add_instructors_to_course'));
36 add_action('wp_ajax_detach_instructor_from_course', array($this, 'detach_instructor_from_course'));
37
38 /**
39 * Frontend Dashboard
40 */
41 add_action('wp_ajax_tutor_delete_dashboard_course', array($this, 'tutor_delete_dashboard_course'));
42
43 /**
44 * Gutenberg author support
45 */
46 add_filter('wp_insert_post_data', array($this, 'tutor_add_gutenberg_author'), '99', 2);
47
48 /**
49 * Frontend metabox supports for course builder
50 * @since v.1.3.4
51 */
52 add_action('tutor/dashboard_course_builder_form_field_after', array($this, 'register_meta_box_in_frontend'));
53
54 /**
55 * Do Stuff for the course save from frontend
56 */
57 add_action('save_tutor_course', array($this, 'attach_product_with_course'), 10, 2);
58
59 /**
60 * Add course level to course settings
61 * @since v.1.4.1
62 */
63 add_action('tutor_course/settings_tab_content/after/general', array($this, 'add_course_level_to_settings'));
64
65 /**
66 * Enable Disable Course Details Page Feature
67 * @since v.1.4.8
68 */
69 $this->course_elements_enable_disable();
70
71 /**
72 * @since v.1.4.8
73 * Check if course starting, set meta if starting
74 */
75 add_action('tutor_lesson_load_before', array($this, 'tutor_lesson_load_before'));
76
77 /**
78 * @since v.1.4.9
79 * Filter product in shop page
80 */
81 $this->filter_product_in_shop_page();
82
83 /**
84 * Remove the course price if enrolled
85 * @since 1.5.8
86 */
87 add_filter('tutor_course_price', array($this, 'remove_price_if_enrolled'));
88
89 /**
90 * Remove course complete button if course completion is strict mode
91 * @since v.1.6.1
92 */
93 add_filter('tutor_course/single/complete_form', array($this, 'tutor_lms_hide_course_complete_btn'));
94 add_filter('get_gradebook_generate_form_html', array($this, 'get_generate_greadbook'));
95
96 /**
97 * Add social share content in header
98 * @since v.1.6.3
99 */
100 add_action('wp_head', array($this, 'social_share_content'));
101
102 /**
103 * Delete course data after deleted course
104 * @since v.1.6.6
105 */
106 add_action('deleted_post', array($this, 'delete_tutor_course_data'));
107 add_action('tutor/dashboard_course_builder_form_field_after', array($this, 'tutor_course_setting_metabox_frontend'));
108
109 /**
110 * Delete course data after deleted course
111 * @since v.1.8.2
112 */
113 add_action('before_delete_post', array($this, 'delete_associated_enrollment'));
114 }
115
116 /**
117 * Registering metabox
118 */
119 public function register_meta_box(){
120 $coursePostType = tutor()->course_post_type;
121 $course_marketplace = tutor_utils()->get_option('enable_course_marketplace');
122 //add_meta_box( 'tutor-course-levels', __( 'Course Level', 'tutor' ), array($this, 'course_level_metabox'), $coursePostType );
123 add_meta_box( 'tutor-course-topics', __( 'Course Builder', 'tutor' ), array($this, 'course_meta_box'), $coursePostType );
124 add_meta_box( 'tutor-course-additional-data', __( 'Additional Data', 'tutor' ), array($this, 'course_additional_data_meta_box'), $coursePostType );
125 add_meta_box( 'tutor-course-videos', __( 'Video', 'tutor' ), array($this, 'video_metabox'), $coursePostType );
126 if ($course_marketplace) {
127 add_meta_box( 'tutor-instructors', __( 'Instructors', 'tutor' ), array( $this, 'instructors_metabox' ), $coursePostType );
128 }
129
130 /**
131 * Tutor course sidebar settings metabox
132 * @since v.1.7.0
133 */
134 add_meta_box( 'tutor-course-sidebar-settings', __( 'Tutor Settings', 'tutor' ), array($this, 'tutor_course_setting_metabox'), $coursePostType, 'side' );
135 }
136
137 public function course_meta_box($echo = true){
138 ob_start();
139 include tutor()->path.'views/metabox/course-topics.php';
140 $content = ob_get_clean();
141
142 if ($echo){
143 echo $content;
144 }else{
145 return $content;
146 }
147 }
148
149 public function course_additional_data_meta_box($echo = true){
150
151 ob_start();
152 include tutor()->path.'views/metabox/course-additional-data.php';
153 $content = ob_get_clean();
154
155 if ($echo){
156 echo $content;
157 }else{
158 return $content;
159 }
160 }
161
162 public function video_metabox($echo = true){
163 ob_start();
164 include tutor()->path.'views/metabox/video-metabox.php';
165 $content = ob_get_clean();
166
167 if ($echo){
168 echo $content;
169 }else{
170 return $content;
171 }
172 }
173
174 public function course_level_metabox($echo = true){
175 ob_start();
176 include tutor()->path.'views/metabox/course-level-metabox.php';
177 $content = ob_get_clean();
178
179 if ($echo){
180 echo $content;
181 }else{
182 return $content;
183 }
184 }
185
186 public function instructors_metabox($echo = true){
187 ob_start();
188 include tutor()->path . 'views/metabox/instructors-metabox.php';
189 $content = ob_get_clean();
190
191 if ($echo){
192 echo $content;
193 }else{
194 return $content;
195 }
196 }
197
198 /**
199 * Register metabox in course builder tutor
200 * @since v.1.3.4
201 */
202 public function register_meta_box_in_frontend(){
203 do_action('tutor_course_builder_metabox_before', get_the_ID());
204 course_builder_section_wrap($this->video_metabox($echo = false), __( 'Video', 'tutor' ) );
205 course_builder_section_wrap($this->course_meta_box($echo = false), __( 'Course Builder', 'tutor' ) );
206 course_builder_section_wrap($this->instructors_metabox($echo = false), __( 'Instructors', 'tutor' ) );
207 course_builder_section_wrap($this->course_additional_data_meta_box($echo = false), __( 'Additional Data', 'tutor' ) );
208 do_action('tutor_course_builder_metabox_after', get_the_ID());
209 }
210
211 /**
212 * @param $post_ID
213 *
214 * Insert Topic and attached it with Course
215 */
216 public function save_course_meta($post_ID, $post){
217 global $wpdb;
218
219 do_action( "tutor_save_course", $post_ID, $post);
220
221 /**
222 * Save course price type
223 */
224 $price_type = tutils()->array_get('tutor_course_price_type', $_POST);
225 if ($price_type){
226 update_post_meta($post_ID, '_tutor_course_price_type', $price_type);
227 }
228
229 //Course Duration
230 if ( ! empty($_POST['course_duration'])){
231 $video = tutils()->sanitize_array($_POST['course_duration']);
232 update_post_meta($post_ID, '_course_duration', $video);
233 }
234
235 if ( ! empty($_POST['course_level'])){
236 $course_level = sanitize_text_field($_POST['course_level']);
237 update_post_meta($post_ID, '_tutor_course_level', $course_level);
238 }
239
240 $additional_data_edit = tutils()->avalue_dot('_tutor_course_additional_data_edit', $_POST);
241 if ($additional_data_edit) {
242 if (!empty($_POST['course_benefits'])) {
243 $course_benefits = wp_kses_post($_POST['course_benefits']);
244 update_post_meta($post_ID, '_tutor_course_benefits', $course_benefits);
245 } else {
246 delete_post_meta($post_ID, '_tutor_course_benefits');
247 }
248
249 if (!empty($_POST['course_requirements'])) {
250 $requirements = wp_kses_post($_POST['course_requirements']);
251 update_post_meta($post_ID, '_tutor_course_requirements', $requirements);
252 } else {
253 delete_post_meta($post_ID, '_tutor_course_requirements');
254 }
255
256 if (!empty($_POST['course_target_audience'])) {
257 $target_audience = wp_kses_post($_POST['course_target_audience']);
258 update_post_meta($post_ID, '_tutor_course_target_audience', $target_audience);
259 } else {
260 delete_post_meta($post_ID, '_tutor_course_target_audience');
261 }
262
263 if (!empty($_POST['course_material_includes'])) {
264 $material_includes = wp_kses_post($_POST['course_material_includes']);
265 update_post_meta($post_ID, '_tutor_course_material_includes', $material_includes);
266 } else {
267 delete_post_meta($post_ID, '_tutor_course_material_includes');
268 }
269 }
270
271
272 /**
273 * Sorting Topics and lesson
274 */
275 if ( ! empty($_POST['tutor_topics_lessons_sorting'])){
276 $new_order = sanitize_text_field(stripslashes($_POST['tutor_topics_lessons_sorting']));
277 $order = json_decode($new_order, true);
278
279 if (is_array($order) && count($order)){
280 $i = 0;
281 foreach ($order as $topic ){
282 $i++;
283 $wpdb->update(
284 $wpdb->posts,
285 array('menu_order' => $i),
286 array('ID' => $topic['topic_id'])
287 );
288
289 /**
290 * Removing All lesson with topic
291 */
292
293 $wpdb->update(
294 $wpdb->posts,
295 array('post_parent' => 0),
296 array('post_parent' => $topic['topic_id'])
297 );
298
299 /**
300 * Lesson Attaching with topic ID
301 * sorting lesson
302 */
303 if (isset($topic['lesson_ids'])){
304 $lesson_ids = $topic['lesson_ids'];
305 }else{
306 $lesson_ids = array();
307 }
308 if (count($lesson_ids)){
309 foreach ($lesson_ids as $lesson_key => $lesson_id ){
310 $wpdb->update(
311 $wpdb->posts,
312 array('post_parent' => $topic['topic_id'], 'menu_order' => $lesson_key),
313 array('ID' => $lesson_id)
314 );
315 }
316 }
317 }
318 }
319 }
320
321 if ($additional_data_edit) {
322 if ( ! empty($_POST['video']['source'])) { //Video
323 $video = tutor_utils()->array_get('video', $_POST);
324 update_post_meta($post_ID, '_video', $video);
325 }else{
326 delete_post_meta($post_ID, '_video');
327 }
328 }
329
330 /**
331 * Adding author to instructor automatically
332 */
333
334 $author_id = $post->post_author;
335 $attached = (int) $wpdb->get_var($wpdb->prepare(
336 "SELECT COUNT(umeta_id) FROM {$wpdb->usermeta}
337 WHERE user_id = %d AND meta_key = '_tutor_instructor_course_id' AND meta_value = %d ", $author_id, $post_ID));
338
339 if ( ! $attached){
340 add_user_meta($author_id, '_tutor_instructor_course_id', $post_ID);
341 }
342
343 /**
344 * Disable question and answer for this course
345 * @since 1.7.0
346 */
347 if ($additional_data_edit) {
348 foreach($this->additional_meta as $key){
349 update_post_meta($post_ID, $key, (isset($_POST[$key]) ? 'yes' : 'no'));
350 }
351 }
352
353 do_action( "tutor_save_course_after", $post_ID, $post);
354 /**
355 * @since 1.8.0
356 */
357 setcookie( "tutor_course_updated", true, time() + 5 );
358 }
359
360 /**
361 * Tutor add course topic
362 */
363 public function tutor_add_course_topic(){
364 tutils()->checking_nonce();
365
366 if (empty($_POST['topic_title']) ) {
367 wp_send_json_error();
368 }
369 $course_id = (int) tutor_utils()->avalue_dot('tutor_topic_course_ID', $_POST);
370 $next_topic_order_id = tutor_utils()->get_next_topic_order_id($course_id);
371
372 if(!tutils()->can_user_manage('course', $course_id)) {
373 wp_send_json_error( array('message'=>__('Access Denied', 'tutor')) );
374 }
375
376 $topic_title = sanitize_text_field( $_POST['topic_title'] );
377 $topic_summery = wp_kses_post( $_POST['topic_summery'] );
378
379 $post_arr = array(
380 'post_type' => 'topics',
381 'post_title' => $topic_title,
382 'post_content' => $topic_summery,
383 'post_status' => 'publish',
384 'post_author' => get_current_user_id(),
385 'post_parent' => $course_id,
386 'menu_order' => $next_topic_order_id,
387 );
388 $current_topic_id = wp_insert_post( $post_arr );
389
390 ob_start();
391 include tutor()->path.'views/metabox/course-contents.php';
392 $course_contents = ob_get_clean();
393
394 wp_send_json_success(array('course_contents' => $course_contents));
395 }
396
397 /**
398 * Update the topic
399 */
400 public function tutor_update_topic(){
401 tutils()->checking_nonce();
402
403 $topic_id = (int) sanitize_text_field($_POST['topic_id']);
404 $topic_title = sanitize_text_field($_POST['topic_title']);
405 $topic_summery = wp_kses_post($_POST['topic_summery']);
406
407 if(!tutils()->can_user_manage('topic', $topic_id)) {
408 wp_send_json_error( array('message'=>__('Access Denied', 'tutor')) );
409 }
410
411 $topic_attr = array(
412 'ID' => $topic_id,
413 'post_title' => $topic_title,
414 'post_content' => $topic_summery,
415 );
416 wp_update_post( $topic_attr );
417
418 wp_send_json_success(array('msg' => __('Topic has been updated', 'tutor') ));
419 }
420
421
422 /**
423 * @param $columns
424 *
425 * @return mixed
426 *
427 * Add Lesson column
428 */
429 public function add_column($columns){
430 $date_col = $columns['date'];
431 unset($columns['date']);
432 $columns['lessons'] = __('Lessons', 'tutor');
433 $columns['students'] = __('Students', 'tutor');
434 $columns['price'] = __('Price', 'tutor');
435 $columns['date'] = $date_col;
436
437 return $columns;
438 }
439
440 /**
441 * @param $column
442 * @param $post_id
443 *
444 */
445 public function custom_lesson_column($column, $post_id ){
446 if ($column === 'lessons'){
447 echo tutor_utils()->get_lesson_count_by_course($post_id);
448 }
449
450 if ($column === 'students'){
451 echo tutor_utils()->count_enrolled_users_by_course($post_id);
452 }
453
454 if ($column === 'price'){
455 $price = tutor_utils()->get_course_price($post_id);
456 if ($price){
457 $monetize_by = tutils()->get_option('monetize_by');
458 if (function_exists('wc_price') && $monetize_by === 'wc'){
459 echo '<span class="tutor-label-success">'.wc_price($price).'</span>';
460 }else{
461 echo '<span class="tutor-label-success">'.$price.'</span>';
462 }
463 }else{
464 echo apply_filters('tutor-loop-default-price', 'free');
465 }
466 }
467 }
468
469
470 public function tutor_delete_topic(){
471
472 tutils()->checking_nonce('get');
473
474 !isset($_GET['topic_id']) ? exit() : 0;
475
476 global $wpdb;
477
478 $topic_id = (int) sanitize_text_field($_GET['topic_id']);
479 $wpdb->update(
480 $wpdb->posts,
481 array('post_parent' => 0),
482 array('post_parent' => $topic_id)
483 );
484
485 $wpdb->delete(
486 $wpdb->postmeta,
487 array('post_id' => $topic_id)
488 );
489
490 wp_delete_post($topic_id);
491 wp_safe_redirect(wp_get_referer());
492 }
493
494 public function tutor_delete_announcement(){
495 tutor_utils()->checking_nonce('get');
496
497 $announcement_id = (int) sanitize_text_field($_GET['topic_id']);
498
499 wp_delete_post($announcement_id);
500 wp_safe_redirect(wp_get_referer());
501 }
502
503 public function enroll_now(){
504
505 //Checking if action comes from Enroll form
506 if (tutor_utils()->array_get('tutor_course_action', $_POST) !== '_tutor_course_enroll_now' || ! isset($_POST['tutor_course_id']) ){
507 return;
508 }
509 //Checking Nonce
510 tutor_utils()->checking_nonce();
511
512 $user_id = get_current_user_id();
513 if ( ! $user_id){
514 exit(__('Please Sign In first', 'tutor'));
515 }
516
517 $course_id = (int) sanitize_text_field($_POST['tutor_course_id']);
518 $user_id = get_current_user_id();
519
520 /**
521 * TODO: need to check purchase information
522 */
523
524 $is_purchasable = tutor_utils()->is_course_purchasable($course_id);
525
526 /**
527 * If is is not purchasable, it's free, and enroll right now
528 *
529 * if purchasable, then process purchase.
530 *
531 * @since: v.1.0.0
532 */
533 if ($is_purchasable){
534 //process purchase
535
536 }else{
537 //Free enroll
538 tutor_utils()->do_enroll($course_id);
539 }
540
541 $referer_url = wp_get_referer();
542 wp_redirect($referer_url);
543 }
544
545 /**
546 *
547 * Mark complete completed
548 *
549 * @since v.1.0.0
550 */
551 public function mark_course_complete(){
552 if ( ! isset($_POST['tutor_action']) || $_POST['tutor_action'] !== 'tutor_complete_course' ){
553 return;
554 }
555 //Checking nonce
556 tutor_utils()->checking_nonce();
557
558 $user_id = get_current_user_id();
559
560 //TODO: need to show view if not signed_in
561 if ( ! $user_id){
562 die(__('Please Sign-In', 'tutor'));
563 }
564
565 $course_id = (int) sanitize_text_field($_POST['course_id']);
566
567 do_action('tutor_course_complete_before', $course_id);
568 /**
569 * Marking course completed at Comment
570 */
571
572 global $wpdb;
573
574 $date = date("Y-m-d H:i:s", tutor_time());
575
576 //Making sure that, hash is unique
577 do{
578 $hash = substr(md5(wp_generate_password(32).$date.$course_id.$user_id), 0, 16);
579 $hasHash = (int) $wpdb->get_var($wpdb->prepare(
580 "SELECT COUNT(comment_ID) from {$wpdb->comments}
581 WHERE comment_agent = 'TutorLMSPlugin' AND comment_type = 'course_completed' AND comment_content = %s ", $hash));
582
583 }while($hasHash > 0);
584
585 $data = array(
586 'comment_post_ID' => $course_id,
587 'comment_author' => $user_id,
588 'comment_date' => $date,
589 'comment_date_gmt' => get_gmt_from_date($date),
590 'comment_content' => $hash, //Identification Hash
591 'comment_approved' => 'approved',
592 'comment_agent' => 'TutorLMSPlugin',
593 'comment_type' => 'course_completed',
594 'user_id' => $user_id,
595 );
596
597 $wpdb->insert($wpdb->comments, $data);
598
599 do_action('tutor_course_complete_after', $course_id, $user_id);
600
601 wp_redirect(get_the_permalink($course_id));
602 }
603
604
605 public function tutor_load_instructors_modal(){
606 tutils()->checking_nonce();
607
608 global $wpdb;
609
610 $course_id = (int) sanitize_text_field($_POST['course_id']);
611 $search_terms = sanitize_text_field(tutor_utils()->avalue_dot('search_terms', $_POST));
612
613 if(!tutils()->can_user_manage('course', $course_id)) {
614 wp_send_json_error( array('message'=>__('Access Denied', 'tutor')) );
615 }
616
617 $saved_instructors = tutor_utils()->get_instructors_by_course($course_id);
618 $instructors = array();
619
620 $not_in_sql = apply_filters('tutor_instructor_query_when_exists', " AND ID <1 ");
621
622 if ($saved_instructors){
623 $saved_instructors_ids = wp_list_pluck($saved_instructors, 'ID');
624 $instructor_not_in_ids = implode(',', $saved_instructors_ids);
625 $not_in_sql .= "AND ID NOT IN($instructor_not_in_ids) ";
626 }
627
628 $search_sql = '';
629 if ($search_terms){
630 $search_sql = "AND (user_login like '%{$search_terms}%' or user_nicename like '%{$search_terms}%' or display_name like '%{$search_terms}%') ";
631 }
632
633 $instructors = $wpdb->get_results("select ID, display_name from {$wpdb->users}
634 INNER JOIN {$wpdb->usermeta} ON ID = user_id AND meta_key = '_tutor_instructor_status' AND meta_value = 'approved'
635 WHERE 1=1 {$not_in_sql} {$search_sql} limit 10 ");
636
637 $output = '';
638 if (is_array($instructors) && count($instructors)){
639 $instructor_output = '';
640 foreach ($instructors as $instructor){
641 $instructor_output .= "<p><label><input type='radio' name='tutor_instructor_ids[]' value='{$instructor->ID}' > {$instructor->display_name} </label></p>";
642 }
643
644 $output .= apply_filters('tutor_course_instructors_html', $instructor_output, $instructors);
645
646 }else{
647 $output .= __('<p>No instructor available or you have already added maximum instructors</p>', 'tutor');
648 }
649
650
651 if ( ! defined('TUTOR_MT_VERSION')){
652 $output .= '<p class="tutor-notice-warning" style="margin-top: 50px; font-size: 14px;">'. sprintf( __('To add unlimited multiple instructors in your course, get %sTutor LMS Pro%s', 'tutor'), '<a href="https://www.themeum.com/product/tutor-lms" target="_blank">', "</a>" ) .'</p>';
653 }
654
655 wp_send_json_success(array('output' => $output));
656 }
657
658 public function tutor_add_instructors_to_course(){
659 tutils()->checking_nonce();
660
661 $course_id = (int) sanitize_text_field($_POST['course_id']);
662 $instructor_ids = tutor_utils()->avalue_dot('tutor_instructor_ids', $_POST);
663
664 if(!tutils()->can_user_manage('course', $course_id)) {
665 wp_send_json_error( array('message'=>__('Access Denied', 'tutor')) );
666 }
667
668 if (is_array($instructor_ids) && count($instructor_ids)){
669 foreach ($instructor_ids as $instructor_id){
670 add_user_meta($instructor_id, '_tutor_instructor_course_id', $course_id);
671 }
672 }
673
674 $saved_instructors = tutor_utils()->get_instructors_by_course($course_id);
675 $output = '';
676
677 if ($saved_instructors){
678 foreach ($saved_instructors as $t){
679
680 $output .= '<div id="added-instructor-id-'.$t->ID.'" class="added-instructor-item added-instructor-item-'.$t->ID.'" data-instructor-id="'.$t->ID.'">
681 <span class="instructor-icon">'.get_avatar($t->ID, 30).'</span>
682 <span class="instructor-name"> '.$t->display_name.' </span>
683 <span class="instructor-control">
684 <a href="javascript:;" class="tutor-instructor-delete-btn"><i class="tutor-icon-line-cross"></i></a>
685 </span>
686 </div>';
687 }
688 }
689
690 wp_send_json_success(array('output' => $output));
691 }
692
693 public function detach_instructor_from_course(){
694 tutils()->checking_nonce();
695
696 global $wpdb;
697
698 $instructor_id = (int) sanitize_text_field($_POST['instructor_id']);
699 $course_id = (int) sanitize_text_field($_POST['course_id']);
700
701 if(!tutils()->can_user_manage('course', $course_id)) {
702 wp_send_json_error( array('message'=>__('Access Denied', 'tutor')) );
703 }
704
705 $wpdb->delete($wpdb->usermeta, array('user_id' => $instructor_id, 'meta_key' => '_tutor_instructor_course_id', 'meta_value' => $course_id) );
706 wp_send_json_success();
707 }
708
709 public function tutor_delete_dashboard_course(){
710 tutils()->checking_nonce();
711
712 $course_id = intval(sanitize_text_field($_POST['course_id']));
713
714 if(!tutils()->can_user_manage('course', $course_id)) {
715 wp_send_json_error( array('message'=>__('Access Denied', 'tutor')) );
716 }
717
718 wp_trash_post($course_id);
719 wp_send_json_success(['element'=>'course']);
720 }
721
722
723 public function tutor_add_gutenberg_author($data , $postarr){
724 global $wpdb;
725
726 $courses_post_type = tutor()->course_post_type;
727 $post_type = tutils()->array_get('post_type', $postarr);
728
729 if ($courses_post_type === $post_type){
730 $post_ID = (int) tutor_utils()->avalue_dot('ID', $postarr);
731 $post_author = (int) $wpdb->get_var($wpdb->prepare("SELECT post_author FROM {$wpdb->posts} WHERE ID = %d ", $post_ID));
732
733 if ($post_author > 0){
734 $data['post_author'] = $post_author;
735 }else{
736 $data['post_author'] = get_current_user_id();
737 }
738 }
739
740 return $data;
741 }
742
743
744 /**
745 * @param $post_ID
746 * @param $postData
747 *
748 * Attach product during save course from the frontend course dashboard.
749 *
750 * @return string
751 *
752 * @since v.1.3.4
753 */
754 public function attach_product_with_course($post_ID, $postData){
755 $attached_product_id = tutor_utils()->get_course_product_id($post_ID);
756 $course_price = sanitize_text_field(tutor_utils()->array_get('course_price', $_POST));
757
758 if ( ! $course_price){
759 return;
760 }
761
762 $monetize_by = tutor_utils()->get_option('monetize_by');
763 $course = get_post($post_ID);
764
765 if ($monetize_by === 'wc'){
766
767 $is_update = false;
768 if ($attached_product_id){
769 $wc_product = get_post_meta($attached_product_id, '_product_version', true);
770 if ($wc_product){
771 $is_update = true;
772 }
773 }
774
775 if ($is_update) {
776 $productObj = wc_get_product($attached_product_id);
777 $productObj->set_price($course_price); // set product price
778 $productObj->set_regular_price($course_price); // set product regular price
779 $product_id = $productObj->save();
780 if($productObj->is_type('subscription')) {
781 update_post_meta( $attached_product_id, '_subscription_price', $course_price );
782 }
783 } else {
784 $productObj = new \WC_Product();
785 $productObj->set_name($course->post_title);
786 $productObj->set_status('publish');
787 $productObj->set_price($course_price); // set product price
788 $productObj->set_regular_price($course_price); // set product regular price
789
790 $product_id = $productObj->save();
791 if ($product_id) {
792 update_post_meta( $post_ID, '_tutor_course_product_id', $product_id );
793 //Mark product for woocommerce
794 update_post_meta( $product_id, '_virtual', 'yes' );
795 update_post_meta( $product_id, '_tutor_product', 'yes' );
796
797 $coursePostThumbnail = get_post_meta( $post_ID, '_thumbnail_id', true );
798 if ( $coursePostThumbnail ) {
799 set_post_thumbnail( $product_id, $coursePostThumbnail );
800 }
801 }
802 }
803
804 }elseif ($monetize_by === 'edd'){
805
806 $is_update = false;
807
808 if ($attached_product_id){
809 $edd_price = get_post_meta($attached_product_id, 'edd_price', true);
810 if ($edd_price){
811 $is_update = true;
812 }
813 }
814
815 if ($is_update){
816 //Update the product
817 update_post_meta( $attached_product_id, 'edd_price', $course_price );
818 }else{
819 //Create new product
820
821 $post_arr = array(
822 'post_type' => 'download',
823 'post_title' => $course->post_title,
824 'post_status' => 'publish',
825 'post_author' => get_current_user_id(),
826 );
827 $download_id = wp_insert_post( $post_arr );
828 if ($download_id ) {
829 //edd_price
830 update_post_meta( $download_id, 'edd_price', $course_price );
831
832 update_post_meta( $post_ID, '_tutor_course_product_id', $download_id );
833 //Mark product for EDD
834 update_post_meta( $download_id, '_tutor_product', 'yes' );
835
836 $coursePostThumbnail = get_post_meta( $post_ID, '_thumbnail_id', true );
837 if ( $coursePostThumbnail ) {
838 set_post_thumbnail( $download_id, $coursePostThumbnail );
839 }
840
841 }
842
843 }
844
845
846 }
847
848 }
849
850
851 /**
852 * Add Course level to course settings
853 * @since v.1.4.1
854 */
855 public function add_course_level_to_settings(){
856 include tutor()->path.'views/metabox/course-level-metabox.php';
857 }
858
859 /**
860 * Check if course starting
861 *
862 * @since v.1.4.8
863 */
864 public function tutor_lesson_load_before(){
865 $course_id = tutils()->get_course_id_by_content(get_the_ID());
866 $completed_lessons = tutor_utils()->get_completed_lesson_count_by_course($course_id);
867 if (is_user_logged_in()){
868 $is_course_started = get_post_meta($course_id, '_tutor_course_started', true);
869 if ( ! $completed_lessons && ! $is_course_started){
870 update_post_meta($course_id, '_tutor_course_started', tutor_time());
871 do_action('tutor/course/started', $course_id);
872 }
873 }
874 }
875
876 /**
877 * Add Course level to course settings
878 * @since v.1.4.8
879 */
880 public function course_elements_enable_disable(){
881 add_filter('tutor_course/single/completing-progress-bar', array($this, 'enable_disable_course_progress_bar') );
882 add_filter('tutor_course/single/material_includes', array($this, 'enable_disable_material_includes') );
883 add_filter('tutor_course/single/content', array($this, 'enable_disable_course_content') );
884 add_filter('tutor_course/single/benefits_html', array($this, 'enable_disable_course_benefits') );
885 add_filter('tutor_course/single/requirements_html', array($this, 'enable_disable_course_requirements') );
886 add_filter('tutor_course/single/audience_html', array($this, 'enable_disable_course_target_audience') );
887 add_filter('tutor_course/single/enrolled/nav_items', array($this, 'enable_disable_course_nav_items') );
888 }
889
890 /**
891 * Enable disable course progress bar
892 * @since v.1.4.8
893 */
894 public function enable_disable_course_progress_bar($html){
895 $disable_option = (bool) get_tutor_option('disable_course_progress_bar');
896 if($disable_option){
897 return '';
898 }
899 return $html;
900 }
901
902 /**
903 * Enable disable material includes
904 * @since v.1.4.8
905 */
906 public function enable_disable_material_includes($html){
907 $disable_option = (bool) get_tutor_option('disable_course_material');
908 if($disable_option){
909 return '';
910 }
911 return $html;
912 }
913
914 /**
915 * Enable disable course content
916 * @since v.1.4.8
917 */
918 public function enable_disable_course_content($html){
919 $disable_option = (bool) get_tutor_option('disable_course_description');
920 if($disable_option){
921 return '';
922 }
923 return $html;
924 }
925
926 /**
927 * Enable disable course benefits
928 * @since v.1.4.8
929 */
930 public function enable_disable_course_benefits($html){
931 $disable_option = (bool) get_tutor_option('disable_course_benefits');
932 if($disable_option){
933 return '';
934 }
935 return $html;
936 }
937
938 /**
939 * Enable disable course requirements
940 * @since v.1.4.8
941 */
942 public function enable_disable_course_requirements($html){
943 $disable_option = (bool) get_tutor_option('disable_course_requirements');
944 if($disable_option){
945 return '';
946 }
947 return $html;
948 }
949
950 /**
951 * Enable disable course target audience
952 * @since v.1.4.8
953 */
954 public function enable_disable_course_target_audience($html){
955 $disable_option = (bool) get_tutor_option('disable_course_target_audience');
956 if($disable_option){
957 return '';
958 }
959 return $html;
960 }
961
962 /**
963 * Enable disable course nav items
964 * @since v.1.4.8
965 */
966 public function enable_disable_course_nav_items($items){
967 global $wp_query, $post;
968 $enable_q_and_a_on_course = (bool) get_tutor_option('enable_q_and_a_on_course');
969 $disable_course_announcements = (bool) get_tutor_option('disable_course_announcements');
970
971 $disable_qa_for_this_course = ($wp_query->is_single && !empty($post)) ? get_post_meta($post->ID, '_tutor_disable_qa', true) : '';
972
973 if(!$enable_q_and_a_on_course || $disable_qa_for_this_course == 'yes') {
974 if(tutils()->array_get('questions', $items)) {
975 unset($items['questions']);
976 }
977 }
978 if($disable_course_announcements){
979 if(tutils()->array_get('announcements', $items)) {
980 unset($items['announcements']);
981 }
982 }
983 return $items;
984 }
985
986 /**
987 * Filter product in shop page
988 * @since v.1.4.9
989 */
990 public function filter_product_in_shop_page(){
991 $hide_course_from_shop_page = (bool) get_tutor_option('hide_course_from_shop_page');
992 if(!$hide_course_from_shop_page){
993 return;
994 }
995 add_action('woocommerce_product_query', array($this, 'filter_woocommerce_product_query'));
996 add_filter('edd_downloads_query', array($this, 'filter_edd_downloads_query'), 10, 2);
997 add_action('pre_get_posts', array($this, 'filter_archive_meta_query'), 1);
998 }
999
1000 /**
1001 * Tutor product meta query
1002 * @since v.1.4.9
1003 */
1004 public function tutor_product_meta_query(){
1005 $meta_query = array(
1006 'key' => '_tutor_product',
1007 'compare' => 'NOT EXISTS'
1008 );
1009 return $meta_query;
1010 }
1011
1012 /**
1013 * Filter product in woocommerce shop page
1014 * @since v.1.4.9
1015 */
1016 public function filter_woocommerce_product_query($wp_query){
1017 $wp_query->set('meta_query', array($this->tutor_product_meta_query()));
1018 return $wp_query;
1019 }
1020
1021 /**
1022 * Filter product in edd downloads shortcode page
1023 * @since v.1.4.9
1024 */
1025 public function filter_edd_downloads_query($query){
1026 $query['meta_query'][] = $this->tutor_product_meta_query();
1027 return $query;
1028 }
1029
1030 /**
1031 * Filter product in edd downloads archive page
1032 * @since v.1.4.9
1033 */
1034 public function filter_archive_meta_query($wp_query){
1035 if(!is_admin() && $wp_query->is_archive && $wp_query->get('post_type') === 'download'){
1036 $wp_query->set('meta_query', array($this->tutor_product_meta_query()));
1037 }
1038 return $wp_query;
1039 }
1040
1041 /**
1042 * @param $html
1043 * @return string
1044 *
1045 * Removed course price if already enrolled at single course
1046 *
1047 * @since v.1.5.8
1048 */
1049 public function remove_price_if_enrolled($html){
1050 $should_removed = apply_filters('should_remove_price_if_enrolled', true);
1051
1052 if ($should_removed){
1053 $course_id = get_the_ID();
1054 $enrolled = tutils()->is_enrolled($course_id);
1055 if ($enrolled){
1056 $html = '';
1057 }
1058 }
1059 return $html;
1060 }
1061
1062 /**
1063 * @param $html
1064 * @return string
1065 *
1066 * Check if all lessons and quizzes done before mark course complete.
1067 */
1068 function tutor_lms_hide_course_complete_btn($html){
1069
1070 $completion_mode = tutils()->get_option('course_completion_process');
1071 if ($completion_mode !== 'strict'){
1072 return $html;
1073 }
1074
1075 $completed_lesson = tutils()->get_completed_lesson_count_by_course();
1076 $lesson_count = tutils()->get_lesson_count_by_course();
1077
1078 if ($completed_lesson < $lesson_count){
1079 return '<p class="suggestion-before-course-complete">'.__('complete all lessons to mark this course as complete', 'tutor').'</p>';
1080 }
1081
1082 $quizzes = array();
1083
1084 $course_contents = tutils()->get_course_contents_by_id();
1085 if (tutils()->count($course_contents)){
1086 foreach ($course_contents as $content){
1087 if ($content->post_type === 'tutor_quiz'){
1088 $quizzes[] = $content;
1089 }
1090 }
1091 }
1092
1093 $is_pass = true;
1094 $required_quiz_pass = 0;
1095
1096 if (tutils()->count($quizzes)){
1097 foreach ($quizzes as $quiz){
1098
1099 $attempt = tutils()->get_quiz_attempt($quiz->ID);
1100 if ($attempt) {
1101 $passing_grade = tutor_utils()->get_quiz_option($quiz->ID, 'passing_grade', 0);
1102 $earned_percentage = $attempt->earned_marks > 0 ? (number_format(($attempt->earned_marks * 100) / $attempt->total_marks)) : 0;
1103
1104 if ($earned_percentage < $passing_grade) {
1105 $required_quiz_pass++;
1106 $is_pass = false;
1107 }
1108 }else{
1109 $required_quiz_pass++;
1110 $is_pass = false;
1111 }
1112 }
1113 }
1114
1115 if ( ! $is_pass){
1116 return '<p class="suggestion-before-course-complete">'.sprintf(__('You have to pass %s quizzes to complete this course.', 'tutor'), $required_quiz_pass).'</p>';
1117 }
1118
1119 return $html;
1120 }
1121
1122 public function get_generate_greadbook($html){
1123 if ( ! tutils()->is_completed_course()){
1124 return '';
1125 }
1126 return $html;
1127 }
1128
1129 /**
1130 * Add social share content in header
1131 * @since v.1.6.3
1132 */
1133 public function social_share_content(){
1134 global $wp_query, $post;
1135 if ($wp_query->is_single && ! empty($wp_query->query_vars['post_type']) && $wp_query->query_vars['post_type'] === $this->course_post_type) { ?>
1136 <!--Facebook-->
1137 <meta property="og:type" content="website"/>
1138 <meta property="og:image" content="<?php echo get_tutor_course_thumbnail_src(); ?>" />
1139 <meta property="og:description" content="<?php echo esc_html($post->post_content); ?>" />
1140 <!--Twitter-->
1141 <meta name="twitter:image" content="<?php echo get_tutor_course_thumbnail_src(); ?>">
1142 <meta name="twitter:description" content="<?php echo esc_html($post->post_content); ?>">
1143 <!--Google+-->
1144 <meta itemprop="image" content="<?php echo get_tutor_course_thumbnail_src(); ?>">
1145 <meta itemprop="description" content="<?php echo esc_html($post->post_content); ?>"> <?php
1146 }
1147 }
1148
1149 /**
1150 * Get posts by type and parent
1151 * @since v.1.6.6
1152 */
1153 public function tutor_get_post_ids($post_type, $post_parent) {
1154 $args = array(
1155 'fields' => 'ids',
1156 'post_type' => $post_type,
1157 'post_parent' => $post_parent,
1158 'post_status' => 'any',
1159 'posts_per_page' => -1,
1160 );
1161 return get_posts($args);
1162 }
1163
1164 /**
1165 * Delete course data when permanently deleting a course.
1166 * @since v.1.6.6
1167 */
1168 function delete_tutor_course_data( $post_id ) {
1169 $course_post_type = tutor()->course_post_type;
1170 $lesson_post_type = tutor()->lesson_post_type;
1171
1172 if (get_post_type($post_id) == $course_post_type) {
1173 global $wpdb;
1174 $topic_ids = $this->tutor_get_post_ids('topics', $post_id);
1175 if ( !empty($topic_ids) ) {
1176 foreach ($topic_ids as $topic_id) {
1177 $content_post_type = apply_filters('tutor_course_contents_post_types', array($lesson_post_type, 'tutor_quiz'));
1178 $topic_content_ids = $this->tutor_get_post_ids($content_post_type, $topic_id);
1179
1180 foreach ($topic_content_ids as $content_id) {
1181 if( get_post_type($content_id) == 'tutor_quiz') {
1182 $wpdb->delete($wpdb->prefix.'tutor_quiz_attempts', array('quiz_id' => $content_id));
1183 $wpdb->delete($wpdb->prefix.'tutor_quiz_attempt_answers', array('quiz_id' => $content_id));
1184
1185 $questions_ids = $wpdb->get_col($wpdb->prepare("SELECT question_id FROM {$wpdb->prefix}tutor_quiz_questions WHERE quiz_id = %d ", $content_id));
1186 if (is_array($questions_ids) && count($questions_ids)){
1187 $in_question_ids = "'".implode("','", $questions_ids)."'";
1188 $wpdb->query("DELETE FROM {$wpdb->prefix}tutor_quiz_question_answers WHERE belongs_question_id IN({$in_question_ids}) ");
1189 }
1190 $wpdb->delete($wpdb->prefix.'tutor_quiz_questions', array('quiz_id' => $content_id));
1191 }
1192 wp_delete_post($content_id, true);
1193 }
1194 wp_delete_post($topic_id, true);
1195 }
1196 }
1197 $child_post_ids = $this->tutor_get_post_ids(array('tutor_announcements', 'tutor_enrolled'), $post_id);
1198 if ( !empty($child_post_ids) ) {
1199 foreach ($child_post_ids as $child_post_id) {
1200 wp_delete_post($child_post_id, true);
1201 }
1202 }
1203 }
1204 }
1205
1206 /**
1207 * tutor course setting metabox
1208 * @since v.1.7.0
1209 */
1210 function tutor_course_setting_metabox( $post ) {
1211
1212 $disable_qa = $this->additional_meta[0];
1213 $is_public = $this->additional_meta[1];
1214
1215 $disable_qa_checked = get_post_meta($post->ID, $disable_qa, true)=='yes' ? 'checked="checked"' : '';
1216 $is_public_checked = get_post_meta($post->ID, $is_public, true)=='yes' ? 'checked="checked"' : '';
1217
1218 do_action('tutor_before_course_sidebar_settings_metabox', $post);
1219 ?>
1220 <div class="tutor-course-sidebar-settings-item" id="_tutor_is_course_public_meta_checkbox" style="display:none">
1221 <label for="<?php echo $is_public; ?>">
1222 <input id="<?php echo $is_public; ?>" type="checkbox" name="<?php echo $is_public; ?>" value="yes" <?php echo $is_public_checked; ?> />
1223 <?php _e('Make This Course Public', 'tutor'); ?>
1224 <small style="display:block;padding-left:24px">
1225 <?php _e('No enrollment required.', 'tutor'); ?>
1226 </small>
1227 </label>
1228 </div>
1229 <div class="tutor-course-sidebar-settings-item">
1230 <label for="<?php echo $disable_qa; ?>">
1231 <input type="hidden" name="_tutor_course_additional_data_edit" value="true" />
1232 <input id="<?php echo $disable_qa; ?>" type="checkbox" name="<?php echo $disable_qa; ?>" value="yes" <?php echo $disable_qa_checked; ?> />
1233 <?php _e('Disable Q&A', 'tutor'); ?>
1234 </label>
1235 </div>
1236 <?php
1237 do_action('tutor_after_course_sidebar_settings_metabox', $post);
1238 }
1239
1240 function tutor_course_setting_metabox_frontend( $post ){
1241 ?>
1242 <div class="tutor-course-builder-section tutor-course-builder-info">
1243 <div class="tutor-course-builder-section-title">
1244 <h3><i class="tutor-icon-down"></i><span><?php esc_html_e('Tutor Settings', 'tutor'); ?></span></h3>
1245 </div>
1246 <div class="tutor-course-builder-section-content">
1247 <div class="tutor-frontend-builder-item-scope">
1248 <div class="tutor-form-group">
1249 <?php $this->tutor_course_setting_metabox($post); ?>
1250 </div>
1251 </div>
1252 </div>
1253 </div>
1254 <?php
1255 }
1256
1257 /**
1258 * Delete associated enrollment
1259 * @since v.1.8.2
1260 */
1261 public function delete_associated_enrollment($post_id) {
1262 global $wpdb;
1263
1264 $enroll_id = $wpdb->get_var( $wpdb->prepare(
1265 "SELECT
1266 post_id
1267 FROM
1268 {$wpdb->postmeta}
1269 WHERE
1270 meta_key='_tutor_enrolled_by_order_id'
1271 AND meta_value = %d
1272 ",
1273 $post_id
1274 ) );
1275
1276 if(is_numeric($enroll_id) && $enroll_id>0) {
1277
1278 $course_id = get_post_field('post_parent', $enroll_id);
1279 $user_id = get_post_field('post_author', $enroll_id);
1280
1281 tutils()->cancel_course_enrol($course_id, $user_id);
1282 }
1283 }
1284 }