Skip to content

Commit

Permalink
Merge pull request #4938 from Automattic/update/reports-structure-pro…
Browse files Browse the repository at this point in the history
…posal

Extract `get_courses` with dependent methods from `Sensei_Analysis_Overview_List_Table`
  • Loading branch information
merkushin committed Mar 30, 2022
2 parents 8b293b5 + c582358 commit cb31337
Show file tree
Hide file tree
Showing 19 changed files with 2,378 additions and 24 deletions.
26 changes: 13 additions & 13 deletions includes/class-sensei-analysis-overview-list-table.php
Expand Up @@ -427,19 +427,19 @@ protected function get_row_data( $item ) {
}

$average_course_progress = $this->get_average_progress_for_courses_table( $item->ID );
$column_data = apply_filters(
'sensei_analysis_overview_column_data',
array(
'title' => $course_title,
'last_activity' => $last_activity_date,
'completions' => $course_completions,
'average_progress' => $average_course_progress,
'average_grade' => $average_grade,
'days_to_completion' => $average_completion_days,
),
$item,
$this
);
$column_data = apply_filters(
'sensei_analysis_overview_column_data',
array(
'title' => $course_title,
'last_activity' => $last_activity_date,
'completions' => $course_completions,
'average_progress' => $average_course_progress,
'average_grade' => $average_grade,
'days_to_completion' => $average_completion_days,
),
$item,
$this
);
break;

case 'lessons':
Expand Down
28 changes: 17 additions & 11 deletions includes/class-sensei-analysis.php
Expand Up @@ -240,22 +240,28 @@ public function load_data_table_files() {
}

