| Server IP : 158.247.231.215 / Your IP : 216.73.217.2 Web Server : Apache/2.4.41 (Ubuntu) System : Linux CTMS 5.4.0-216-generic #236-Ubuntu SMP Fri Apr 11 19:53:21 UTC 2025 x86_64 User : www-data ( 33) PHP Version : 8.0.30 Disable Function : pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,pcntl_unshare, MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : ON | Pkexec : ON Directory : /mnt/blockstorage/ctms/wp-content/mu-plugins/ |
Upload File : |
<?php
/**
* Plugin Name: Custom Course Statistics API
* Description: Adds REST API endpoint for user course statistics
* Version: 1.0.0
* Author: Custom Development
*/
if (!defined('ABSPATH')) {
exit;
}
/**
* Register REST API routes
*/
add_action('rest_api_init', function() {
register_rest_route('custom/v1', '/user/(?P<user_id>\d+)/course-stats', array(
'methods' => 'GET',
'callback' => 'custom_get_user_course_stats',
'permission_callback' => '__return_true',
'args' => array(
'user_id' => array(
'required' => true,
'validate_callback' => function($param, $request, $key) {
return is_numeric($param);
}
),
),
));
register_rest_route('custom/v1', '/register', array(
'methods' => 'POST',
'callback' => 'custom_register_user',
'permission_callback' => '__return_true',
));
register_rest_route('custom/v1', '/me', array(
'methods' => 'GET',
'callback' => 'custom_get_current_user',
'permission_callback' => 'custom_verify_jwt_token',
));
register_rest_route('custom/v1', '/password/change', array(
'methods' => 'POST',
'callback' => 'custom_change_password',
'permission_callback' => 'custom_verify_jwt_token',
));
register_rest_route('custom/v1', '/me/membership', array(
'methods' => 'GET',
'callback' => 'custom_get_user_membership',
'permission_callback' => 'custom_verify_jwt_token',
));
register_rest_route('custom/v1', '/me/invoices', array(
'methods' => 'GET',
'callback' => 'custom_get_user_invoices',
'permission_callback' => 'custom_verify_jwt_token',
));
register_rest_route('custom/v1', '/me/social-profiles', array(
'methods' => 'GET',
'callback' => 'custom_get_social_profiles',
'permission_callback' => 'custom_verify_jwt_token',
));
register_rest_route('custom/v1', '/me/social-profiles', array(
'methods' => 'POST',
'callback' => 'custom_update_social_profiles',
'permission_callback' => 'custom_verify_jwt_token',
));
register_rest_route('custom/v1', '/me/quiz-attempts', array(
'methods' => 'GET',
'callback' => 'custom_get_user_quiz_attempts',
'permission_callback' => 'custom_verify_jwt_token',
));
});
/**
* Get user course statistics
*/
function custom_get_user_course_stats($request) {
$user_id = $request->get_param('user_id');
$user = get_user_by('ID', $user_id);
if (!$user) {
return new WP_REST_Response(array(
'success' => false,
'message' => 'User not found',
), 404);
}
$enrolled_count = custom_get_enrolled_courses_count($user_id);
$active_count = custom_get_active_courses_count($user_id);
$completed_count = custom_get_completed_courses_count($user_id);
return new WP_REST_Response(array(
'success' => true,
'user_id' => $user_id,
'user_name' => $user->display_name,
'statistics' => array(
'enrolled_courses' => $enrolled_count,
'active_courses' => $active_count,
'completed_courses' => $completed_count,
),
), 200);
}
/**
* Get enrolled courses count
*/
function custom_get_enrolled_courses_count($user_id) {
global $wpdb;
$count = $wpdb->get_var($wpdb->prepare(
"SELECT COUNT(DISTINCT class_id) FROM {$wpdb->prefix}class_enrollment WHERE student_id = %d",
$user_id
));
return (int) $count;
}
/**
* Get active courses count
*/
function custom_get_active_courses_count($user_id) {
if (function_exists('tutor_utils')) {
$active_courses = tutor_utils()->get_active_courses_by_user($user_id);
if (is_object($active_courses) && $active_courses->have_posts()) {
return $active_courses->post_count;
}
}
global $wpdb;
$enrolled_ids = $wpdb->get_col($wpdb->prepare(
"SELECT DISTINCT class_id FROM {$wpdb->prefix}class_enrollment WHERE student_id = %d",
$user_id
));
if (empty($enrolled_ids)) {
return 0;
}
$completed_ids = custom_get_completed_course_ids($user_id);
$active_ids = array_diff($enrolled_ids, $completed_ids);
return count($active_ids);
}
/**
* Get completed courses count
*/
function custom_get_completed_courses_count($user_id) {
if (function_exists('tutor_utils')) {
$completed_courses = tutor_utils()->get_completed_courses_ids_by_user($user_id);
return count($completed_courses);
}
$completed_ids = custom_get_completed_course_ids($user_id);
return count($completed_ids);
}
/**
* Get completed course IDs
*/
function custom_get_completed_course_ids($user_id) {
global $wpdb;
$completed_meta = $wpdb->get_results($wpdb->prepare(
"SELECT meta_key, meta_value FROM {$wpdb->prefix}usermeta WHERE user_id = %d AND (meta_key LIKE '_tutor_course_completed_%%' OR meta_key LIKE '%%course_completed%%')",
$user_id
));
$completed_ids = array();
foreach ($completed_meta as $meta) {
if (strpos($meta->meta_key, '_tutor_course_completed_') === 0) {
$course_id = str_replace('_tutor_course_completed_', '', $meta->meta_key);
if (is_numeric($course_id)) {
$completed_ids[] = (int) $course_id;
}
}
}
if (function_exists('tutor_utils')) {
$enrolled_ids = $wpdb->get_col($wpdb->prepare(
"SELECT DISTINCT class_id FROM {$wpdb->prefix}class_enrollment WHERE student_id = %d",
$user_id
));
foreach ($enrolled_ids as $course_id) {
$course_progress = tutor_utils()->get_course_completed_percent($course_id, $user_id);
if (isset($course_progress['completed_percent']) && $course_progress['completed_percent'] >= 100) {
if (!in_array($course_id, $completed_ids)) {
$completed_ids[] = (int) $course_id;
}
}
}
}
return array_unique($completed_ids);
}
/**
* Register new user
*/
function custom_register_user($request) {
$username = sanitize_user($request->get_param('username'));
$email = sanitize_email($request->get_param('email'));
$password = $request->get_param('password');
if (empty($username) || empty($email) || empty($password)) {
return new WP_REST_Response(array(
'success' => false,
'message' => 'Username, email, and password are required',
), 400);
}
if (!is_email($email)) {
return new WP_REST_Response(array(
'success' => false,
'message' => 'Invalid email address',
), 400);
}
if (username_exists($username)) {
return new WP_REST_Response(array(
'success' => false,
'message' => 'Username already exists',
), 400);
}
if (email_exists($email)) {
return new WP_REST_Response(array(
'success' => false,
'message' => 'Email already exists',
), 400);
}
if (strlen($password) < 6) {
return new WP_REST_Response(array(
'success' => false,
'message' => 'Password must be at least 6 characters',
), 400);
}
$user_id = wp_create_user($username, $password, $email);
if (is_wp_error($user_id)) {
return new WP_REST_Response(array(
'success' => false,
'message' => $user_id->get_error_message(),
), 500);
}
$user = get_user_by('id', $user_id);
wp_set_current_user($user_id);
$token_request = new WP_REST_Request('POST', '/jwt-auth/v1/token');
$token_request->set_param('username', $username);
$token_request->set_param('password', $password);
$token_response = rest_do_request($token_request);
if ($token_response->is_error()) {
return new WP_REST_Response(array(
'success' => true,
'message' => 'User created successfully',
'user_id' => $user_id,
'username' => $username,
'email' => $email,
), 201);
}
$token_data = $token_response->get_data();
return new WP_REST_Response(array(
'success' => true,
'message' => 'User created and logged in successfully',
'user_id' => $user_id,
'username' => $username,
'email' => $email,
'token' => isset($token_data['token']) ? $token_data['token'] : null,
), 201);
}
/**
* Verify JWT token for authentication
*/
function custom_verify_jwt_token($request) {
$auth_header = $request->get_header('authorization');
if (!$auth_header) {
return new WP_Error('jwt_auth_no_auth_header', 'Authorization header not found.', array('status' => 401));
}
list($token) = sscanf($auth_header, 'Bearer %s');
if (!$token) {
return new WP_Error('jwt_auth_bad_auth_header', 'Authorization header malformed.', array('status' => 401));
}
$secret_key = defined('JWT_AUTH_SECRET_KEY') ? JWT_AUTH_SECRET_KEY : false;
if (!$secret_key) {
return new WP_Error('jwt_auth_bad_config', 'JWT is not configured properly.', array('status' => 500));
}
try {
$token_parts = explode('.', $token);
if (count($token_parts) !== 3) {
return new WP_Error('jwt_auth_invalid_token', 'Invalid token format.', array('status' => 401));
}
list($header, $payload, $signature) = $token_parts;
$valid_signature = hash_hmac('sha256', "$header.$payload", $secret_key, true);
$valid_signature = rtrim(strtr(base64_encode($valid_signature), '+/', '-_'), '=');
if ($signature !== $valid_signature) {
return new WP_Error('jwt_auth_invalid_token', 'Token signature verification failed.', array('status' => 401));
}
$payload_data = json_decode(base64_decode(strtr($payload, '-_', '+/')), true);
if (!$payload_data || !isset($payload_data['data']['user']['id'])) {
return new WP_Error('jwt_auth_invalid_token', 'Invalid token payload.', array('status' => 401));
}
if (isset($payload_data['exp']) && $payload_data['exp'] < time()) {
return new WP_Error('jwt_auth_token_expired', 'Token has expired.', array('status' => 401));
}
$user_id = $payload_data['data']['user']['id'];
$user = get_user_by('id', $user_id);
if (!$user) {
return new WP_Error('jwt_auth_user_not_found', 'User not found.', array('status' => 404));
}
wp_set_current_user($user_id);
return true;
} catch (Exception $e) {
return new WP_Error('jwt_auth_error', $e->getMessage(), array('status' => 401));
}
}
/**
* Get current authenticated user info
*/
function custom_get_current_user($request) {
$user_id = get_current_user_id();
if (!$user_id) {
return new WP_REST_Response(array(
'success' => false,
'message' => 'User not authenticated',
), 401);
}
$user = get_user_by('id', $user_id);
if (!$user) {
return new WP_REST_Response(array(
'success' => false,
'message' => 'User not found',
), 404);
}
$user_meta = get_user_meta($user_id);
return new WP_REST_Response(array(
'success' => true,
'id' => $user_id,
'username' => $user->user_login,
'email' => $user->user_email,
'name' => $user->display_name,
'first_name' => isset($user_meta['first_name'][0]) ? $user_meta['first_name'][0] : '',
'last_name' => isset($user_meta['last_name'][0]) ? $user_meta['last_name'][0] : '',
'nickname' => isset($user_meta['nickname'][0]) ? $user_meta['nickname'][0] : '',
'description' => isset($user_meta['description'][0]) ? $user_meta['description'][0] : '',
'avatar_url' => get_avatar_url($user_id),
'roles' => $user->roles,
), 200);
}
/**
* Change password for authenticated user
*/
function custom_change_password($request) {
$user_id = get_current_user_id();
if (!$user_id) {
return new WP_REST_Response(array(
'success' => false,
'message' => 'User not authenticated',
), 401);
}
$current_password = $request->get_param('current_password');
$new_password = $request->get_param('new_password');
$confirm_password = $request->get_param('confirm_password');
if (empty($current_password) || empty($new_password) || empty($confirm_password)) {
return new WP_REST_Response(array(
'success' => false,
'message' => 'All password fields are required',
), 400);
}
if ($new_password !== $confirm_password) {
return new WP_REST_Response(array(
'success' => false,
'message' => 'New password and confirm password do not match',
), 400);
}
if (strlen($new_password) < 6) {
return new WP_REST_Response(array(
'success' => false,
'message' => 'New password must be at least 6 characters',
), 400);
}
$user = get_user_by('id', $user_id);
if (!$user) {
return new WP_REST_Response(array(
'success' => false,
'message' => 'User not found',
), 404);
}
if (!wp_check_password($current_password, $user->data->user_pass, $user_id)) {
return new WP_REST_Response(array(
'success' => false,
'message' => 'Current password is incorrect',
), 400);
}
wp_set_password($new_password, $user_id);
return new WP_REST_Response(array(
'success' => true,
'message' => 'Password changed successfully',
), 200);
}
/**
* Get user membership information
*/
function custom_get_user_membership($request) {
$user_id = get_current_user_id();
if (!$user_id) {
return new WP_REST_Response(array(
'success' => false,
'message' => 'User not authenticated',
), 401);
}
global $wpdb;
$current_membership = $wpdb->get_row($wpdb->prepare(
"SELECT
mu.membership_id,
ml.name,
ml.description,
mu.initial_payment,
mu.billing_amount,
mu.cycle_number,
mu.cycle_period,
mu.billing_limit,
mu.trial_amount,
mu.trial_limit,
mu.startdate,
mu.enddate
FROM {$wpdb->prefix}pmpro_memberships_users mu
JOIN {$wpdb->prefix}pmpro_membership_levels ml ON mu.membership_id = ml.id
WHERE mu.user_id = %d
AND mu.status = 'active'
ORDER BY mu.id DESC
LIMIT 1",
$user_id
));
$membership_history = $wpdb->get_results($wpdb->prepare(
"SELECT
mu.membership_id,
ml.name,
mu.startdate,
mu.enddate,
mu.status
FROM {$wpdb->prefix}pmpro_memberships_users mu
JOIN {$wpdb->prefix}pmpro_membership_levels ml ON mu.membership_id = ml.id
WHERE mu.user_id = %d
ORDER BY mu.startdate DESC",
$user_id
));
$currency = get_option('pmpro_currency', 'USD');
return new WP_REST_Response(array(
'success' => true,
'current_membership' => $current_membership ? array(
'id' => (int) $current_membership->membership_id,
'name' => $current_membership->name,
'description' => $current_membership->description,
'start_date' => $current_membership->startdate,
'end_date' => $current_membership->enddate === '0000-00-00 00:00:00' ? null : $current_membership->enddate,
'initial_payment' => (float) $current_membership->initial_payment,
'billing_amount' => (float) $current_membership->billing_amount,
'cycle_number' => (int) $current_membership->cycle_number,
'cycle_period' => $current_membership->cycle_period,
'billing_limit' => (int) $current_membership->billing_limit,
'trial_amount' => (float) $current_membership->trial_amount,
'trial_limit' => (int) $current_membership->trial_limit,
'status' => 'active',
) : null,
'membership_history' => array_map(function($item) {
return array(
'id' => (int) $item->membership_id,
'name' => $item->name,
'start_date' => $item->startdate,
'end_date' => $item->enddate === '0000-00-00 00:00:00' ? null : $item->enddate,
'status' => $item->status,
);
}, $membership_history),
'currency' => $currency,
), 200);
}
/**
* Get user invoices/orders
*/
function custom_get_user_invoices($request) {
$user_id = get_current_user_id();
if (!$user_id) {
return new WP_REST_Response(array(
'success' => false,
'message' => 'User not authenticated',
), 401);
}
global $wpdb;
$orders = $wpdb->get_results($wpdb->prepare(
"SELECT
o.id,
o.code,
o.membership_id,
ml.name as membership_name,
o.total,
o.subtotal,
o.tax,
o.couponamount,
o.payment_type,
o.cardtype,
o.status,
o.gateway,
o.gateway_environment,
o.timestamp
FROM {$wpdb->prefix}pmpro_membership_orders o
LEFT JOIN {$wpdb->prefix}pmpro_membership_levels ml ON o.membership_id = ml.id
WHERE o.user_id = %d
ORDER BY o.timestamp DESC",
$user_id
));
$currency = get_option('pmpro_currency', 'USD');
return new WP_REST_Response(array(
'success' => true,
'invoices' => array_map(function($order) use ($currency) {
return array(
'id' => (int) $order->id,
'code' => $order->code,
'membership_id' => (int) $order->membership_id,
'membership_name' => $order->membership_name,
'amount' => (float) $order->total,
'subtotal' => (float) $order->subtotal,
'tax' => (float) $order->tax,
'discount' => (float) $order->couponamount,
'currency' => $currency,
'payment_type' => $order->payment_type,
'card_type' => $order->cardtype,
'status' => $order->status,
'gateway' => $order->gateway,
'date' => $order->timestamp,
);
}, $orders),
'total_count' => count($orders),
), 200);
}
/**
* Get user social profiles
*/
function custom_get_social_profiles($request) {
$user_id = get_current_user_id();
if (!$user_id) {
return new WP_REST_Response(array(
'success' => false,
'message' => 'User not authenticated',
), 401);
}
$facebook = get_user_meta($user_id, '_tutor_profile_facebook', true);
$twitter = get_user_meta($user_id, '_tutor_profile_twitter', true);
$linkedin = get_user_meta($user_id, '_tutor_profile_linkedin', true);
$website = get_user_meta($user_id, '_tutor_profile_website', true);
$github = get_user_meta($user_id, '_tutor_profile_github', true);
return new WP_REST_Response(array(
'success' => true,
'social_profiles' => array(
'facebook' => $facebook ?: '',
'twitter' => $twitter ?: '',
'linkedin' => $linkedin ?: '',
'website' => $website ?: '',
'github' => $github ?: '',
),
), 200);
}
/**
* Update user social profiles
*/
function custom_update_social_profiles($request) {
$user_id = get_current_user_id();
if (!$user_id) {
return new WP_REST_Response(array(
'success' => false,
'message' => 'User not authenticated',
), 401);
}
$facebook = sanitize_text_field($request->get_param('facebook'));
$twitter = sanitize_text_field($request->get_param('twitter'));
$linkedin = sanitize_text_field($request->get_param('linkedin'));
$website = esc_url_raw($request->get_param('website'));
$github = sanitize_text_field($request->get_param('github'));
if ($facebook !== null) {
update_user_meta($user_id, '_tutor_profile_facebook', $facebook);
}
if ($twitter !== null) {
update_user_meta($user_id, '_tutor_profile_twitter', $twitter);
}
if ($linkedin !== null) {
update_user_meta($user_id, '_tutor_profile_linkedin', $linkedin);
}
if ($website !== null) {
update_user_meta($user_id, '_tutor_profile_website', $website);
}
if ($github !== null) {
update_user_meta($user_id, '_tutor_profile_github', $github);
}
return new WP_REST_Response(array(
'success' => true,
'message' => 'Social profiles updated successfully',
'social_profiles' => array(
'facebook' => get_user_meta($user_id, '_tutor_profile_facebook', true) ?: '',
'twitter' => get_user_meta($user_id, '_tutor_profile_twitter', true) ?: '',
'linkedin' => get_user_meta($user_id, '_tutor_profile_linkedin', true) ?: '',
'website' => get_user_meta($user_id, '_tutor_profile_website', true) ?: '',
'github' => get_user_meta($user_id, '_tutor_profile_github', true) ?: '',
),
), 200);
}
/**
* Get user quiz attempts
*/
function custom_get_user_quiz_attempts($request) {
$user_id = get_current_user_id();
if (!$user_id) {
return new WP_REST_Response(array(
'success' => false,
'message' => 'User not authenticated',
), 401);
}
global $wpdb;
$quiz_attempts_table = $wpdb->prefix . 'quiz_attempts';
$quiz_attempt_data_table = $wpdb->prefix . 'quiz_attempt_data';
$users_table = $wpdb->prefix . 'users';
$quizzes_table = $wpdb->prefix . 'quizzes';
$questions_table = $wpdb->prefix . 'quiz_questions';
// Check if tables exist
$table_exists = $wpdb->get_var("SHOW TABLES LIKE '$quiz_attempts_table'");
if (!$table_exists) {
return new WP_REST_Response(array(
'success' => false,
'message' => 'Quiz attempts table not found',
), 404);
}
// Retrieve all quiz attempts for the user in reverse chronological order
$quiz_attempts = $wpdb->get_results($wpdb->prepare("
SELECT
ua.id AS attempt_id,
u.display_name AS student_name,
ua.score,
ua.datetime,
c.name AS class_name,
q.title AS quiz_title
FROM $quiz_attempts_table ua
INNER JOIN $quizzes_table q ON ua.quiz_id = q.id
INNER JOIN $users_table u ON ua.student_id = u.ID
INNER JOIN {$wpdb->prefix}class_enrollment ce ON ua.student_id = ce.student_id
INNER JOIN {$wpdb->prefix}classes c ON ce.class_id = c.id
WHERE ua.student_id = %d
ORDER BY ua.datetime DESC
", $user_id));
if (empty($quiz_attempts)) {
return new WP_REST_Response(array(
'success' => true,
'quiz_attempts' => array(),
'total_count' => 0,
), 200);
}
// Format the results
$formatted_attempts = array();
foreach ($quiz_attempts as $attempt) {
$attempt_id = $attempt->attempt_id;
// Get detailed results for this attempt
$details_results = $wpdb->get_results($wpdb->prepare("
SELECT qq.title AS question_title, qad.answer, qad.is_correct
FROM $quiz_attempt_data_table qad
INNER JOIN $questions_table qq ON qad.question_id = qq.id
WHERE qad.quiz_attempt_id = %d
", $attempt_id));
$correct_answers = 0;
$total_questions = count($details_results);
$question_details = array();
foreach ($details_results as $detail) {
if ($detail->is_correct) {
$correct_answers++;
}
$question_details[] = array(
'question' => $detail->question_title,
'answer' => $detail->answer,
'is_correct' => (bool) $detail->is_correct,
);
}
$percentage = $total_questions > 0 ? round(($correct_answers / $total_questions) * 100, 2) : 0;
$formatted_attempts[] = array(
'attempt_id' => (int) $attempt->attempt_id,
'name' => $attempt->student_name,
'class' => $attempt->class_name,
'quiz_title' => $attempt->quiz_title,
'score' => $attempt->score,
'date' => $attempt->datetime,
'percentage' => $percentage,
'correct_answers' => $correct_answers,
'total_questions' => $total_questions,
'details' => $question_details,
);
}
return new WP_REST_Response(array(
'success' => true,
'quiz_attempts' => $formatted_attempts,
'total_count' => count($formatted_attempts),
), 200);
}