文件操作 - Instructor.php
返回文件管理
返回主菜单
删除本文件
文件: /home/www/public/wp-content/plugins/tutor/classes/Instructor.php
编辑文件内容
<?php /** * Manage Instructor * * @package Tutor * @author Themeum <support@themeum.com> * @link https://themeum.com * @since 1.0.0 */ namespace TUTOR; defined( 'ABSPATH' ) || exit; use DateInterval; use DateTime; use Tutor\GDPR\Controllers\LegalConsent; use Tutor\Helpers\DateTimeHelper; use Tutor\Helpers\QueryHelper; use Tutor\Traits\JsonResponse; /** * Instructor class * * @since 1.0.0 */ class Instructor { use JsonResponse; /** * Error message * * @var string */ protected $error_msgs = ''; /** * Constructor * * @since 1.0.0 * * @param bool $register_hook register hook or not. * * @return void */ public function __construct( $register_hook = true ) { if ( ! $register_hook ) { return; } add_action( 'template_redirect', array( $this, 'register_instructor' ) ); add_action( 'template_redirect', array( $this, 'apply_instructor' ) ); // Add instructor from admin panel. add_action( 'wp_ajax_tutor_add_instructor', array( $this, 'add_new_instructor' ) ); /** * Instructor Approval * Block Unblock * * @since 1.5.3 */ add_action( 'wp_ajax_instructor_approval_action', array( $this, 'instructor_approval_action' ) ); /** * Check if instructor can publish courses * * @since 1.5.9 */ add_action( 'tutor_option_save_after', array( $this, 'can_publish_tutor_courses' ) ); /** * Hide instructor rejection message * * @since 1.9.2 */ add_action( 'wp_loaded', array( $this, 'hide_instructor_notice' ) ); add_action( 'wp_ajax_tutor_save_instructor_home_sections_order', array( $this, 'ajax_save_home_sections_order' ) ); add_action( 'wp_ajax_tutor_save_instructor_home_sections_visibility', array( $this, 'ajax_save_home_section_visibility' ) ); } /** * Template Redirect Callback * For Register new user and mark him as instructor * * @since 1.0.0 * @return void|null */ public function register_instructor() { // Here tutor_action checking required before nonce checking. if ( 'tutor_register_instructor' !== Input::post( 'tutor_action' ) || ! get_option( 'users_can_register', false ) ) { return; } // Checking nonce. tutor_utils()->checking_nonce(); $required_fields = apply_filters( 'tutor_instructor_registration_required_fields', array( 'first_name' => __( 'First name field is required', 'tutor' ), 'last_name' => __( 'Last name field is required', 'tutor' ), 'email' => __( 'E-Mail field is required', 'tutor' ), 'user_login' => __( 'User Name field is required', 'tutor' ), 'password' => __( 'Password field is required', 'tutor' ), 'password_confirmation' => __( 'Password Confirmation field is required', 'tutor' ), ) ); $validation_errors = array(); /* * Push into validation_errors * Error registration_errors */ $errors = apply_filters( 'registration_errors', new \WP_Error(), '', '' ); foreach ( $errors->errors as $key => $value ) { $validation_errors[ $key ] = $value[0]; } foreach ( $required_fields as $required_key => $required_value ) { if ( empty( $_POST[ $required_key ] ) ) { //phpcs:ignore WordPress.Security.NonceVerification.Missing $validation_errors[ $required_key ] = $required_value; } } $validate_consent = LegalConsent::validate_consent( LegalConsent::DISPLAY_ON_INS_REG, $_POST ); if ( is_wp_error( $validate_consent ) ) { $validation_errors[ $validate_consent->get_error_code() ] = $validate_consent->get_error_message(); } if ( ! filter_var( tutor_utils()->input_old( 'email' ), FILTER_VALIDATE_EMAIL ) ) { $validation_errors['email'] = __( 'Valid E-Mail is required', 'tutor' ); } if ( tutor_utils()->input_old( 'password' ) !== tutor_utils()->input_old( 'password_confirmation' ) ) { $validation_errors['password_confirmation'] = __( 'Your passwords should match each other. Please recheck.', 'tutor' ); } if ( count( $validation_errors ) ) { $this->error_msgs = $validation_errors; add_filter( 'tutor_instructor_register_validation_errors', array( $this, 'tutor_instructor_form_validation_errors' ) ); return; } $first_name = sanitize_text_field( tutor_utils()->input_old( 'first_name' ) ); $last_name = sanitize_text_field( tutor_utils()->input_old( 'last_name' ) ); $email = sanitize_text_field( tutor_utils()->input_old( 'email' ) ); $user_login = sanitize_text_field( tutor_utils()->input_old( 'user_login' ) ); $password = sanitize_text_field( tutor_utils()->input_old( 'password' ) ); $userdata = array( 'user_login' => $user_login, 'user_email' => $email, 'first_name' => $first_name, 'last_name' => $last_name, 'user_pass' => $password, ); global $wpdb; $wpdb->query( 'START TRANSACTION' ); $user_id = wp_insert_user( $userdata ); if ( is_wp_error( $user_id ) ) { $this->error_msgs = $user_id->get_error_messages(); add_filter( 'tutor_instructor_register_validation_errors', array( $this, 'tutor_instructor_form_validation_errors' ) ); return; } $user = get_user_by( 'id', $user_id ); $is_req_email_verification = apply_filters( 'tutor_require_email_verification', false ); if ( $is_req_email_verification ) { do_action( 'tutor_send_verification_mail', get_userdata( $user_id ), 'instructor-registration' ); $reg_done = apply_filters( 'tutor_registration_done', true ); if ( ! $reg_done ) { $wpdb->query( 'ROLLBACK' ); return; } else { $wpdb->query( 'COMMIT' ); } } else { /** * Tutor Free - regular instructor reg process. */ $this->update_instructor_meta( $user_id ); $wpdb->query( 'COMMIT' ); if ( $user ) { wp_set_current_user( $user_id, $user->user_login ); wp_set_auth_cookie( $user_id ); do_action( 'tutor_after_instructor_signup', $user_id ); } } if ( $user ) { do_action( 'tutor_new_instructor_registered', $user_id, $validate_consent ); } wp_safe_redirect( tutor_utils()->get_nocache_url( tutor_utils()->input_old( '_wp_http_referer' ) ) ); die(); } /** * Get instructor reg validation errors. * * @since 1.0.0 * @return string */ public function tutor_instructor_form_validation_errors() { return $this->error_msgs; } /** * Template Redirect Callback * for instructor applying when a user already logged in * * @since 1.0.0 * @return void|null */ public function apply_instructor() { // Here tutor_action checking required before nonce checking. if ( 'tutor_apply_instructor' !== Input::post( 'tutor_action' ) ) { return; } // Checking nonce. tutor_utils()->checking_nonce(); $user_id = get_current_user_id(); if ( $user_id ) { if ( tutor_utils()->is_instructor() ) { die( esc_html__( 'Already applied for instructor', 'tutor' ) ); } else { update_user_meta( $user_id, '_is_tutor_instructor', tutor_time() ); update_user_meta( $user_id, '_tutor_instructor_status', apply_filters( 'tutor_initial_instructor_status', 'pending' ) ); update_user_meta( $user_id, User::APPLICATION_SOURCE_META, User::SOURCE_STUDENT_DASHBOARD ); do_action( 'tutor_new_instructor_after', $user_id ); } } else { die( esc_html__( 'Permission denied', 'tutor' ) ); } wp_redirect( tutor_utils()->input_old( '_wp_http_referer' ) ); die(); } /** * Add new instructor * * @since 1.0.0 * @return void */ public function add_new_instructor() { tutor_utils()->checking_nonce(); // Only admin should be able to add instructor. if ( ! current_user_can( 'manage_options' ) || ! get_option( 'users_can_register', false ) ) { wp_send_json_error(); } $required_fields = apply_filters( 'tutor_instructor_registration_required_fields', array( 'first_name' => __( 'First name field is required', 'tutor' ), 'last_name' => __( 'Last name field is required', 'tutor' ), 'email' => __( 'E-Mail field is required', 'tutor' ), 'user_login' => __( 'User Name field is required', 'tutor' ), 'password' => __( 'Password field is required', 'tutor' ), 'password_confirmation' => __( 'Your passwords should match each other. Please recheck.', 'tutor' ), ) ); $validation_errors = array(); foreach ( $required_fields as $required_key => $required_value ) { if ( empty( $_POST[ $required_key ] ) ) { //phpcs:ignore WordPress.Security.NonceVerification.Missing $validation_errors[ $required_key ] = $required_value; } } if ( ! filter_var( tutor_utils()->input_old( 'email' ), FILTER_VALIDATE_EMAIL ) ) { $validation_errors['email'] = __( 'Valid E-Mail is required', 'tutor' ); } if ( tutor_utils()->input_old( 'password' ) !== tutor_utils()->input_old( 'password_confirmation' ) ) { $validation_errors['password_confirmation'] = __( 'Your passwords should match each other. Please recheck.', 'tutor' ); } if ( count( $validation_errors ) ) { wp_send_json_error( array( 'errors' => $validation_errors ) ); } $first_name = sanitize_text_field( tutor_utils()->input_old( 'first_name' ) ); $last_name = sanitize_text_field( tutor_utils()->input_old( 'last_name' ) ); $email = sanitize_text_field( tutor_utils()->input_old( 'email' ) ); $user_login = sanitize_text_field( tutor_utils()->input_old( 'user_login' ) ); $phone_number = sanitize_text_field( tutor_utils()->input_old( 'phone_number' ) ); $password = sanitize_text_field( tutor_utils()->input_old( 'password' ) ); $tutor_profile_bio = Input::post( 'tutor_profile_bio', '', Input::TYPE_KSES_POST ); $tutor_profile_job_title = sanitize_text_field( tutor_utils()->input_old( 'tutor_profile_job_title' ) ); $userdata = apply_filters( 'add_new_instructor_data', array( 'user_login' => $user_login, 'user_email' => $email, 'first_name' => $first_name, 'last_name' => $last_name, 'role' => tutor()->instructor_role, 'user_pass' => $password, ) ); do_action( 'tutor_add_new_instructor_before' ); $user_id = wp_insert_user( $userdata ); if ( ! is_wp_error( $user_id ) ) { update_user_meta( $user_id, 'phone_number', $phone_number ); update_user_meta( $user_id, 'description', $tutor_profile_bio ); update_user_meta( $user_id, '_tutor_profile_bio', $tutor_profile_bio ); update_user_meta( $user_id, '_tutor_profile_job_title', $tutor_profile_job_title ); update_user_meta( $user_id, '_is_tutor_instructor', tutor_time() ); update_user_meta( $user_id, '_tutor_instructor_status', apply_filters( 'tutor_initial_instructor_status', 'approved' ) ); do_action( 'tutor_add_new_instructor_after', $user_id ); wp_send_json_success( array( 'msg' => __( 'Instructor has been added successfully', 'tutor' ) ) ); } wp_send_json_error( array( 'errors' => $user_id ) ); } /** * Handle instructor approval action * This function not used maybe, will be removed * * @since 1.0.0 * @return void */ public function instructor_approval_action() { tutor_utils()->checking_nonce(); if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( array( 'message' => __( 'Access Denied', 'tutor' ) ) ); } $instructor_id = Input::post( 'instructor_id', 0, Input::TYPE_INT ); $action = Input::post( 'action_name' ); if ( 'approve' === $action ) { do_action( 'tutor_before_approved_instructor', $instructor_id ); update_user_meta( $instructor_id, '_tutor_instructor_status', 'approved' ); update_user_meta( $instructor_id, '_tutor_instructor_approved', tutor_time() ); update_user_meta( $instructor_id, User::INSTRUCTOR_APPROVAL_NOTICE_META, true ); $instructor = new \WP_User( $instructor_id ); $instructor->add_role( tutor()->instructor_role ); // Send E-Mail to this user about instructor approval via hook. do_action( 'tutor_after_approved_instructor', $instructor_id ); } if ( 'blocked' === $action ) { do_action( 'tutor_before_blocked_instructor', $instructor_id ); update_user_meta( $instructor_id, '_tutor_instructor_status', 'blocked' ); $instructor = new \WP_User( $instructor_id ); $instructor->remove_role( tutor()->instructor_role ); do_action( 'tutor_after_blocked_instructor', $instructor_id ); // TODO: send E-Mail to this user about instructor blocked, should via hook. } if ( 'remove-instructor' === $action ) { do_action( 'tutor_before_rejected_instructor', $instructor_id ); $user = new \WP_User( $instructor_id ); $user->remove_role( tutor()->instructor_role ); tutor_utils()->remove_instructor_role( $instructor_id ); update_user_meta( $instructor_id, '_is_tutor_instructor_rejected', tutor_time() ); update_user_meta( $instructor_id, 'tutor_instructor_show_rejection_message', true ); // Send E-Mail to this user about instructor rejection via hook. do_action( 'tutor_after_rejected_instructor', $instructor_id ); } wp_send_json_success(); } /** * Hide instructor notice * * @since 1.0.0 * @return void */ public function hide_instructor_notice() { if ( 'hide_instructor_notice' === Input::get( 'tutor_action' ) ) { delete_user_meta( get_current_user_id(), 'tutor_instructor_show_rejection_message' ); } elseif ( 'hide_instructor_approval_notice' === Input::get( 'tutor_action' ) ) { delete_user_meta( get_current_user_id(), User::INSTRUCTOR_APPROVAL_NOTICE_META ); } } /** * Can instructor publish courses directly * Fixed in Gutenberg * * @since 1.5.9 * @return void */ public function can_publish_tutor_courses() { $can_publish_course = (bool) tutor_utils()->get_option( 'instructor_can_publish_course' ); $instructor_role = tutor()->instructor_role; $instructor = get_role( $instructor_role ); if ( $can_publish_course ) { $instructor->add_cap( 'publish_tutor_courses' ); } else { $instructor->remove_cap( 'publish_tutor_courses' ); } } /** * Update instructor meta just after register * * @since 2.1.9 * * @param integer $user_id user id. * * @return void */ public function update_instructor_meta( int $user_id ) { update_user_meta( $user_id, '_is_tutor_instructor', tutor_time() ); update_user_meta( $user_id, '_tutor_instructor_status', apply_filters( 'tutor_initial_instructor_status', 'pending' ) ); update_user_meta( $user_id, User::APPLICATION_SOURCE_META, User::SOURCE_INSTRUCTOR_REGISTRATION ); do_action( 'tutor_new_instructor_after', $user_id ); } /** * Calculate the previous comparison date range based on a selected date range. * * @since 4.0.0 * * @param string|null $selected_start_date Selected start date (Y-m-d). * @param string|null $selected_end_date Selected end date (Y-m-d). * * @return array { * @type string $previous_start_date Previous period start date (Y-m-d). * @type string $previous_end_date Previous period end date (Y-m-d). * } */ public static function get_comparison_date_range( $selected_start_date, $selected_end_date ) { $format = DateTimeHelper::FORMAT_DATE; if ( empty( $selected_start_date ) && empty( $selected_end_date ) ) { $now = DateTimeHelper::now(); return array( 'previous_start_date' => $now->create( 'first day of this month' )->format( $format ), 'previous_end_date' => $now->create( 'last day of this month' )->format( $format ), ); } $start = new DateTime( $selected_start_date ); $end = new DateTime( $selected_end_date ); $days = $start->diff( $end )->days + 1; $previous_start_date = $start->sub( DateInterval::createFromDateString( "$days days" ) )->format( $format ); $previous_end_date = $end->sub( DateInterval::createFromDateString( "$days days" ) )->format( $format ); return array( 'previous_start_date' => $previous_start_date, 'previous_end_date' => $previous_end_date, ); } /** * Get course completion distribution data for a specific instructor. * * @since 4.0.0 * * @param array $instructor_course_ids Optional list of course IDs. * * @return array { * Enrollment distribution counts. * * @type int $enrolled Total number of enrollments. * @type int $completed Number of fully completed enrollments (100% progress). * @type int $inprogress Number of enrollments with partial progress (>0 and <100). * @type int $inactive Number of enrollments with no progress (0%). * @type int $cancelled Number of cancelled enrollments. * } */ public static function get_course_completion_distribution_data_by_instructor( $instructor_course_ids = array() ) { global $wpdb; $counts = array( 'enrolled' => 0, 'completed' => 0, 'inprogress' => 0, 'inactive' => 0, 'cancelled' => 0, ); $cancel_statuses = array( 'cancel', 'canceled', 'cancelled' ); $post_statuses = array_merge( $cancel_statuses, array( 'completed' ) ); if ( empty( $instructor_course_ids ) ) { return $counts; } $where = array( 'post_type' => 'tutor_enrolled', 'post_status' => array( 'IN', $post_statuses ), 'post_parent' => array( 'IN', $instructor_course_ids ), ); $args = array( 'select' => array( 'id', 'post_status', 'post_author', 'post_parent' ), 'where' => $where, ); $enrollments = QueryHelper::query( $wpdb->posts, $args ); if ( empty( $enrollments ) ) { return $counts; } $counts['enrolled'] = count( $enrollments ); foreach ( $enrollments as $enrollment ) { // Cancelled enrollment. if ( in_array( $enrollment->post_status, $cancel_statuses, true ) ) { ++$counts['cancelled']; continue; } // Completed course. if ( tutor_utils()->is_completed_course( $enrollment->post_parent, $enrollment->post_author ) ) { ++$counts['completed']; continue; } $course_progress = (int) tutor_utils()->get_course_completed_percent( $enrollment->post_parent, $enrollment->post_author ); if ( 100 === $course_progress ) { // If progress is 100% but the `Complete Course` button hasn't been clicked. ++$counts['completed']; continue; } if ( $course_progress > 0 ) { ++$counts['inprogress']; continue; } ++$counts['inactive']; } return $counts; } /** * Retrieve the top-performing courses for a given instructor. * * @since 4.0.0 * * @param int $instructor_id Instructor user ID. * @param array $args { * Optional query arguments. * * @type string $start_date Optional start date (Y-m-d). * @type string $end_date Optional end date (Y-m-d). * @type string $order_by Sorting criteria. Accepts 'revenue' or 'student'. * } * @param int $limit Maximum number of courses to return. Default 4. * * @return array List of course objects containing: * - course_id (int) * - course_title (string) * - total_revenue (float) * - total_student (int) * * @throws \Exception When a database error occurs. */ public static function get_top_performing_courses_by_instructor( $instructor_id, $args, $limit = 4 ) { global $wpdb; $start_date = $args['start_date'] ?? null; $end_date = $args['end_date'] ?? null; $order_by = 'revenue' === $args['order_by'] ? 'total_revenue' : 'total_student'; $complete_status = tutor_utils()->get_earnings_completed_statuses(); $amount_type = User::is_admin() && is_admin() ? 'earnings.admin_amount' : 'earnings.instructor_amount'; $amount_rate = User::is_admin() && is_admin() ? 'earnings.admin_rate' : 'earnings.instructor_rate'; $amount_condition = "CASE WHEN orders.tax_type = 'inclusive' AND earnings.course_price_grand_total > 0 THEN ( earnings.course_price_grand_total - orders.tax_amount ) * ( $amount_rate/100 ) ELSE $amount_type END"; $earning_where_clause = array( 'earnings.user_id' => $instructor_id, 'earnings.order_status' => array( 'IN', $complete_status ), ); $enrollment_where_clause = array( 'post_type' => 'tutor_enrolled', 'post_status' => 'completed', ); if ( ! empty( $start_date ) && ! empty( $end_date ) ) { $earning_where_clause['earnings.created_at'] = array( 'BETWEEN', array( $start_date, $end_date ) ); $enrollment_where_clause['post_date'] = array( 'BETWEEN', array( $start_date, $end_date ) ); } $earning_where_clause = QueryHelper::prepare_where_clause( $earning_where_clause ); $enrollment_where_clause = QueryHelper::prepare_where_clause( $enrollment_where_clause ); $earnings_sql = "SELECT earnings.course_id, SUM($amount_condition) AS total_revenue FROM {$wpdb->tutor_earnings} earnings LEFT JOIN {$wpdb->tutor_orders} orders ON orders.id = earnings.order_id WHERE {$earning_where_clause} GROUP BY earnings.course_id"; $enrollment_sql = QueryHelper::prepare_raw_query( "SELECT post_parent AS course_id, COUNT(DISTINCT post_author) AS total_student FROM {$wpdb->posts} WHERE {$enrollment_where_clause} GROUP BY post_parent", array() ); //phpcs:disabled $result = $wpdb->get_results( $wpdb->prepare( "SELECT post.ID AS course_id, post.post_title AS course_title, COALESCE(earnings.total_revenue, 0) AS total_revenue, COALESCE(enrollments.total_student, 0) AS total_student FROM {$wpdb->posts} post INNER JOIN ({$earnings_sql}) earnings ON earnings.course_id = post.ID LEFT JOIN ({$enrollment_sql}) enrollments ON enrollments.course_id = post.ID WHERE post.post_type = %s ORDER BY {$order_by} DESC LIMIT %d", tutor()->course_post_type, $limit ) ); //phpcs:enable // If error occurred then throw new exception. if ( $wpdb->last_error ) { throw new \Exception( $wpdb->last_error ); //phpcs:ignore. } return $result; } /** * Format top performing instructor courses for presentation. * * @since 4.0.0 * * @param array $top_courses List of course objects returned from analytics. * * @return array Formatted top performing courses data. */ public static function format_instructor_top_performing_courses( $top_courses ) { if ( empty( $top_courses ) ) { return array(); } return array_map( function ( $course ) { return array( 'name' => $course->course_title, 'url' => get_permalink( $course->course_id ), 'revenue' => tutor_utils()->tutor_price( $course->total_revenue ?? 0 ), 'students' => $course->total_student ?? 0, ); }, $top_courses ); } /** * Retrieve upcoming live session tasks (Zoom / Google Meet) for an instructor. * * @since 4.0.0 * * @param int $instructor_id Instructor (author) user ID. * * @return array List of upcoming live task posts. */ public static function get_instructor_upcoming_live_tasks( $instructor_id ) { $is_google_meet_enable = tutor_utils()->is_addon_enabled( 'google-meet' ); $is_zoom_enable = tutor_utils()->is_addon_enabled( 'tutor-zoom' ); $meta_keys = array_filter( array( $is_google_meet_enable ? 'tutor-google-meet-start-datetime' : null, $is_zoom_enable ? '_tutor_zm_start_datetime' : null, ) ); $post_types = array_filter( array( $is_google_meet_enable ? tutor()->meet_post_type : null, $is_zoom_enable ? tutor()->zoom_post_type : null, ) ); if ( empty( $meta_keys ) ) { return array(); } return get_posts( array( 'post_type' => $post_types, 'post_status' => 'publish', 'author' => $instructor_id, 'numberposts' => 5, 'meta_query' => array( array( 'key' => $meta_keys, 'value' => gmdate( 'Y-m-d H:i:s', strtotime( 'now' ) ), 'compare' => '>=', 'type' => 'DATETIME', ), ), ) ); } /** * Format upcoming instructor live tasks for presentation. * * @since 4.0.0 * * @param array $upcoming_live_tasks List of live task post objects. * * @return array Formatted upcoming live tasks data. */ public static function format_instructor_upcoming_live_tasks( $upcoming_live_tasks ) { if ( empty( $upcoming_live_tasks ) ) { return array(); } return array_map( function ( $task ) { $is_zoom = tutor()->zoom_post_type === $task->post_type; $is_meet = tutor()->meet_post_type === $task->post_type; $live_meta_key = $is_zoom ? '_tutor_zm_start_datetime' : ( $is_meet ? 'tutor-google-meet-start-datetime' : '' ); $url = $is_zoom ? ( json_decode( get_post_meta( $task->ID, '_tutor_zm_data' )[0] )->join_url ?? '' ) : ( $is_meet ? get_post_meta( $task->ID, 'tutor-google-meet-link', true ) : '' ); $start_date = get_post_meta( $task->ID, $live_meta_key, true ); return array( 'name' => $task->post_title, 'date' => wp_date( 'Y-m-d h:i A', strtotime( $start_date ) ), 'url' => $url, 'post_type' => $task->post_type, ); }, $upcoming_live_tasks ); } /** * Format recent instructor reviews for display. * * @since 4.0.0 * * @param array $reviews List of review objects. * * @return array Formatted recent reviews data. */ public static function format_instructor_recent_reviews( $reviews ) { if ( empty( $reviews ) ) { return array(); } return array_map( function ( $review ) { return array( 'user' => array( 'id' => $review->user_id, 'name' => $review->display_name, 'avatar' => tutor_utils()->get_user_avatar_url( $review->user_id ), ), 'course_name' => get_the_title( $review->comment_post_ID ), 'date' => $review->comment_date, 'rating' => $review->rating, 'review_text' => $review->comment_content, ); }, $reviews ); } /** * * Calculates percentage change and UI metadata for a statistics card. * * @since 4.0.0 * * @param float $current_data Current period value. * @param float $previous_data Previous period value. * * @return array{ * percentage: string, * icon: string, * class: string, * icon_class: string * } */ public static function get_stat_card_details( float $current_data, float $previous_data ) { if ( empty( $previous_data ) && empty( $current_data ) ) { return array( 'percentage' => '', 'icon' => Icon::MINUS, 'class' => 'tutor-text-primary', ); } if ( empty( $previous_data ) ) { $percentage = 100; } else { $percentage = ( ( $current_data - $previous_data ) / $previous_data ) * 100; } $is_negative = $percentage < 0; $icon = $is_negative ? Icon::ARROW_DOWN : Icon::ARROW_UP; $class = $is_negative ? 'tutor-p2 tutor-actions-critical-primary' : 'tutor-p2 tutor-actions-success-primary'; return array( 'percentage' => number_format( abs( $percentage ), 2 ) . '%', 'icon' => $icon, 'class' => $class, 'icon_class' => '-tutor-mb-1', ); } /** * Save the instructor home sections order for the current user. * * @since 4.0.0 * * @return void Sends JSON success or error response and exits. */ public function ajax_save_home_sections_order() { tutor_utils()->check_nonce(); if ( ! User::is_instructor() ) { $this->response_bad_request( tutor_utils()->error_message() ); } $order = Input::post( 'order', array(), Input::TYPE_ARRAY ); $order = array_values( array_map( 'sanitize_key', $order ) ); update_user_meta( get_current_user_id(), '_tutor_instructor_home_sections_order', array_flip( $order ) ); $this->response_success( __( 'Settings saved successfully', 'tutor' ) ); } /** * Save the visibility state of instructor home sections. * * @since 4.0.0 * * @return void Sends JSON success or error response and exits. */ public function ajax_save_home_section_visibility() { tutor_utils()->check_nonce(); if ( ! User::is_instructor() ) { $this->response_bad_request( tutor_utils()->error_message() ); } $items = Input::post( 'items', '', Input::TYPE_STRING ); $items = array_map( 'rest_sanitize_boolean', (array) json_decode( $items ) ); update_user_meta( get_current_user_id(), '_tutor_instructor_home_sections_visibility', $items ); $this->response_success( __( 'Settings saved successfully', 'tutor' ) ); } }
修改文件时间
将文件时间修改为当前时间的前一年
删除文件