PluginProbe
Tutor LMS – eLearning and online course solution / 3.4.2
Tutor LMS – eLearning and online course solution v3.4.2
4.0.7 4.0.6 4.0.5 4.0.4 4.0.3 4.0.2 4.0.1 4.0.0 3.9.15 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 All 191 releases
← All changes | classes/Instructor.php +9 -505 trunk3.4.2 View file →
@@ -9,17 +9,12 @@
9 9 */
10 10
11 11 namespace TUTOR;
12 12
13 -defined( 'ABSPATH' ) || exit;
13 +if ( ! defined( 'ABSPATH' ) ) {
14 + exit;
15 +}
14 16
15 -use DateInterval;
16 -use DateTime;
17 -use Tutor\GDPR\Controllers\LegalConsent;
18 -use Tutor\Helpers\DateTimeHelper;
19 -use Tutor\Helpers\QueryHelper;
20 -use Tutor\Traits\JsonResponse;
21 -
22 17 /**
23 18 * Instructor class
24 19 *
25 20 * @since 1.0.0
@@ -24,9 +19,8 @@
24 19 *
25 20 * @since 1.0.0
26 21 */
27 22 class Instructor {
28 - use JsonResponse;
29 23
30 24 /**
31 25 * Error message
32 26 *
@@ -73,11 +67,8 @@
73 67 *
74 68 * @since 1.9.2
75 69 */
76 70 add_action( 'wp_loaded', array( $this, 'hide_instructor_notice' ) );
77 -
78 - add_action( 'wp_ajax_tutor_save_instructor_home_sections_order', array( $this, 'ajax_save_home_sections_order' ) );
79 - add_action( 'wp_ajax_tutor_save_instructor_home_sections_visibility', array( $this, 'ajax_save_home_section_visibility' ) );
80 71 }
81 72
82 73 /**
83 74 * Template Redirect Callback
@@ -93,8 +84,9 @@
93 84 }
94 85
95 86 // Checking nonce.
96 87 tutor_utils()->checking_nonce();
88 +
97 89 $required_fields = apply_filters(
98 90 'tutor_instructor_registration_required_fields',
99 91 array(
100 92 'first_name' => __( 'First name field is required', 'tutor' ),
@@ -122,17 +114,11 @@
122 114 $validation_errors[ $required_key ] = $required_value;
123 115 }
124 116 }
125 117
126 - $validate_consent = LegalConsent::validate_consent( LegalConsent::DISPLAY_ON_INS_REG, $_POST );
127 - if ( is_wp_error( $validate_consent ) ) {
128 - $validation_errors[ $validate_consent->get_error_code() ] = $validate_consent->get_error_message();
129 - }
130 -
131 118 if ( ! filter_var( tutor_utils()->input_old( 'email' ), FILTER_VALIDATE_EMAIL ) ) {
132 119 $validation_errors['email'] = __( 'Valid E-Mail is required', 'tutor' );
133 120 }
134 -
135 121 if ( tutor_utils()->input_old( 'password' ) !== tutor_utils()->input_old( 'password_confirmation' ) ) {
136 122 $validation_errors['password_confirmation'] = __( 'Your passwords should match each other. Please recheck.', 'tutor' );
137 123 }
138 124
@@ -157,9 +143,11 @@
157 143 );
158 144
159 145 global $wpdb;
160 146 $wpdb->query( 'START TRANSACTION' );
147 +
161 148 $user_id = wp_insert_user( $userdata );
149 +
162 150 if ( is_wp_error( $user_id ) ) {
163 151 $this->error_msgs = $user_id->get_error_messages();
164 152 add_filter( 'tutor_instructor_register_validation_errors', array( $this, 'tutor_instructor_form_validation_errors' ) );
165 153 return;
@@ -164,11 +152,10 @@
164 152 add_filter( 'tutor_instructor_register_validation_errors', array( $this, 'tutor_instructor_form_validation_errors' ) );
165 153 return;
166 154 }
167 155
168 - $user = get_user_by( 'id', $user_id );
156 + $is_req_email_verification = apply_filters( 'tutor_require_email_verification', false );
169 157
170 - $is_req_email_verification = apply_filters( 'tutor_require_email_verification', false );
171 158 if ( $is_req_email_verification ) {
172 159 do_action( 'tutor_send_verification_mail', get_userdata( $user_id ), 'instructor-registration' );
173 160 $reg_done = apply_filters( 'tutor_registration_done', true );
174 161 if ( ! $reg_done ) {
@@ -182,9 +169,9 @@
182 169 * Tutor Free - regular instructor reg process.
183 170 */
184 171 $this->update_instructor_meta( $user_id );
185 172 $wpdb->query( 'COMMIT' );
186 -
173 + $user = get_user_by( 'id', $user_id );
187 174 if ( $user ) {
188 175 wp_set_current_user( $user_id, $user->user_login );
189 176 wp_set_auth_cookie( $user_id );
190 177 do_action( 'tutor_after_instructor_signup', $user_id );
@@ -190,13 +177,9 @@
190 177 do_action( 'tutor_after_instructor_signup', $user_id );
191 178 }
192 179 }
193 180
194 - if ( $user ) {
195 - do_action( 'tutor_new_instructor_registered', $user_id, $validate_consent );
196 - }
197 -
198 - wp_safe_redirect( tutor_utils()->get_nocache_url( tutor_utils()->input_old( '_wp_http_referer' ) ) );
181 + wp_redirect( tutor_utils()->input_old( '_wp_http_referer' ) );
199 182 die();
200 183 }
201 184
202 185 /**
@@ -231,9 +214,8 @@
231 214 die( esc_html__( 'Already applied for instructor', 'tutor' ) );
232 215 } else {
233 216 update_user_meta( $user_id, '_is_tutor_instructor', tutor_time() );
234 217 update_user_meta( $user_id, '_tutor_instructor_status', apply_filters( 'tutor_initial_instructor_status', 'pending' ) );
235 - update_user_meta( $user_id, User::APPLICATION_SOURCE_META, User::SOURCE_STUDENT_DASHBOARD );
236 218
237 219 do_action( 'tutor_new_instructor_after', $user_id );
238 220 }
239 221 } else {
@@ -350,9 +332,8 @@
350 332 do_action( 'tutor_before_approved_instructor', $instructor_id );
351 333
352 334 update_user_meta( $instructor_id, '_tutor_instructor_status', 'approved' );
353 335 update_user_meta( $instructor_id, '_tutor_instructor_approved', tutor_time() );
354 - update_user_meta( $instructor_id, User::INSTRUCTOR_APPROVAL_NOTICE_META, true );
355 336
356 337 $instructor = new \WP_User( $instructor_id );
357 338 $instructor->add_role( tutor()->instructor_role );
358 339
@@ -396,10 +377,8 @@
396 377 */
397 378 public function hide_instructor_notice() {
398 379 if ( 'hide_instructor_notice' === Input::get( 'tutor_action' ) ) {
399 380 delete_user_meta( get_current_user_id(), 'tutor_instructor_show_rejection_message' );
400 - } elseif ( 'hide_instructor_approval_notice' === Input::get( 'tutor_action' ) ) {
401 - delete_user_meta( get_current_user_id(), User::INSTRUCTOR_APPROVAL_NOTICE_META );
402 381 }
403 382 }
404 383
405 384 /**
@@ -433,483 +412,8 @@
433 412 */
434 413 public function update_instructor_meta( int $user_id ) {
435 414 update_user_meta( $user_id, '_is_tutor_instructor', tutor_time() );
436 415 update_user_meta( $user_id, '_tutor_instructor_status', apply_filters( 'tutor_initial_instructor_status', 'pending' ) );
437 - update_user_meta( $user_id, User::APPLICATION_SOURCE_META, User::SOURCE_INSTRUCTOR_REGISTRATION );
438 416
439 417 do_action( 'tutor_new_instructor_after', $user_id );
440 - }
441 -
442 - /**
443 - * Calculate the previous comparison date range based on a selected date range.
444 - *
445 - * @since 4.0.0
446 - *
447 - * @param string|null $selected_start_date Selected start date (Y-m-d).
448 - * @param string|null $selected_end_date Selected end date (Y-m-d).
449 - *
450 - * @return array {
451 - * @type string $previous_start_date Previous period start date (Y-m-d).
452 - * @type string $previous_end_date Previous period end date (Y-m-d).
453 - * }
454 - */
455 - public static function get_comparison_date_range( $selected_start_date, $selected_end_date ) {
456 -
457 - $format = DateTimeHelper::FORMAT_DATE;
458 -
459 - if ( empty( $selected_start_date ) && empty( $selected_end_date ) ) {
460 -
461 - $now = DateTimeHelper::now();
462 - return array(
463 - 'previous_start_date' => $now->create( 'first day of this month' )->format( $format ),
464 - 'previous_end_date' => $now->create( 'last day of this month' )->format( $format ),
465 - );
466 - }
467 -
468 - $start = new DateTime( $selected_start_date );
469 - $end = new DateTime( $selected_end_date );
470 - $days = $start->diff( $end )->days + 1;
471 -
472 - $previous_start_date = $start->sub( DateInterval::createFromDateString( "$days days" ) )->format( $format );
473 - $previous_end_date = $end->sub( DateInterval::createFromDateString( "$days days" ) )->format( $format );
474 -
475 - return array(
476 - 'previous_start_date' => $previous_start_date,
477 - 'previous_end_date' => $previous_end_date,
478 - );
479 - }
480 -
481 - /**
482 - * Get course completion distribution data for a specific instructor.
483 - *
484 - * @since 4.0.0
485 - *
486 - * @param array $instructor_course_ids Optional list of course IDs.
487 - *
488 - * @return array {
489 - * Enrollment distribution counts.
490 - *
491 - * @type int $enrolled Total number of enrollments.
492 - * @type int $completed Number of fully completed enrollments (100% progress).
493 - * @type int $inprogress Number of enrollments with partial progress (>0 and <100).
494 - * @type int $inactive Number of enrollments with no progress (0%).
495 - * @type int $cancelled Number of cancelled enrollments.
496 - * }
497 - */
498 - public static function get_course_completion_distribution_data_by_instructor( $instructor_course_ids = array() ) {
499 -
500 - global $wpdb;
501 -
502 - $counts = array(
503 - 'enrolled' => 0,
504 - 'completed' => 0,
505 - 'inprogress' => 0,
506 - 'inactive' => 0,
507 - 'cancelled' => 0,
508 - );
509 -
510 - $cancel_statuses = array( 'cancel', 'canceled', 'cancelled' );
511 - $post_statuses = array_merge( $cancel_statuses, array( 'completed' ) );
512 -
513 - if ( empty( $instructor_course_ids ) ) {
514 - return $counts;
515 - }
516 -
517 - $where = array(
518 - 'post_type' => 'tutor_enrolled',
519 - 'post_status' => array( 'IN', $post_statuses ),
520 - 'post_parent' => array( 'IN', $instructor_course_ids ),
521 - );
522 -
523 - $args = array(
524 - 'select' => array( 'id', 'post_status', 'post_author', 'post_parent' ),
525 - 'where' => $where,
526 - );
527 -
528 - $enrollments = QueryHelper::query( $wpdb->posts, $args );
529 -
530 - if ( empty( $enrollments ) ) {
531 - return $counts;
532 - }
533 -
534 - $counts['enrolled'] = count( $enrollments );
535 -
536 - foreach ( $enrollments as $enrollment ) {
537 -
538 - // Cancelled enrollment.
539 - if ( in_array( $enrollment->post_status, $cancel_statuses, true ) ) {
540 - ++$counts['cancelled'];
541 - continue;
542 - }
543 -
544 - // Completed course.
545 - if ( tutor_utils()->is_completed_course( $enrollment->post_parent, $enrollment->post_author ) ) {
546 - ++$counts['completed'];
547 - continue;
548 - }
549 -
550 - $course_progress = (int) tutor_utils()->get_course_completed_percent( $enrollment->post_parent, $enrollment->post_author );
551 -
552 - if ( 100 === $course_progress ) { // If progress is 100% but the `Complete Course` button hasn't been clicked.
553 - ++$counts['completed'];
554 - continue;
555 - }
556 -
557 - if ( $course_progress > 0 ) {
558 - ++$counts['inprogress'];
559 - continue;
560 - }
561 -
562 - ++$counts['inactive'];
563 - }
564 -
565 - return $counts;
566 - }
567 -
568 - /**
569 - * Retrieve the top-performing courses for a given instructor.
570 - *
571 - * @since 4.0.0
572 - *
573 - * @param int $instructor_id Instructor user ID.
574 - * @param array $args {
575 - * Optional query arguments.
576 - *
577 - * @type string $start_date Optional start date (Y-m-d).
578 - * @type string $end_date Optional end date (Y-m-d).
579 - * @type string $order_by Sorting criteria. Accepts 'revenue' or 'student'.
580 - * }
581 -
582 - * @param int $limit Maximum number of courses to return. Default 4.
583 - *
584 - * @return array List of course objects containing:
585 - * - course_id (int)
586 - * - course_title (string)
587 - * - total_revenue (float)
588 - * - total_student (int)
589 - *
590 - * @throws \Exception When a database error occurs.
591 - */
592 - public static function get_top_performing_courses_by_instructor( $instructor_id, $args, $limit = 4 ) {
593 -
594 - global $wpdb;
595 -
596 - $start_date = $args['start_date'] ?? null;
597 - $end_date = $args['end_date'] ?? null;
598 - $order_by = 'revenue' === $args['order_by'] ? 'total_revenue' : 'total_student';
599 -
600 - $complete_status = tutor_utils()->get_earnings_completed_statuses();
601 -
602 - $amount_type = User::is_admin() && is_admin() ? 'earnings.admin_amount' : 'earnings.instructor_amount';
603 - $amount_rate = User::is_admin() && is_admin() ? 'earnings.admin_rate' : 'earnings.instructor_rate';
604 -
605 - $amount_condition = "CASE
606 - WHEN orders.tax_type = 'inclusive' AND earnings.course_price_grand_total > 0
607 - THEN ( earnings.course_price_grand_total - orders.tax_amount ) * ( $amount_rate/100 )
608 - ELSE $amount_type
609 - END";
610 -
611 - $earning_where_clause = array(
612 - 'earnings.user_id' => $instructor_id,
613 - 'earnings.order_status' => array( 'IN', $complete_status ),
614 - );
615 -
616 - $enrollment_where_clause = array(
617 - 'post_type' => 'tutor_enrolled',
618 - 'post_status' => 'completed',
619 - );
620 -
621 - if ( ! empty( $start_date ) && ! empty( $end_date ) ) {
622 - $earning_where_clause['earnings.created_at'] = array( 'BETWEEN', array( $start_date, $end_date ) );
623 - $enrollment_where_clause['post_date'] = array( 'BETWEEN', array( $start_date, $end_date ) );
624 - }
625 -
626 - $earning_where_clause = QueryHelper::prepare_where_clause( $earning_where_clause );
627 - $enrollment_where_clause = QueryHelper::prepare_where_clause( $enrollment_where_clause );
628 -
629 - $earnings_sql = "SELECT
630 - earnings.course_id,
631 - SUM($amount_condition) AS total_revenue
632 - FROM {$wpdb->tutor_earnings} earnings
633 - LEFT JOIN {$wpdb->tutor_orders} orders ON orders.id = earnings.order_id
634 - WHERE {$earning_where_clause}
635 - GROUP BY earnings.course_id";
636 -
637 - $enrollment_sql = QueryHelper::prepare_raw_query(
638 - "SELECT
639 - post_parent AS course_id,
640 - COUNT(DISTINCT post_author) AS total_student
641 - FROM {$wpdb->posts}
642 - WHERE {$enrollment_where_clause}
643 - GROUP BY post_parent",
644 - array()
645 - );
646 -
647 - //phpcs:disabled
648 - $result = $wpdb->get_results(
649 - $wpdb->prepare(
650 - "SELECT
651 - post.ID AS course_id,
652 - post.post_title AS course_title,
653 - COALESCE(earnings.total_revenue, 0) AS total_revenue,
654 - COALESCE(enrollments.total_student, 0) AS total_student
655 - FROM {$wpdb->posts} post
656 - INNER JOIN ({$earnings_sql}) earnings
657 - ON earnings.course_id = post.ID
658 - LEFT JOIN ({$enrollment_sql}) enrollments
659 - ON enrollments.course_id = post.ID
660 - WHERE post.post_type = %s
661 - ORDER BY {$order_by} DESC
662 - LIMIT %d",
663 - tutor()->course_post_type,
664 - $limit
665 - )
666 - );
667 - //phpcs:enable
668 -
669 - // If error occurred then throw new exception.
670 - if ( $wpdb->last_error ) {
671 - throw new \Exception( $wpdb->last_error ); //phpcs:ignore.
672 - }
673 -
674 - return $result;
675 - }
676 -
677 - /**
678 - * Format top performing instructor courses for presentation.
679 - *
680 - * @since 4.0.0
681 - *
682 - * @param array $top_courses List of course objects returned from analytics.
683 - *
684 - * @return array Formatted top performing courses data.
685 - */
686 - public static function format_instructor_top_performing_courses( $top_courses ) {
687 -
688 - if ( empty( $top_courses ) ) {
689 - return array();
690 - }
691 -
692 - return array_map(
693 - function ( $course ) {
694 - return array(
695 - 'name' => $course->course_title,
696 - 'url' => get_permalink( $course->course_id ),
697 - 'revenue' => tutor_utils()->tutor_price( $course->total_revenue ?? 0 ),
698 - 'students' => $course->total_student ?? 0,
699 - );
700 - },
701 - $top_courses
702 - );
703 - }
704 -
705 - /**
706 - * Retrieve upcoming live session tasks (Zoom / Google Meet) for an instructor.
707 - *
708 - * @since 4.0.0
709 - *
710 - * @param int $instructor_id Instructor (author) user ID.
711 - *
712 - * @return array List of upcoming live task posts.
713 - */
714 - public static function get_instructor_upcoming_live_tasks( $instructor_id ) {
715 -
716 - $is_google_meet_enable = tutor_utils()->is_addon_enabled( 'google-meet' );
717 - $is_zoom_enable = tutor_utils()->is_addon_enabled( 'tutor-zoom' );
718 -
719 - $meta_keys = array_filter(
720 - array(
721 - $is_google_meet_enable ? 'tutor-google-meet-start-datetime' : null,
722 - $is_zoom_enable ? '_tutor_zm_start_datetime' : null,
723 - )
724 - );
725 -
726 - $post_types = array_filter(
727 - array(
728 - $is_google_meet_enable ? tutor()->meet_post_type : null,
729 - $is_zoom_enable ? tutor()->zoom_post_type : null,
730 - )
731 - );
732 -
733 - if ( empty( $meta_keys ) ) {
734 - return array();
735 - }
736 -
737 - return get_posts(
738 - array(
739 - 'post_type' => $post_types,
740 - 'post_status' => 'publish',
741 - 'author' => $instructor_id,
742 - 'numberposts' => 5,
743 - 'meta_query' => array(
744 - array(
745 - 'key' => $meta_keys,
746 - 'value' => gmdate( 'Y-m-d H:i:s', strtotime( 'now' ) ),
747 - 'compare' => '>=',
748 - 'type' => 'DATETIME',
749 - ),
750 - ),
751 - )
752 - );
753 - }
754 -
755 - /**
756 - * Format upcoming instructor live tasks for presentation.
757 - *
758 - * @since 4.0.0
759 - *
760 - * @param array $upcoming_live_tasks List of live task post objects.
761 - *
762 - * @return array Formatted upcoming live tasks data.
763 - */
764 - public static function format_instructor_upcoming_live_tasks( $upcoming_live_tasks ) {
765 -
766 - if ( empty( $upcoming_live_tasks ) ) {
767 - return array();
768 - }
769 -
770 - return array_map(
771 - function ( $task ) {
772 -
773 - $is_zoom = tutor()->zoom_post_type === $task->post_type;
774 - $is_meet = tutor()->meet_post_type === $task->post_type;
775 -
776 - $live_meta_key = $is_zoom ? '_tutor_zm_start_datetime'
777 - : ( $is_meet ? 'tutor-google-meet-start-datetime' : '' );
778 -
779 - $url = $is_zoom ? ( json_decode( get_post_meta( $task->ID, '_tutor_zm_data' )[0] )->join_url ?? '' )
780 - : ( $is_meet ? get_post_meta( $task->ID, 'tutor-google-meet-link', true ) : '' );
781 -
782 - $start_date = get_post_meta( $task->ID, $live_meta_key, true );
783 -
784 - return array(
785 - 'name' => $task->post_title,
786 - 'date' => wp_date( 'Y-m-d h:i A', strtotime( $start_date ) ),
787 - 'url' => $url,
788 - 'post_type' => $task->post_type,
789 - );
790 - },
791 - $upcoming_live_tasks
792 - );
793 - }
794 -
795 - /**
796 - * Format recent instructor reviews for display.
797 - *
798 - * @since 4.0.0
799 - *
800 - * @param array $reviews List of review objects.
801 - *
802 - * @return array Formatted recent reviews data.
803 - */
804 - public static function format_instructor_recent_reviews( $reviews ) {
805 -
806 - if ( empty( $reviews ) ) {
807 - return array();
808 - }
809 -
810 - return array_map(
811 - function ( $review ) {
812 - return array(
813 - 'user' => array(
814 - 'id' => $review->user_id,
815 - 'name' => $review->display_name,
816 - 'avatar' => tutor_utils()->get_user_avatar_url( $review->user_id ),
817 - ),
818 - 'course_name' => get_the_title( $review->comment_post_ID ),
819 - 'date' => $review->comment_date,
820 - 'rating' => $review->rating,
821 - 'review_text' => $review->comment_content,
822 - );
823 - },
824 - $reviews
825 - );
826 - }
827 -
828 - /**
829 - *
830 - * Calculates percentage change and UI metadata for a statistics card.
831 - *
832 - * @since 4.0.0
833 - *
834 - * @param float $current_data Current period value.
835 - * @param float $previous_data Previous period value.
836 - *
837 - * @return array{
838 - * percentage: string,
839 - * icon: string,
840 - * class: string,
841 - * icon_class: string
842 - * }
843 - */
844 - public static function get_stat_card_details( float $current_data, float $previous_data ) {
845 -
846 - if ( empty( $previous_data ) && empty( $current_data ) ) {
847 - return array(
848 - 'percentage' => '',
849 - 'icon' => Icon::MINUS,
850 - 'class' => 'tutor-text-primary',
851 - );
852 - }
853 -
854 - if ( empty( $previous_data ) ) {
855 - $percentage = 100;
856 - } else {
857 - $percentage = ( ( $current_data - $previous_data ) / $previous_data ) * 100;
858 - }
859 -
860 - $is_negative = $percentage < 0;
861 - $icon = $is_negative ? Icon::ARROW_DOWN : Icon::ARROW_UP;
862 - $class = $is_negative ? 'tutor-p2 tutor-actions-critical-primary' : 'tutor-p2 tutor-actions-success-primary';
863 -
864 - return array(
865 - 'percentage' => number_format( abs( $percentage ), 2 ) . '%',
866 - 'icon' => $icon,
867 - 'class' => $class,
868 - 'icon_class' => '-tutor-mb-1',
869 - );
870 - }
871 -
872 - /**
873 - * Save the instructor home sections order for the current user.
874 - *
875 - * @since 4.0.0
876 - *
877 - * @return void Sends JSON success or error response and exits.
878 - */
879 - public function ajax_save_home_sections_order() {
880 - tutor_utils()->check_nonce();
881 -
882 - if ( ! User::is_instructor() ) {
883 - $this->response_bad_request( tutor_utils()->error_message() );
884 - }
885 -
886 - $order = Input::post( 'order', array(), Input::TYPE_ARRAY );
887 - $order = array_values( array_map( 'sanitize_key', $order ) );
888 -
889 - update_user_meta( get_current_user_id(), '_tutor_instructor_home_sections_order', array_flip( $order ) );
890 -
891 - $this->response_success( __( 'Settings saved successfully', 'tutor' ) );
892 - }
893 -
894 - /**
895 - * Save the visibility state of instructor home sections.
896 - *
897 - * @since 4.0.0
898 - *
899 - * @return void Sends JSON success or error response and exits.
900 - */
901 - public function ajax_save_home_section_visibility() {
902 - tutor_utils()->check_nonce();
903 -
904 - if ( ! User::is_instructor() ) {
905 - $this->response_bad_request( tutor_utils()->error_message() );
906 - }
907 -
908 - $items = Input::post( 'items', '', Input::TYPE_STRING );
909 - $items = array_map( 'rest_sanitize_boolean', (array) json_decode( $items ) );
910 -
911 - update_user_meta( get_current_user_id(), '_tutor_instructor_home_sections_visibility', $items );
912 -
913 - $this->response_success( __( 'Settings saved successfully', 'tutor' ) );
914 418 }
915 419 }