/**
* load_data_object creates new instance of class
* The load_data_object method creates new instance of class
*
* @param string $name Name of class
* @param integer $data constructor arguments
* @param undefined $optional_data optional constructor arguments
* @return object class instance object
* @param string $name Name of class.
* @param mixed $data Constructor arguments.
* @param mixed $optional_data Optional constructor arguments.
* @return Sensei_List_Table Class instance object
*/
public function load_data_object( $name = '', $data = 0, $optional_data = null ) {
// Load Analysis data
$object_name = 'Sensei_Analysis_' . $name . '_List_Table';
if ( is_null( $optional_data ) ) {
$sensei_analysis_object = new $object_name( $data );
if ( 'Overview' === $name ) {
$factory = new Sensei_Reports_Overview_List_Table_Factory();
$sensei_analysis_object = $factory->create( $data );
} else {
$sensei_analysis_object = new $object_name( $data, $optional_data );
$object_name = 'Sensei_Analysis_' . $name . '_List_Table';
if ( is_null( $optional_data ) ) {
$sensei_analysis_object = new $object_name( $data );
} else {
$sensei_analysis_object = new $object_name( $data, $optional_data );
}
}

$sensei_analysis_object->prepare_items();

return $sensei_analysis_object;
}

Expand Down Expand Up @@ -283,7 +289,7 @@ public function analysis_page() {

$this->check_course_lesson( $course_id, $lesson_id, $user_id );

$type = isset( $_GET['view'] ) ? esc_html( $_GET['view'] ) : false;
$type = isset( $_GET['view'] ) ? esc_html( $_GET['view'] ) : 'students';

if ( 0 < $lesson_id ) {
// Viewing a specific Lesson and all its Learners
Expand Down
2 changes: 2 additions & 0 deletions includes/class-sensei-autoloader.php
Expand Up @@ -74,6 +74,8 @@ public function __construct() {
new Sensei_Autoloader_Bundle( 'course-theme' ),
new Sensei_Autoloader_Bundle( 'course-video' ),
new Sensei_Autoloader_Bundle( 'course-video/blocks' ),
new Sensei_Autoloader_Bundle( 'reports/overview/data-provider' ),
new Sensei_Autoloader_Bundle( 'reports/overview/list-table' ),
);

// Add Sensei custom auto loader.
Expand Down
@@ -0,0 +1,162 @@
<?php
/**
* File containing the Sensei_Reports_Overview_Data_Provider_Courses class.
*
* @package sensei
*/

if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Class Sensei_Reports_Overview_Data_Provider_Courses
*
* @since 4.3.0
*/
class Sensei_Reports_Overview_Data_Provider_Courses implements Sensei_Reports_Overview_Data_Provider_Interface {

/**
* Total number of courses found with given criteria.
*
* @var int Total number of items
*/
private $last_total_items = 0;

/**
* Contains start date and time for filtering.
*
* @var string|null
*/
private $date_from;

/**
* Contains end date and time for filtering.
*
* @var string|null
*/
private $date_to;

/**
* Get the data for the overview report.
*
* @param array $filters Filters to apply to the data.
*
* @return array
*/
public function get_items( array $filters ): array {
$this->date_from = $filters['last_activity_date_from'] ?? null;
$this->date_to = $filters['last_activity_date_to'] ?? null;

$course_args = array(
'post_type' => 'course',
'post_status' => array( 'publish', 'private' ),
'posts_per_page' => $filters['number'],
'offset' => $filters['offset'],
'orderby' => $filters['orderby'],
'order' => $filters['order'],
'suppress_filters' => 0,
);

if ( isset( $filters['search'] ) ) {
$course_args['s'] = $filters['search'];
}

add_filter( 'posts_clauses', [ $this, 'filter_courses_by_last_activity' ] );
add_filter( 'posts_clauses', [ $this, 'add_days_to_completion_to_courses_queries' ] );
$courses_query = new WP_Query( apply_filters( 'sensei_analysis_overview_filter_courses', $course_args ) );
remove_filter( 'posts_clauses', [ $this, 'filter_courses_by_last_activity' ] );
remove_filter( 'posts_clauses', [ $this, 'add_days_to_completion_to_courses_queries' ] );

$this->last_total_items = $courses_query->found_posts;

return $courses_query->posts;
}

/**
* Filter the courses by last activity start/end date.
*
* @access private
*
* @param array $clauses Associative array of the clauses for the query.
*
* @return array Modified associative array of the clauses for the query.
*/
public function filter_courses_by_last_activity( array $clauses ): array {
global $wpdb;

if ( ! $this->date_from && ! $this->date_to ) {
return $clauses;
}
// Fetch the lessons within the expected last activity range.
$lessons_query = "SELECT cm.comment_post_id lesson_id, MAX(cm.comment_date_gmt) as comment_date_gmt
FROM {$wpdb->comments} cm
WHERE cm.comment_approved IN ('complete', 'passed', 'graded')
AND cm.comment_type = 'sensei_lesson_status'";

// Filter by start date.
if ( $this->date_from ) {
$lessons_query .= $wpdb->prepare(
' AND cm.comment_date_gmt >= %s',
$this->date_from
);
}
$lessons_query .= ' GROUP BY cm.comment_post_id';

// Fetch the course IDs associated with those lessons.
$course_query = "SELECT DISTINCT(pm.meta_value) course_id
FROM {$wpdb->postmeta} pm JOIN ({$lessons_query}) cm
ON cm.lesson_id = pm.post_id
AND pm.meta_key = '_lesson_course'
GROUP BY pm.meta_value
";

// Filter by end date.
if ( $this->date_to ) {
$course_query .= $wpdb->prepare(
' HAVING MAX(cm.comment_date_gmt) <= %s',
$this->date_to
);
}

$clauses['where'] .= " AND {$wpdb->posts}.ID IN ({$course_query})";

return $clauses;
}


/**
* Add the sum of days taken by each student to complete a course and the number of completions for each course.
*
* @access private
*
* @param array $clauses Associative array of the clauses for the query.
*
* @return array Modified associative array of the clauses for the query.
*/
public function add_days_to_completion_to_courses_queries( $clauses ) {
global $wpdb;

// Get the number of days to complete a course: `days to complete = complete date - start date + 1`.
$clauses['fields'] .= ", SUM( ABS( DATEDIFF( {$wpdb->comments}.comment_date, STR_TO_DATE( {$wpdb->commentmeta}.meta_value, '%Y-%m-%d %H:%i:%s' ) ) ) + 1 ) AS days_to_completion";
// We consider the course as completed if there is a comment and corresponding meta for it.
$clauses['fields'] .= ", COUNT({$wpdb->commentmeta}.comment_id) AS count_of_completions";
$clauses['join'] .= " LEFT JOIN {$wpdb->comments} ON {$wpdb->comments}.comment_post_ID = {$wpdb->posts}.ID";
$clauses['join'] .= " AND {$wpdb->comments}.comment_type IN ('sensei_course_status')";
$clauses['join'] .= " AND {$wpdb->comments}.comment_approved IN ( 'complete' )";
$clauses['join'] .= " AND {$wpdb->comments}.comment_post_ID = {$wpdb->posts}.ID";
$clauses['join'] .= " LEFT JOIN {$wpdb->commentmeta} ON {$wpdb->comments}.comment_ID = {$wpdb->commentmeta}.comment_id";
$clauses['join'] .= " AND {$wpdb->commentmeta}.meta_key = 'start'";
$clauses['groupby'] .= " {$wpdb->posts}.ID";

return $clauses;
}

/**
* Get the total number of items found for the last query.
*
* @return int
*/
public function get_last_total_items(): int {
return $this->last_total_items;
}
}
@@ -0,0 +1,30 @@
<?php
/**
* File containing the interface Sensei_Reports_Overview_DataProvider_Interface.
*
* @package sensei
*/

/**
* Interface Sensei_Reports_Overview_Data_Provider_Interface.
*
* @since 4.3.0
*/
interface Sensei_Reports_Overview_Data_Provider_Interface {
/**
* Get the data for the overview report.
*
* @param array $filters Filters to apply to the data.
*
* @return array
*/
public function get_items( array $filters ): array;

/**
* Get the total number of items found for the last query.
*
* @return int
*/
public function get_last_total_items(): int;

}
@@ -0,0 +1,39 @@
<?php
/**
* File containing the Sensei_Reports_Overview_Data_Provider_Lessons class.
*
* @package sensei
*/

if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}

/**
* Class Sensei_Reports_Overview_Data_Provider_Courses
*
* @since 4.3.0
*/
class Sensei_Reports_Overview_Data_Provider_Lessons implements Sensei_Reports_Overview_Data_Provider_Interface {
/**
* Get the data for the overview report.
*
* @param array $filters Filters to apply to the data.
*
* @return array
*/
public function get_items( array $filters ): array {
// TODO: Implement get_items() method.
return [];
}

/**
* Get the total number of items found for the last query.
*
* @return int
*/
public function get_last_total_items(): int {
// TODO: Implement get_last_total_items() method.
return 0;
}
}

0 comments on commit cb31337

Please sign in to comment.