<?php
/**
* File containing Sensei_Tool_Enrolment_Debug class.
*
* @package sensei-lms
* @since 3.7.0
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Sensei_Tool_Enrolment_Debug class.
*
* @since 3.7.0
*/
class Sensei_Tool_Enrolment_Debug implements Sensei_Tool_Interface, Sensei_Tool_Interactive_Interface {
const NONCE_ACTION = 'enrolment-debug';
/**
* Debug results.
*
* @var array
*/
private $results;
/**
* Get the ID of the tool.
*
* @return string
*/
public function get_id() {
return 'enrolment-debug';
}
/**
* Get the name of the tool.
*
* @return string
*/
public function get_name() {
return __( 'Debug Course Enrollment', 'sensei-lms' );
}
/**
* Get the description of the tool.
*
* @return string
*/
public function get_description() {
return __( 'Check what the enrollment status is between a course and student.', 'sensei-lms' );
}
/**
* Is the tool a single action?
*
* @return bool
*/
public function is_single_action() {
return false;
}
/**
* Output tool view for interactive action methods.
*/
public function output() {
// If the results were processed by `process()`, show them here.
if ( $this->results ) {
$results = $this->results;
include __DIR__ . '/views/html-enrolment-debug.php';
}
$user_query_args = [
'number' => 100,
'orderby' => 'display_name',
'order' => 'ASC',
];
$user_search = new WP_User_Query( $user_query_args );
// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- Variable used in view.
$users = false;
if ( $user_search->get_total() < 100 ) {
$users = $user_search->get_results();
}
$course_query_args = [
'posts_per_page' => 100,
'orderby' => 'title',
'order' => 'ASC',
'post_type' => 'course',
'post_status' => 'any',
];
$course_search = new WP_Query( $course_query_args );
// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- Variable used in view.
$courses = false;
if ( $course_search->found_posts < 100 ) {
$courses = $course_search->posts;
}
// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- Variable used in view.
$tool_id = $this->get_id();
include __DIR__ . '/views/html-enrolment-debug-form.php';
}
/**
* Return to tool with a message.
*
* @param string $message Message to show.
* @param bool $is_error True if this is an error message.
*/
private function return_with_message( $message, $is_error ) {
Sensei_Tools::instance()->add_user_message( $message, $is_error );
wp_safe_redirect( Sensei_Tools::instance()->get_tool_url( $this ) );
exit;
}
/**
* Process the tool action.
*/
public function process() {
Sensei()->assets->enqueue( 'sensei-enrolment-debug', 'css/enrolment-debug.css' );
if ( empty( $_GET['course_id'] ) && empty( $_GET['user_id'] ) ) {
return;
}
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Don't modify the nonce.
if ( empty( $_GET['_wpnonce'] ) || ! wp_verify_nonce( wp_unslash( $_GET['_wpnonce'] ), self::NONCE_ACTION ) ) {
Sensei_Tools::instance()->trigger_invalid_request( $this );
return;
}
if ( empty( $_GET['user_id'] ) ) {
$this->return_with_message( __( 'Please select a user.', 'sensei-lms' ), true );
return;
}
if ( empty( $_GET['course_id'] ) ) {
$this->return_with_message( __( 'Please select a course ID.', 'sensei-lms' ), true );
return;
}
$user_id = intval( $_GET['user_id'] );
$course_id = intval( $_GET['course_id'] );
$user = get_user_by( 'ID', $user_id );
if ( ! $user ) {
$this->return_with_message( __( 'Invalid user ID selected.', 'sensei-lms' ), true );
return;
}
$course = get_post( $course_id );
if ( ! $course || 'course' !== get_post_type( $course ) ) {
$this->return_with_message( __( 'Invalid course ID selected.', 'sensei-lms' ), true );
return;
}
$this->results = $this->get_debug_results( $user, $course );
}
/**
* Get the debug results for a user/course.
*
* @param WP_User $user User object.
* @param WP_Post $course Course post object.
*
* @return array
*/
private function get_debug_results( WP_User $user, WP_Post $course ) {
$enrolment_manager = Sensei_Course_Enrolment_Manager::instance();
$course_enrolment = Sensei_Course_Enrolment::get_course_instance( $course->ID );
$provider_results = $course_enrolment->get_enrolment_check_results( $user->ID );
$is_enrolled = $course_enrolment->is_enrolled( $user->ID );
$provider_results_arr = [];
$results_stale = false;
if ( ! $provider_results || $provider_results->get_version_hash() !== $course_enrolment->get_current_enrolment_result_version() ) {
$results_stale = true;
$provider_results = $course_enrolment->get_enrolment_check_results( $user->ID );
}
if ( $provider_results ) {
$provider_results_arr = $provider_results->get_provider_results();
}
$debug_results = [
'course' => $course->post_title,
'course_id' => $course->ID,
'course_published' => 'publish' === $course->post_status,
'user' => $user->display_name . ' (' . $user->ID . ')',
'user_id' => $user->ID,
'is_enrolled' => $is_enrolled,
'is_removed' => $course_enrolment->is_learner_removed( $user->ID ),
'results_stale' => $results_stale,
'results_match' => true,
'results_time' => $provider_results ? self::format_date( $provider_results->get_time() ) : null,
'providers' => [],
'progress' => false,
];
$course_progress_id = Sensei_Utils::has_started_course( $course->ID, $user->ID );
if ( $course_progress_id ) {
$course_progress = get_comment( $course_progress_id );
$user_start_date = get_comment_meta( $course_progress_id, 'start', true );
$status = esc_html__( 'In Progress', 'sensei-lms' );
if ( 'complete' === $course_progress->comment_approved ) {
$status = esc_html__( 'Completed', 'sensei-lms' );
$percent_complete = 100;
} else {
$percent_complete = $this->get_percent_complete( $user->ID, $course->ID );
}
$last_activity_time = $this->get_last_progress_activity_date( $user->ID, $course->ID, $course_progress_id );
$debug_results['progress'] = [
'start_date' => self::format_date( strtotime( $user_start_date ) ),
'last_activity' => $last_activity_time ? self::format_date( $last_activity_time ) : false,
'status' => $status,
'percent_complete' => $percent_complete,
];
}
$providers = $enrolment_manager->get_all_enrolment_providers();
foreach ( $providers as $provider ) {
$provider_info = [
'id' => $provider->get_id(),
'name' => $provider->get_name(),
'handles_course' => $provider->handles_enrolment( $course->ID ),
'is_enrolled' => null,
'debug' => false,
'logs' => [],
'history' => false,
];
if ( $provider_info['handles_course'] ) {
$provider_info['is_enrolled'] = false;
try {
$provider_info['is_enrolled'] = $provider->is_enrolled( $user->ID, $course->ID );
} catch ( Exception $e ) {
$provider_info['logs'][] = [
'timestamp' => time(),
'message' => $e->getMessage(),
'data' => [
'source' => $e->getFile() . ':' . $e->getLine(),
'trace' => $e->getTraceAsString(),
],
];
}
if (
! isset( $provider_results_arr[ $provider->get_id() ] )
|| $provider_results_arr[ $provider->get_id() ] !== $provider_info['is_enrolled']
) {
$debug_results['results_match'] = false;
}
} else {
if ( isset( $provider_results_arr[ $provider->get_id() ] ) ) {
$debug_results['results_match'] = false;
}
}
if (
interface_exists( 'Sensei_Course_Enrolment_Provider_Debug_Interface' )
&& $provider instanceof Sensei_Course_Enrolment_Provider_Debug_Interface
) {
$provider_info['debug'] = $provider->debug( $user->ID, $course->ID );
}
if ( class_exists( 'Sensei_Enrolment_Provider_Journal_Store' ) ) {
$provider_info['logs'] = array_merge( $provider_info['logs'], Sensei_Enrolment_Provider_Journal_Store::get_provider_logs( $provider, $user->ID, $course->ID ) );
$provider_info['history'] = Sensei_Enrolment_Provider_Journal_Store::get_provider_history( $provider, $user->ID, $course->ID );
}
$debug_results['providers'][ $provider_info['id'] ] = $provider_info;
}
return $debug_results;
}
/**
* Get the unix timestamp of the last progress activity.
*
* @param int $user_id User ID.
* @param int $course_id Course post ID.
* @param int $course_progress_id Course progress comment ID.
*
* @return int|false
*/
private function get_last_progress_activity_date( $user_id, $course_id, $course_progress_id ) {
$dates = [];
$course_progress = get_comment( $course_progress_id );
if ( ! $course_progress ) {
return false;
}
$dates[] = strtotime( $course_progress->comment_date_gmt );
$course_lessons = Sensei()->course->course_lessons( $course_id );
foreach ( $course_lessons as $lesson ) {
$lesson_progress = Sensei()->lesson_progress_repository->get( $lesson->ID, $user_id );
if ( $lesson_progress ) {
$dates[] = $lesson_progress->get_updated_at()->getTimestamp();
}
}
return max( $dates );
}
/**
* Get the percent complete for a user's progress in a course.
*
* @param int $user_id User ID.
* @param int $course_id Course post ID.
*
* @return false|float
*/
private function get_percent_complete( $user_id, $course_id ) {
$completed_lesson_ids = [];
$course_lessons = Sensei()->course->course_lessons( $course_id );
if ( empty( $course_lessons ) ) {
return 0;
}
foreach ( $course_lessons as $lesson ) {
$is_lesson_completed = Sensei_Utils::user_completed_lesson( $lesson->ID, $user_id );
if ( $is_lesson_completed ) {
$completed_lesson_ids[] = $lesson->ID;
}
}
return round( ( count( $completed_lesson_ids ) / count( $course_lessons ) ) * 100 );
}
/**
* Get the URL for the enrolment debug tool for a user/course.
*
* @param int $user_id User ID.
* @param int $course_id Course post ID.
*
* @return string
*/
public static function get_enrolment_debug_url( $user_id, $course_id ) {
return wp_nonce_url(
add_query_arg(
[
'course_id' => $course_id,
'user_id' => $user_id,
],
Sensei_Tools::instance()->get_tool_url( new self() )
),
self::NONCE_ACTION
);
}
/**
* Format the date time to be human readable.
*
* @param int|float $time Format the time.
*
* @return string
*/
public static function format_date( $time ) {
$time = round( $time );
$date_time_format = get_option( 'date_format' ) . ' ' . get_option( 'time_format' );
if ( function_exists( 'wp_date' ) ) {
$formatted_time = wp_date( $date_time_format, $time );
} else {
$formatted_time = date_i18n( $date_time_format, $time );
}
return $formatted_time;
}
/**
* Add debug button to row.
*
* @param array $row_data Row data for learner management.
* @param array $item Activity information for row.
* @param int $course_id Course post ID.
*
* @return array
*/
public static function add_debug_action( $row_data, $item, $course_id = null ) {
/**
* Show the enrolment debug button on learner management.
*
* @since 3.7.0
*
* @hook sensei_show_enrolment_debug_button
*
* @param {bool} $show_button Whether to show the button.
* @param {int} $user_id User ID.
* @param {int} $course_id Course ID.
* @return {bool} Filtered value.
*/
$show_button = apply_filters( 'sensei_show_enrolment_debug_button', false, $item->user_id, $course_id );
if (
! $course_id
|| ! current_user_can( 'manage_sensei' )
|| ! $show_button
|| 'course' !== get_post_type( $course_id )
) {
return $row_data;
}
$button_url = self::get_enrolment_debug_url( $item->user_id, $course_id );
$row_data['actions'] .= ' <a class="button" href="' . esc_url( $button_url ) . '">' . esc_html__( 'Debug Enrollment', 'sensei-lms' ) . '</a>';
return $row_data;
}
/**
* Is the tool currently available?
*
* @return bool True if tool is available.
*/
public function is_available() {
return true;
}
}