From a65139b0d9a0bd53e3909e73017a96b7700cb321 Mon Sep 17 00:00:00 2001 From: Miroslav Mitev Date: Fri, 8 Sep 2023 22:15:17 +0300 Subject: [PATCH 01/18] Fix unnecessary file loading from `Sensei_CLI` --- includes/class-sensei-cli.php | 8 -------- 1 file changed, 8 deletions(-) diff --git a/includes/class-sensei-cli.php b/includes/class-sensei-cli.php index 4b5667d8d7..a88fca35e4 100644 --- a/includes/class-sensei-cli.php +++ b/includes/class-sensei-cli.php @@ -17,17 +17,9 @@ class Sensei_CLI { * Class constructor. */ public function __construct() { - $this->load(); $this->register(); } - /** - * Load the command files. - */ - private function load() { - require_once dirname( __FILE__ ) . '/cli/class-sensei-db-seed-command.php'; - } - /** * Register the CLI commands. */ From 50af630e56f7f85f3d0f06b90fa4fd3c50f770d7 Mon Sep 17 00:00:00 2001 From: Miroslav Mitev Date: Fri, 8 Sep 2023 22:15:57 +0300 Subject: [PATCH 02/18] Fix `Sensei_Course::get_all_courses` return type --- includes/class-sensei-course.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/includes/class-sensei-course.php b/includes/class-sensei-course.php index 0366497c81..93ddc7e13a 100755 --- a/includes/class-sensei-course.php +++ b/includes/class-sensei-course.php @@ -2261,9 +2261,7 @@ public function load_user_courses_content( $user = false ) { * Returns a list of all courses * * @since 1.8.0 - * @return array $courses{ - * @type $course WP_Post - * } + * @return WP_Post[] */ public static function get_all_courses() { From 2d3bf21f216ff9d4e8e178172e5745908d22d23a Mon Sep 17 00:00:00 2001 From: Miroslav Mitev Date: Fri, 8 Sep 2023 22:16:09 +0300 Subject: [PATCH 03/18] Add the validate progress command --- includes/class-sensei-cli.php | 1 + ...ss-sensei-db-validate-progress-command.php | 199 ++++++++++++++++++ 2 files changed, 200 insertions(+) create mode 100644 includes/cli/class-sensei-db-validate-progress-command.php diff --git a/includes/class-sensei-cli.php b/includes/class-sensei-cli.php index a88fca35e4..9c57136c04 100644 --- a/includes/class-sensei-cli.php +++ b/includes/class-sensei-cli.php @@ -25,5 +25,6 @@ public function __construct() { */ private function register() { WP_CLI::add_command( 'sensei db seed', Sensei_DB_Seed_Command::class ); + WP_CLI::add_command( 'sensei db validate progress', Sensei_DB_Validate_Progress_Command::class ); } } diff --git a/includes/cli/class-sensei-db-validate-progress-command.php b/includes/cli/class-sensei-db-validate-progress-command.php new file mode 100644 index 0000000000..d47803aa52 --- /dev/null +++ b/includes/cli/class-sensei-db-validate-progress-command.php @@ -0,0 +1,199 @@ +is_progress_migration_complete() ) { + WP_CLI::error( 'The progress migration is not complete. Please run the progress migration first.' ); + } + + $courses = Sensei_Course::get_all_courses(); + + foreach ( $courses as $course ) { + $this->validate_course( $course ); + + $lessons = Sensei()->course->course_lessons( $course->ID, 'any' ); + foreach ( $lessons as $lesson ) { + $this->validate_lesson( $lesson ); + } + } + } + + /** + * Check if the progress migration is complete. + * + * @return bool + */ + private function is_progress_migration_complete(): bool { + return (bool) get_option( Migration_Job_Scheduler::COMPLETED_OPTION_NAME, false ); + } + + /** + * Validate the course progress. + * + * @param WP_Post $course Course post object. + */ + private function validate_course( WP_Post $course ): void { + WP_CLI::log( "Validating course {$course->post_title} ({$course->ID})..." ); + + global $wpdb; + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching + $user_ids = $wpdb->get_col( + $wpdb->prepare( + "SELECT user_id FROM {$wpdb->comments} + WHERE comment_type = 'sensei_course_status' + AND comment_post_ID = %d", + $course->ID + ) + ); + if ( ! $user_ids ) { + return; + } + + $comments_based_repository = new Comments_Based_Course_Progress_Repository(); + $tables_based_repository = new Tables_Based_Course_Progress_Repository( $wpdb ); + + foreach ( $user_ids as $user_id ) { + $comments_based_progress = $comments_based_repository->get( $course->ID, $user_id ); + $tables_based_progress = $tables_based_repository->get( $course->ID, $user_id ); + + if ( ! $comments_based_progress ) { + WP_CLI::warning( 'Comments based progress not found.' ); + continue; + } + + if ( ! $tables_based_progress ) { + WP_CLI::warning( 'Tables based progress not found.' ); + continue; + } + + if ( + $comments_based_progress->get_course_id() !== $tables_based_progress->get_course_id() + || $comments_based_progress->get_user_id() !== $tables_based_progress->get_user_id() + || $comments_based_progress->get_status() !== $tables_based_progress->get_status() + || $comments_based_progress->get_started_at() != $tables_based_progress->get_started_at() // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison + || $comments_based_progress->get_completed_at() != $tables_based_progress->get_completed_at() // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison + ) { + WP_CLI::warning( 'Data mismatch between comments and tables based progress.' ); + WP_CLI\Utils\format_items( + 'table', + [ + [ + 'source' => 'comments', + 'course_id' => $comments_based_progress->get_course_id(), + 'user_id' => $comments_based_progress->get_user_id(), + 'status' => $comments_based_progress->get_status(), + 'started_at' => $comments_based_progress->get_started_at(), + 'completed_at' => $comments_based_progress->get_completed_at(), + ], + [ + 'source' => 'tables', + 'course_id' => $tables_based_progress->get_course_id(), + 'user_id' => $tables_based_progress->get_user_id(), + 'status' => $tables_based_progress->get_status(), + 'started_at' => $tables_based_progress->get_started_at(), + 'completed_at' => $tables_based_progress->get_completed_at(), + ], + ], + [ 'source', 'course_id', 'user_id', 'status', 'started_at', 'completed_at' ] + ); + } + } + } + + /** + * Validate the lesson progress. + * + * @param WP_Post $lesson Lesson post object. + */ + private function validate_lesson( WP_Post $lesson ): void { + WP_CLI::log( "Validating lesson {$lesson->post_title} ({$lesson->ID})..." ); + + global $wpdb; + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching + $user_ids = $wpdb->get_col( + $wpdb->prepare( + "SELECT user_id FROM {$wpdb->comments} + WHERE comment_type = 'sensei_lesson_status' + AND comment_post_ID = %d", + $lesson->ID + ) + ); + if ( ! $user_ids ) { + return; + } + + $comments_based_repository = new Comments_Based_Lesson_Progress_Repository(); + $tables_based_repository = new Tables_Based_Lesson_Progress_Repository( $wpdb ); + + foreach ( $user_ids as $user_id ) { + $comments_based_progress = $comments_based_repository->get( $lesson->ID, $user_id ); + $tables_based_progress = $tables_based_repository->get( $lesson->ID, $user_id ); + + if ( ! $comments_based_progress ) { + WP_CLI::warning( 'Comments based progress not found.' ); + continue; + } + + if ( ! $tables_based_progress ) { + WP_CLI::warning( 'Tables based progress not found.' ); + continue; + } + + if ( + $comments_based_progress->get_lesson_id() !== $tables_based_progress->get_lesson_id() + || $comments_based_progress->get_user_id() !== $tables_based_progress->get_user_id() + || $comments_based_progress->get_status() !== $tables_based_progress->get_status() + || $comments_based_progress->get_started_at() != $tables_based_progress->get_started_at() // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison + || $comments_based_progress->get_completed_at() != $tables_based_progress->get_completed_at() // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison + ) { + WP_CLI::warning( 'Data mismatch between comments and tables based progress.' ); + WP_CLI\Utils\format_items( + 'table', + [ + [ + 'source' => 'comments', + 'lesson_id' => $comments_based_progress->get_lesson_id(), + 'user_id' => $comments_based_progress->get_user_id(), + 'status' => $comments_based_progress->get_status(), + 'started_at' => $comments_based_progress->get_started_at(), + 'completed_at' => $comments_based_progress->get_completed_at(), + ], + [ + 'source' => 'tables', + 'lesson_id' => $tables_based_progress->get_lesson_id(), + 'user_id' => $tables_based_progress->get_user_id(), + 'status' => $tables_based_progress->get_status(), + 'started_at' => $tables_based_progress->get_started_at(), + 'completed_at' => $tables_based_progress->get_completed_at(), + ], + ], + [ 'source', 'lesson_id', 'user_id', 'status', 'started_at', 'completed_at' ] + ); + } + } + } +} From d2f1f215421d02593c71e8587f91443fb4c8a0b0 Mon Sep 17 00:00:00 2001 From: Miroslav Mitev Date: Mon, 25 Sep 2023 18:08:40 +0300 Subject: [PATCH 04/18] Refactor the validate progress command --- ...ss-sensei-db-validate-progress-command.php | 284 +++++++++++++----- 1 file changed, 202 insertions(+), 82 deletions(-) diff --git a/includes/cli/class-sensei-db-validate-progress-command.php b/includes/cli/class-sensei-db-validate-progress-command.php index d47803aa52..8e23e3f892 100644 --- a/includes/cli/class-sensei-db-validate-progress-command.php +++ b/includes/cli/class-sensei-db-validate-progress-command.php @@ -6,10 +6,15 @@ */ use Sensei\Internal\Migration\Migration_Job_Scheduler; +use Sensei\Internal\Student_Progress\Course_Progress\Models\Course_Progress_Interface; use Sensei\Internal\Student_Progress\Course_Progress\Repositories\Comments_Based_Course_Progress_Repository; use Sensei\Internal\Student_Progress\Course_Progress\Repositories\Tables_Based_Course_Progress_Repository; +use Sensei\Internal\Student_Progress\Lesson_Progress\Models\Lesson_Progress_Interface; use Sensei\Internal\Student_Progress\Lesson_Progress\Repositories\Comments_Based_Lesson_Progress_Repository; use Sensei\Internal\Student_Progress\Lesson_Progress\Repositories\Tables_Based_Lesson_Progress_Repository; +use Sensei\Internal\Student_Progress\Quiz_Progress\Models\Quiz_Progress_Interface; +use Sensei\Internal\Student_Progress\Quiz_Progress\Repositories\Comments_Based_Quiz_Progress_Repository; +use Sensei\Internal\Student_Progress\Quiz_Progress\Repositories\Tables_Based_Quiz_Progress_Repository; defined( 'ABSPATH' ) || exit; @@ -19,6 +24,13 @@ * @since $$next_version$$ */ class Sensei_DB_Validate_Progress_Command { + /** + * Whether the progress data is valid. + * + * @var bool + */ + private $is_valid = true; + /** * Seed the database. * @@ -30,18 +42,73 @@ public function __invoke( array $args = [], array $assoc_args = [] ) { WP_CLI::error( 'The progress migration is not complete. Please run the progress migration first.' ); } - $courses = Sensei_Course::get_all_courses(); + foreach ( $this->get_course_ids() as $course_id ) { + $this->validate_course_progress( $course_id ); + } + + foreach ( $this->get_lesson_ids() as $lesson_id ) { + $this->validate_lesson_progress( $lesson_id ); + } - foreach ( $courses as $course ) { - $this->validate_course( $course ); + foreach ( $this->get_quiz_ids() as $quiz_id ) { + $this->validate_quiz_progress( $quiz_id ); + } - $lessons = Sensei()->course->course_lessons( $course->ID, 'any' ); - foreach ( $lessons as $lesson ) { - $this->validate_lesson( $lesson ); - } + if ( $this->is_valid ) { + WP_CLI::success( 'Progress data is valid.' ); + } else { + WP_CLI::error( 'Progress data mismatch detected.' ); } } + /** + * Get the course IDs. + * + * @return int[] + */ + private function get_course_ids(): array { + return get_posts( + [ + 'post_type' => 'course', + 'post_status' => 'any', + 'posts_per_page' => -1, + 'fields' => 'ids', + ] + ); + } + + /** + * Get the lesson IDs. + * + * @return int[] + */ + private function get_lesson_ids(): array { + return get_posts( + [ + 'post_type' => 'lesson', + 'post_status' => 'any', + 'posts_per_page' => -1, + 'fields' => 'ids', + ] + ); + } + + /** + * Get the quiz IDs. + * + * @return int[] + */ + private function get_quiz_ids(): array { + return get_posts( + [ + 'post_type' => 'quiz', + 'post_status' => 'any', + 'posts_per_page' => -1, + 'fields' => 'ids', + ] + ); + } + /** * Check if the progress migration is complete. * @@ -54,11 +121,9 @@ private function is_progress_migration_complete(): bool { /** * Validate the course progress. * - * @param WP_Post $course Course post object. + * @param int $course_id Course post ID. */ - private function validate_course( WP_Post $course ): void { - WP_CLI::log( "Validating course {$course->post_title} ({$course->ID})..." ); - + private function validate_course_progress( int $course_id ): void { global $wpdb; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $user_ids = $wpdb->get_col( @@ -66,7 +131,7 @@ private function validate_course( WP_Post $course ): void { "SELECT user_id FROM {$wpdb->comments} WHERE comment_type = 'sensei_course_status' AND comment_post_ID = %d", - $course->ID + $course_id ) ); if ( ! $user_ids ) { @@ -77,8 +142,8 @@ private function validate_course( WP_Post $course ): void { $tables_based_repository = new Tables_Based_Course_Progress_Repository( $wpdb ); foreach ( $user_ids as $user_id ) { - $comments_based_progress = $comments_based_repository->get( $course->ID, $user_id ); - $tables_based_progress = $tables_based_repository->get( $course->ID, $user_id ); + $comments_based_progress = $comments_based_repository->get( $course_id, $user_id ); + $tables_based_progress = $tables_based_repository->get( $course_id, $user_id ); if ( ! $comments_based_progress ) { WP_CLI::warning( 'Comments based progress not found.' ); @@ -90,48 +155,16 @@ private function validate_course( WP_Post $course ): void { continue; } - if ( - $comments_based_progress->get_course_id() !== $tables_based_progress->get_course_id() - || $comments_based_progress->get_user_id() !== $tables_based_progress->get_user_id() - || $comments_based_progress->get_status() !== $tables_based_progress->get_status() - || $comments_based_progress->get_started_at() != $tables_based_progress->get_started_at() // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison - || $comments_based_progress->get_completed_at() != $tables_based_progress->get_completed_at() // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison - ) { - WP_CLI::warning( 'Data mismatch between comments and tables based progress.' ); - WP_CLI\Utils\format_items( - 'table', - [ - [ - 'source' => 'comments', - 'course_id' => $comments_based_progress->get_course_id(), - 'user_id' => $comments_based_progress->get_user_id(), - 'status' => $comments_based_progress->get_status(), - 'started_at' => $comments_based_progress->get_started_at(), - 'completed_at' => $comments_based_progress->get_completed_at(), - ], - [ - 'source' => 'tables', - 'course_id' => $tables_based_progress->get_course_id(), - 'user_id' => $tables_based_progress->get_user_id(), - 'status' => $tables_based_progress->get_status(), - 'started_at' => $tables_based_progress->get_started_at(), - 'completed_at' => $tables_based_progress->get_completed_at(), - ], - ], - [ 'source', 'course_id', 'user_id', 'status', 'started_at', 'completed_at' ] - ); - } + $this->compare_progress( $comments_based_progress, $tables_based_progress ); } } /** * Validate the lesson progress. * - * @param WP_Post $lesson Lesson post object. + * @param int $lesson_id Lesson post ID. */ - private function validate_lesson( WP_Post $lesson ): void { - WP_CLI::log( "Validating lesson {$lesson->post_title} ({$lesson->ID})..." ); - + private function validate_lesson_progress( int $lesson_id ): void { global $wpdb; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $user_ids = $wpdb->get_col( @@ -139,7 +172,7 @@ private function validate_lesson( WP_Post $lesson ): void { "SELECT user_id FROM {$wpdb->comments} WHERE comment_type = 'sensei_lesson_status' AND comment_post_ID = %d", - $lesson->ID + $lesson_id ) ); if ( ! $user_ids ) { @@ -150,8 +183,8 @@ private function validate_lesson( WP_Post $lesson ): void { $tables_based_repository = new Tables_Based_Lesson_Progress_Repository( $wpdb ); foreach ( $user_ids as $user_id ) { - $comments_based_progress = $comments_based_repository->get( $lesson->ID, $user_id ); - $tables_based_progress = $tables_based_repository->get( $lesson->ID, $user_id ); + $comments_based_progress = $comments_based_repository->get( $lesson_id, $user_id ); + $tables_based_progress = $tables_based_repository->get( $lesson_id, $user_id ); if ( ! $comments_based_progress ) { WP_CLI::warning( 'Comments based progress not found.' ); @@ -163,37 +196,124 @@ private function validate_lesson( WP_Post $lesson ): void { continue; } - if ( - $comments_based_progress->get_lesson_id() !== $tables_based_progress->get_lesson_id() - || $comments_based_progress->get_user_id() !== $tables_based_progress->get_user_id() - || $comments_based_progress->get_status() !== $tables_based_progress->get_status() - || $comments_based_progress->get_started_at() != $tables_based_progress->get_started_at() // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison - || $comments_based_progress->get_completed_at() != $tables_based_progress->get_completed_at() // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison - ) { - WP_CLI::warning( 'Data mismatch between comments and tables based progress.' ); - WP_CLI\Utils\format_items( - 'table', - [ - [ - 'source' => 'comments', - 'lesson_id' => $comments_based_progress->get_lesson_id(), - 'user_id' => $comments_based_progress->get_user_id(), - 'status' => $comments_based_progress->get_status(), - 'started_at' => $comments_based_progress->get_started_at(), - 'completed_at' => $comments_based_progress->get_completed_at(), - ], - [ - 'source' => 'tables', - 'lesson_id' => $tables_based_progress->get_lesson_id(), - 'user_id' => $tables_based_progress->get_user_id(), - 'status' => $tables_based_progress->get_status(), - 'started_at' => $tables_based_progress->get_started_at(), - 'completed_at' => $tables_based_progress->get_completed_at(), - ], - ], - [ 'source', 'lesson_id', 'user_id', 'status', 'started_at', 'completed_at' ] - ); + $this->compare_progress( $comments_based_progress, $tables_based_progress ); + } + } + + /** + * Validate the quiz progress. + * + * @param int $quiz_id Quiz post ID. + */ + private function validate_quiz_progress( int $quiz_id ): void { + $lesson_id = Sensei()->quiz->get_lesson_id( $quiz_id ); + + global $wpdb; + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching + $user_ids = $wpdb->get_col( + $wpdb->prepare( + "SELECT user_id FROM {$wpdb->comments} + WHERE comment_type = 'sensei_lesson_status' + AND comment_post_ID = %d", + $lesson_id + ) + ); + if ( ! $user_ids ) { + return; + } + + $comments_based_repository = new Comments_Based_Quiz_Progress_Repository(); + $tables_based_repository = new Tables_Based_Quiz_Progress_Repository( $wpdb ); + + foreach ( $user_ids as $user_id ) { + $comments_based_progress = $comments_based_repository->get( $quiz_id, $user_id ); + $tables_based_progress = $tables_based_repository->get( $quiz_id, $user_id ); + + if ( ! $comments_based_progress ) { + WP_CLI::warning( 'Comments based progress not found.' ); + continue; } + + if ( ! $tables_based_progress ) { + WP_CLI::warning( 'Tables based progress not found.' ); + continue; + } + + $this->compare_progress( $comments_based_progress, $tables_based_progress ); } } + + /** + * Compare the comments and tables based progress. + * + * @param object $comments_based_progress Comments based progress. + * @param object $tables_based_progress Tables based progress. + */ + private function compare_progress( object $comments_based_progress, object $tables_based_progress ): void { + // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison + if ( $this->get_progress_data( $comments_based_progress ) != $this->get_progress_data( $tables_based_progress ) ) { + $this->is_valid = false; + $this->log_progress_mismatch( $comments_based_progress, $tables_based_progress ); + } + } + + /** + * Get the progress data. + * + * @param object $progress Progress. + * + * @return array + * @throws \InvalidArgumentException When invalid progress type is provided. + */ + private function get_progress_data( object $progress ): array { + if ( $progress instanceof Course_Progress_Interface ) { + $type = 'course'; + $post_id = $progress->get_course_id(); + } elseif ( $progress instanceof Lesson_Progress_Interface ) { + $type = 'lesson'; + $post_id = $progress->get_lesson_id(); + } elseif ( $progress instanceof Quiz_Progress_Interface ) { + $type = 'quiz'; + $post_id = $progress->get_quiz_id(); + } else { + throw new \InvalidArgumentException( 'Invalid progress type.' ); + } + + return [ + 'type' => $type, + 'post_id' => $post_id, + 'user_id' => $progress->get_user_id(), + 'status' => $progress->get_status(), + 'started_at' => $progress->get_started_at(), + 'completed_at' => $progress->get_completed_at(), + ]; + } + + /** + * Log a progress mismatch. + * + * @param object $comments_based_progress Comments based progress. + * @param object $tables_based_progress Tables based progress. + */ + private function log_progress_mismatch( object $comments_based_progress, object $tables_based_progress ): void { + WP_CLI::warning( 'Data mismatch between comments and tables based progress.' ); + WP_CLI\Utils\format_items( + 'table', + [ + array_merge( + [ + 'source' => 'comments', + ], + $this->get_progress_data( $comments_based_progress ) + ), + array_merge( + [ + 'source' => 'tables', + ], + $this->get_progress_data( $tables_based_progress ) + ), + ], + [ 'source', 'type', 'post_id', 'user_id', 'status', 'started_at', 'completed_at' ] + ); + } } From a05f4d2b3131c2d654215551563a57d0efc7e58b Mon Sep 17 00:00:00 2001 From: Miroslav Mitev Date: Mon, 16 Oct 2023 17:00:23 +0300 Subject: [PATCH 05/18] Fix quiz progress migration not working in come cases When multiple users have quiz progress on the same quiz, only the last one is migrated. --- .../class-student-progress-migration.php | 8 ++-- .../test-class-student-progress-migration.php | 44 +++++++++++++++---- 2 files changed, 40 insertions(+), 12 deletions(-) diff --git a/includes/internal/migration/migrations/class-student-progress-migration.php b/includes/internal/migration/migrations/class-student-progress-migration.php index b038654cde..7af3ee4b8d 100644 --- a/includes/internal/migration/migrations/class-student-progress-migration.php +++ b/includes/internal/migration/migrations/class-student-progress-migration.php @@ -122,7 +122,7 @@ private function get_comments_and_meta( int $after_comment_id, bool $dry_run ): // At the moment we don't care about post meta for course progress. if ( 'sensei_lesson_status' === $progress_comment->comment_type ) { // Map the post ID to the comment ID. Is used later to map post meta to the comment ID. - $post_ids[ $progress_comment->comment_post_ID ] = $progress_comment->comment_ID; + $post_ids[ $progress_comment->comment_post_ID ][] = $progress_comment->comment_ID; } } @@ -188,8 +188,10 @@ private function get_comments_and_meta( int $after_comment_id, bool $dry_run ): $mapped_meta[ $comment_id ]['status'] = $comment_status; } } else { - // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key - $mapped_meta[ $comment_id ][ $meta->meta_key ] = $meta->meta_value; + foreach ( $post_ids[ $meta->post_id ] as $comment_id ) { + // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key + $mapped_meta[ $comment_id ][ $meta->meta_key ] = $meta->meta_value; + } } } diff --git a/tests/unit-tests/internal/migration/migrations/test-class-student-progress-migration.php b/tests/unit-tests/internal/migration/migrations/test-class-student-progress-migration.php index 821b153bd4..e6232145a7 100644 --- a/tests/unit-tests/internal/migration/migrations/test-class-student-progress-migration.php +++ b/tests/unit-tests/internal/migration/migrations/test-class-student-progress-migration.php @@ -4,6 +4,7 @@ use Sensei\Internal\Migration\Migrations\Student_Progress_Migration; use Sensei_Factory; +use Sensei_Utils; /** * Class Student_Progress_Migration_Test @@ -61,8 +62,8 @@ public function testRun_CommentsExist_ReturnsMatchingNumberOfInserts(): void { ) ); - \Sensei_Utils::start_user_on_course( 1, $course_id ); - \Sensei_Utils::user_start_lesson( 1, $lesson_id, true ); + Sensei_Utils::start_user_on_course( 1, $course_id ); + Sensei_Utils::user_start_lesson( 1, $lesson_id, true ); update_option( 'sensei_migrated_progress_last_comment_id', 0 ); @@ -94,11 +95,18 @@ public function testRun_CommentsExist_CreatesProgressMatchingEntriesInCustomTabl ), ) ); + $user_1 = $this->factory->user->create(); + $user_2 = $this->factory->user->create(); + update_post_meta( $quiz_id, '_quiz_lesson', $lesson_id ); - \Sensei_Utils::start_user_on_course( 1, $course_id ); - \Sensei_Utils::user_start_lesson( 1, $lesson_id, true ); - \Sensei_Utils::user_passed_quiz( $quiz_id, 1 ); + Sensei_Utils::start_user_on_course( $user_1, $course_id ); + Sensei_Utils::user_start_lesson( $user_1, $lesson_id, true ); + Sensei_Utils::user_passed_quiz( $quiz_id, $user_1 ); + + Sensei_Utils::start_user_on_course( $user_2, $course_id ); + Sensei_Utils::user_start_lesson( $user_2, $lesson_id, true ); + Sensei_Utils::user_passed_quiz( $quiz_id, $user_2 ); update_option( 'sensei_migrated_progress_last_comment_id', 0 ); @@ -109,19 +117,37 @@ public function testRun_CommentsExist_CreatesProgressMatchingEntriesInCustomTabl $actual_rows = $this->get_table_based_progress(); $expected = array( array( - 'user_id' => 1, + 'user_id' => $user_1, + 'post_id' => $course_id, + 'status' => 'in-progress', + 'type' => 'course', + ), + array( + 'user_id' => $user_1, + 'post_id' => $lesson_id, + 'status' => 'complete', + 'type' => 'lesson', + ), + array( + 'user_id' => $user_1, + 'post_id' => $quiz_id, + 'status' => 'passed', + 'type' => 'quiz', + ), + array( + 'user_id' => $user_2, 'post_id' => $course_id, 'status' => 'in-progress', 'type' => 'course', ), array( - 'user_id' => 1, + 'user_id' => $user_2, 'post_id' => $lesson_id, 'status' => 'complete', 'type' => 'lesson', ), array( - 'user_id' => 1, + 'user_id' => $user_2, 'post_id' => $quiz_id, 'status' => 'passed', 'type' => 'quiz', @@ -133,7 +159,7 @@ public function testRun_CommentsExist_CreatesProgressMatchingEntriesInCustomTabl private function get_table_based_progress(): array { global $wpdb; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching - $rows = $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}sensei_lms_progress" ); + $rows = $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}sensei_lms_progress ORDER BY user_id" ); $result = array(); foreach ( $rows as $row ) { From 7feb991789bb4c3da8b719f487e03ec96e0201e4 Mon Sep 17 00:00:00 2001 From: Miroslav Mitev Date: Mon, 16 Oct 2023 18:01:47 +0300 Subject: [PATCH 06/18] Improve the log messages of the progress validation command --- ...ss-sensei-db-validate-progress-command.php | 60 +++++++++++++++++-- 1 file changed, 54 insertions(+), 6 deletions(-) diff --git a/includes/cli/class-sensei-db-validate-progress-command.php b/includes/cli/class-sensei-db-validate-progress-command.php index 8e23e3f892..04c79c016f 100644 --- a/includes/cli/class-sensei-db-validate-progress-command.php +++ b/includes/cli/class-sensei-db-validate-progress-command.php @@ -146,12 +146,28 @@ private function validate_course_progress( int $course_id ): void { $tables_based_progress = $tables_based_repository->get( $course_id, $user_id ); if ( ! $comments_based_progress ) { - WP_CLI::warning( 'Comments based progress not found.' ); + $this->is_valid = false; + WP_CLI::warning( + 'Course comments based progress not found for: ' . wp_json_encode( + [ + 'course_id' => $course_id, + 'user_id' => $user_id, + ] + ) + ); continue; } if ( ! $tables_based_progress ) { - WP_CLI::warning( 'Tables based progress not found.' ); + $this->is_valid = false; + WP_CLI::warning( + 'Course tables based progress not found for: ' . wp_json_encode( + [ + 'course_id' => $course_id, + 'user_id' => $user_id, + ] + ) + ); continue; } @@ -187,12 +203,28 @@ private function validate_lesson_progress( int $lesson_id ): void { $tables_based_progress = $tables_based_repository->get( $lesson_id, $user_id ); if ( ! $comments_based_progress ) { - WP_CLI::warning( 'Comments based progress not found.' ); + $this->is_valid = false; + WP_CLI::warning( + 'Lesson comments based progress not found for: ' . wp_json_encode( + [ + 'lesson_id' => $lesson_id, + 'user_id' => $user_id, + ] + ) + ); continue; } if ( ! $tables_based_progress ) { - WP_CLI::warning( 'Tables based progress not found.' ); + $this->is_valid = false; + WP_CLI::warning( + 'Lesson tables based progress not found for: ' . wp_json_encode( + [ + 'lesson_id' => $lesson_id, + 'user_id' => $user_id, + ] + ) + ); continue; } @@ -230,12 +262,28 @@ private function validate_quiz_progress( int $quiz_id ): void { $tables_based_progress = $tables_based_repository->get( $quiz_id, $user_id ); if ( ! $comments_based_progress ) { - WP_CLI::warning( 'Comments based progress not found.' ); + $this->is_valid = false; + WP_CLI::warning( + 'Quiz comments based progress not found for: ' . wp_json_encode( + [ + 'quiz_id' => $quiz_id, + 'user_id' => $user_id, + ] + ) + ); continue; } if ( ! $tables_based_progress ) { - WP_CLI::warning( 'Tables based progress not found.' ); + $this->is_valid = false; + WP_CLI::warning( + 'Quiz tables based progress not found for: ' . wp_json_encode( + [ + 'quiz_id' => $quiz_id, + 'user_id' => $user_id, + ] + ) + ); continue; } From 18f03c63b6087bca9d05ff487a8da94c4ec2b2ca Mon Sep 17 00:00:00 2001 From: Miroslav Mitev Date: Tue, 17 Oct 2023 21:47:02 +0300 Subject: [PATCH 07/18] Move the progress validation logic to its own class --- ...ss-sensei-db-validate-progress-command.php | 351 ++-------------- .../validations/class-progress-validation.php | 386 ++++++++++++++++++ .../validations/class-validation-error.php | 86 ++++ 3 files changed, 496 insertions(+), 327 deletions(-) create mode 100644 includes/internal/migration/validations/class-progress-validation.php create mode 100644 includes/internal/migration/validations/class-validation-error.php diff --git a/includes/cli/class-sensei-db-validate-progress-command.php b/includes/cli/class-sensei-db-validate-progress-command.php index 04c79c016f..4e7a079b57 100644 --- a/includes/cli/class-sensei-db-validate-progress-command.php +++ b/includes/cli/class-sensei-db-validate-progress-command.php @@ -5,363 +5,60 @@ * @package sensei */ -use Sensei\Internal\Migration\Migration_Job_Scheduler; -use Sensei\Internal\Student_Progress\Course_Progress\Models\Course_Progress_Interface; -use Sensei\Internal\Student_Progress\Course_Progress\Repositories\Comments_Based_Course_Progress_Repository; -use Sensei\Internal\Student_Progress\Course_Progress\Repositories\Tables_Based_Course_Progress_Repository; -use Sensei\Internal\Student_Progress\Lesson_Progress\Models\Lesson_Progress_Interface; -use Sensei\Internal\Student_Progress\Lesson_Progress\Repositories\Comments_Based_Lesson_Progress_Repository; -use Sensei\Internal\Student_Progress\Lesson_Progress\Repositories\Tables_Based_Lesson_Progress_Repository; -use Sensei\Internal\Student_Progress\Quiz_Progress\Models\Quiz_Progress_Interface; -use Sensei\Internal\Student_Progress\Quiz_Progress\Repositories\Comments_Based_Quiz_Progress_Repository; -use Sensei\Internal\Student_Progress\Quiz_Progress\Repositories\Tables_Based_Quiz_Progress_Repository; +use Sensei\Internal\Migration\Validations\Progress_Validation; defined( 'ABSPATH' ) || exit; /** * WP-CLI command that validates the progress data. * - * @since $$next_version$$ + * @since $$next-version$$ */ class Sensei_DB_Validate_Progress_Command { - /** - * Whether the progress data is valid. - * - * @var bool - */ - private $is_valid = true; - /** * Seed the database. * + * @since $$next-version$$ + * * @param array $args Command arguments. * @param array $assoc_args Command arguments with names. */ public function __invoke( array $args = [], array $assoc_args = [] ) { - if ( ! $this->is_progress_migration_complete() ) { - WP_CLI::error( 'The progress migration is not complete. Please run the progress migration first.' ); - } + $progress_validation = new Progress_Validation(); - foreach ( $this->get_course_ids() as $course_id ) { - $this->validate_course_progress( $course_id ); - } - - foreach ( $this->get_lesson_ids() as $lesson_id ) { - $this->validate_lesson_progress( $lesson_id ); - } + $progress_validation->run(); - foreach ( $this->get_quiz_ids() as $quiz_id ) { - $this->validate_quiz_progress( $quiz_id ); - } - - if ( $this->is_valid ) { + if ( ! $progress_validation->has_errors() ) { WP_CLI::success( 'Progress data is valid.' ); - } else { - WP_CLI::error( 'Progress data mismatch detected.' ); - } - } - - /** - * Get the course IDs. - * - * @return int[] - */ - private function get_course_ids(): array { - return get_posts( - [ - 'post_type' => 'course', - 'post_status' => 'any', - 'posts_per_page' => -1, - 'fields' => 'ids', - ] - ); - } - - /** - * Get the lesson IDs. - * - * @return int[] - */ - private function get_lesson_ids(): array { - return get_posts( - [ - 'post_type' => 'lesson', - 'post_status' => 'any', - 'posts_per_page' => -1, - 'fields' => 'ids', - ] - ); - } - - /** - * Get the quiz IDs. - * - * @return int[] - */ - private function get_quiz_ids(): array { - return get_posts( - [ - 'post_type' => 'quiz', - 'post_status' => 'any', - 'posts_per_page' => -1, - 'fields' => 'ids', - ] - ); - } - - /** - * Check if the progress migration is complete. - * - * @return bool - */ - private function is_progress_migration_complete(): bool { - return (bool) get_option( Migration_Job_Scheduler::COMPLETED_OPTION_NAME, false ); - } - - /** - * Validate the course progress. - * - * @param int $course_id Course post ID. - */ - private function validate_course_progress( int $course_id ): void { - global $wpdb; - // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching - $user_ids = $wpdb->get_col( - $wpdb->prepare( - "SELECT user_id FROM {$wpdb->comments} - WHERE comment_type = 'sensei_course_status' - AND comment_post_ID = %d", - $course_id - ) - ); - if ( ! $user_ids ) { return; } - $comments_based_repository = new Comments_Based_Course_Progress_Repository(); - $tables_based_repository = new Tables_Based_Course_Progress_Repository( $wpdb ); + $this->output_validation_errors( $progress_validation ); - foreach ( $user_ids as $user_id ) { - $comments_based_progress = $comments_based_repository->get( $course_id, $user_id ); - $tables_based_progress = $tables_based_repository->get( $course_id, $user_id ); - - if ( ! $comments_based_progress ) { - $this->is_valid = false; - WP_CLI::warning( - 'Course comments based progress not found for: ' . wp_json_encode( - [ - 'course_id' => $course_id, - 'user_id' => $user_id, - ] - ) - ); - continue; - } - - if ( ! $tables_based_progress ) { - $this->is_valid = false; - WP_CLI::warning( - 'Course tables based progress not found for: ' . wp_json_encode( - [ - 'course_id' => $course_id, - 'user_id' => $user_id, - ] - ) - ); - continue; - } - - $this->compare_progress( $comments_based_progress, $tables_based_progress ); - } + WP_CLI::error( 'Progress data is not valid.' ); } /** - * Validate the lesson progress. + * Output the validation errors. * - * @param int $lesson_id Lesson post ID. - */ - private function validate_lesson_progress( int $lesson_id ): void { - global $wpdb; - // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching - $user_ids = $wpdb->get_col( - $wpdb->prepare( - "SELECT user_id FROM {$wpdb->comments} - WHERE comment_type = 'sensei_lesson_status' - AND comment_post_ID = %d", - $lesson_id - ) - ); - if ( ! $user_ids ) { - return; - } - - $comments_based_repository = new Comments_Based_Lesson_Progress_Repository(); - $tables_based_repository = new Tables_Based_Lesson_Progress_Repository( $wpdb ); - - foreach ( $user_ids as $user_id ) { - $comments_based_progress = $comments_based_repository->get( $lesson_id, $user_id ); - $tables_based_progress = $tables_based_repository->get( $lesson_id, $user_id ); - - if ( ! $comments_based_progress ) { - $this->is_valid = false; - WP_CLI::warning( - 'Lesson comments based progress not found for: ' . wp_json_encode( - [ - 'lesson_id' => $lesson_id, - 'user_id' => $user_id, - ] - ) - ); - continue; - } - - if ( ! $tables_based_progress ) { - $this->is_valid = false; - WP_CLI::warning( - 'Lesson tables based progress not found for: ' . wp_json_encode( - [ - 'lesson_id' => $lesson_id, - 'user_id' => $user_id, - ] - ) - ); - continue; - } - - $this->compare_progress( $comments_based_progress, $tables_based_progress ); - } - } - - /** - * Validate the quiz progress. + * @since $$next-version$$ * - * @param int $quiz_id Quiz post ID. + * @param Progress_Validation $progress_validation Progress validation. */ - private function validate_quiz_progress( int $quiz_id ): void { - $lesson_id = Sensei()->quiz->get_lesson_id( $quiz_id ); - - global $wpdb; - // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching - $user_ids = $wpdb->get_col( - $wpdb->prepare( - "SELECT user_id FROM {$wpdb->comments} - WHERE comment_type = 'sensei_lesson_status' - AND comment_post_ID = %d", - $lesson_id - ) - ); - if ( ! $user_ids ) { - return; - } - - $comments_based_repository = new Comments_Based_Quiz_Progress_Repository(); - $tables_based_repository = new Tables_Based_Quiz_Progress_Repository( $wpdb ); - - foreach ( $user_ids as $user_id ) { - $comments_based_progress = $comments_based_repository->get( $quiz_id, $user_id ); - $tables_based_progress = $tables_based_repository->get( $quiz_id, $user_id ); - - if ( ! $comments_based_progress ) { - $this->is_valid = false; - WP_CLI::warning( - 'Quiz comments based progress not found for: ' . wp_json_encode( - [ - 'quiz_id' => $quiz_id, - 'user_id' => $user_id, - ] - ) + private function output_validation_errors( Progress_Validation $progress_validation ) { + foreach ( $progress_validation->get_errors() as $error ) { + WP_CLI::warning( $error->get_message() ); + + if ( $error->has_data() ) { + $error_data = $error->get_data(); + $error_data = is_array( $error_data[0] ) ? $error_data : [ $error_data ]; + + WP_CLI\Utils\format_items( + 'table', + $error_data, + array_keys( $error_data[0] ) ); - continue; } - - if ( ! $tables_based_progress ) { - $this->is_valid = false; - WP_CLI::warning( - 'Quiz tables based progress not found for: ' . wp_json_encode( - [ - 'quiz_id' => $quiz_id, - 'user_id' => $user_id, - ] - ) - ); - continue; - } - - $this->compare_progress( $comments_based_progress, $tables_based_progress ); - } - } - - /** - * Compare the comments and tables based progress. - * - * @param object $comments_based_progress Comments based progress. - * @param object $tables_based_progress Tables based progress. - */ - private function compare_progress( object $comments_based_progress, object $tables_based_progress ): void { - // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison - if ( $this->get_progress_data( $comments_based_progress ) != $this->get_progress_data( $tables_based_progress ) ) { - $this->is_valid = false; - $this->log_progress_mismatch( $comments_based_progress, $tables_based_progress ); - } - } - - /** - * Get the progress data. - * - * @param object $progress Progress. - * - * @return array - * @throws \InvalidArgumentException When invalid progress type is provided. - */ - private function get_progress_data( object $progress ): array { - if ( $progress instanceof Course_Progress_Interface ) { - $type = 'course'; - $post_id = $progress->get_course_id(); - } elseif ( $progress instanceof Lesson_Progress_Interface ) { - $type = 'lesson'; - $post_id = $progress->get_lesson_id(); - } elseif ( $progress instanceof Quiz_Progress_Interface ) { - $type = 'quiz'; - $post_id = $progress->get_quiz_id(); - } else { - throw new \InvalidArgumentException( 'Invalid progress type.' ); } - - return [ - 'type' => $type, - 'post_id' => $post_id, - 'user_id' => $progress->get_user_id(), - 'status' => $progress->get_status(), - 'started_at' => $progress->get_started_at(), - 'completed_at' => $progress->get_completed_at(), - ]; - } - - /** - * Log a progress mismatch. - * - * @param object $comments_based_progress Comments based progress. - * @param object $tables_based_progress Tables based progress. - */ - private function log_progress_mismatch( object $comments_based_progress, object $tables_based_progress ): void { - WP_CLI::warning( 'Data mismatch between comments and tables based progress.' ); - WP_CLI\Utils\format_items( - 'table', - [ - array_merge( - [ - 'source' => 'comments', - ], - $this->get_progress_data( $comments_based_progress ) - ), - array_merge( - [ - 'source' => 'tables', - ], - $this->get_progress_data( $tables_based_progress ) - ), - ], - [ 'source', 'type', 'post_id', 'user_id', 'status', 'started_at', 'completed_at' ] - ); } } diff --git a/includes/internal/migration/validations/class-progress-validation.php b/includes/internal/migration/validations/class-progress-validation.php new file mode 100644 index 0000000000..85dcd95737 --- /dev/null +++ b/includes/internal/migration/validations/class-progress-validation.php @@ -0,0 +1,386 @@ +errors = []; + + if ( ! $this->is_progress_migration_complete() ) { + $this->add_error( 'The progress migration is not complete. Please run the progress migration first.' ); + } + + foreach ( $this->get_course_ids() as $course_id ) { + $this->validate_course_progress( $course_id ); + } + + foreach ( $this->get_lesson_ids() as $lesson_id ) { + $this->validate_lesson_progress( $lesson_id ); + } + + foreach ( $this->get_quiz_ids() as $quiz_id ) { + $this->validate_quiz_progress( $quiz_id ); + } + } + + /** + * Check if there are validation errors. + * + * @internal + * + * @since $$next-version$$ + * + * @return bool + */ + public function has_errors(): bool { + return (bool) $this->errors; + } + + /** + * Get the validation errors. + * + * @internal + * + * @since $$next-version$$ + * + * @return Validation_Error[] + */ + public function get_errors(): array { + return $this->errors; + } + + /** + * Add a validation error. + * + * @param string $message Error message. + * @param array $data Error data. + */ + private function add_error( string $message, array $data = [] ): void { + $this->errors[] = new Validation_Error( $message, $data ); + } + + /** + * Get the course IDs. + * + * @return int[] + */ + private function get_course_ids(): array { + return get_posts( + [ + 'post_type' => 'course', + 'post_status' => 'any', + 'posts_per_page' => -1, + 'fields' => 'ids', + ] + ); + } + + /** + * Get the lesson IDs. + * + * @return int[] + */ + private function get_lesson_ids(): array { + return get_posts( + [ + 'post_type' => 'lesson', + 'post_status' => 'any', + 'posts_per_page' => -1, + 'fields' => 'ids', + ] + ); + } + + /** + * Get the quiz IDs. + * + * @return int[] + */ + private function get_quiz_ids(): array { + return get_posts( + [ + 'post_type' => 'quiz', + 'post_status' => 'any', + 'posts_per_page' => -1, + 'fields' => 'ids', + ] + ); + } + + /** + * Check if the progress migration is complete. + * + * @return bool + */ + private function is_progress_migration_complete(): bool { + return (bool) get_option( Migration_Job_Scheduler::COMPLETED_OPTION_NAME, false ); + } + + /** + * Validate the course progress. + * + * @param int $course_id Course post ID. + */ + private function validate_course_progress( int $course_id ): void { + global $wpdb; + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching + $user_ids = $wpdb->get_col( + $wpdb->prepare( + "SELECT user_id FROM {$wpdb->comments} + WHERE comment_type = 'sensei_course_status' + AND comment_post_ID = %d", + $course_id + ) + ); + if ( ! $user_ids ) { + return; + } + + $comments_based_repository = new Comments_Based_Course_Progress_Repository(); + $tables_based_repository = new Tables_Based_Course_Progress_Repository( $wpdb ); + + foreach ( $user_ids as $user_id ) { + $comments_based_progress = $comments_based_repository->get( $course_id, $user_id ); + $tables_based_progress = $tables_based_repository->get( $course_id, $user_id ); + + if ( ! $comments_based_progress ) { + $this->add_error( + 'Course comments based progress not found.', + [ + 'course_id' => $course_id, + 'user_id' => $user_id, + ] + ); + continue; + } + + if ( ! $tables_based_progress ) { + $this->add_error( + 'Course tables based progress not found.', + [ + 'course_id' => $course_id, + 'user_id' => $user_id, + ] + ); + continue; + } + + $this->compare_progress( $comments_based_progress, $tables_based_progress ); + } + } + + /** + * Validate the lesson progress. + * + * @param int $lesson_id Lesson post ID. + */ + private function validate_lesson_progress( int $lesson_id ): void { + global $wpdb; + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching + $user_ids = $wpdb->get_col( + $wpdb->prepare( + "SELECT user_id FROM {$wpdb->comments} + WHERE comment_type = 'sensei_lesson_status' + AND comment_post_ID = %d", + $lesson_id + ) + ); + if ( ! $user_ids ) { + return; + } + + $comments_based_repository = new Comments_Based_Lesson_Progress_Repository(); + $tables_based_repository = new Tables_Based_Lesson_Progress_Repository( $wpdb ); + + foreach ( $user_ids as $user_id ) { + $comments_based_progress = $comments_based_repository->get( $lesson_id, $user_id ); + $tables_based_progress = $tables_based_repository->get( $lesson_id, $user_id ); + + if ( ! $comments_based_progress ) { + $this->add_error( + 'Lesson comments based progress not found.', + [ + 'lesson_id' => $lesson_id, + 'user_id' => $user_id, + ] + ); + continue; + } + + if ( ! $tables_based_progress ) { + $this->add_error( + 'Lesson tables based progress not found.', + [ + 'lesson_id' => $lesson_id, + 'user_id' => $user_id, + ] + ); + continue; + } + + $this->compare_progress( $comments_based_progress, $tables_based_progress ); + } + } + + /** + * Validate the quiz progress. + * + * @param int $quiz_id Quiz post ID. + */ + private function validate_quiz_progress( int $quiz_id ): void { + $lesson_id = Sensei()->quiz->get_lesson_id( $quiz_id ); + + global $wpdb; + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching + $user_ids = $wpdb->get_col( + $wpdb->prepare( + "SELECT user_id FROM {$wpdb->comments} + WHERE comment_type = 'sensei_lesson_status' + AND comment_post_ID = %d", + $lesson_id + ) + ); + if ( ! $user_ids ) { + return; + } + + $comments_based_repository = new Comments_Based_Quiz_Progress_Repository(); + $tables_based_repository = new Tables_Based_Quiz_Progress_Repository( $wpdb ); + + foreach ( $user_ids as $user_id ) { + $comments_based_progress = $comments_based_repository->get( $quiz_id, $user_id ); + $tables_based_progress = $tables_based_repository->get( $quiz_id, $user_id ); + + if ( ! $comments_based_progress ) { + $this->add_error( + 'Quiz comments based progress not found.', + [ + 'quiz_id' => $quiz_id, + 'user_id' => $user_id, + ] + ); + continue; + } + + if ( ! $tables_based_progress ) { + $this->add_error( + 'Quiz tables based progress not found.', + [ + 'quiz_id' => $quiz_id, + 'user_id' => $user_id, + ] + ); + continue; + } + + $this->compare_progress( $comments_based_progress, $tables_based_progress ); + } + } + + /** + * Compare the comments and tables based progress. + * + * @param object $comments_based_progress Comments based progress. + * @param object $tables_based_progress Tables based progress. + */ + private function compare_progress( object $comments_based_progress, object $tables_based_progress ): void { + // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison -- Intended. + if ( $this->get_progress_data( $comments_based_progress ) != $this->get_progress_data( $tables_based_progress ) ) { + $this->add_mismatch_error( $comments_based_progress, $tables_based_progress ); + } + } + + /** + * Get the progress data. + * + * @param object $progress Progress. + * + * @return array + * @throws \InvalidArgumentException When invalid progress type is provided. + */ + private function get_progress_data( object $progress ): array { + if ( $progress instanceof Course_Progress_Interface ) { + $type = 'course'; + $post_id = $progress->get_course_id(); + } elseif ( $progress instanceof Lesson_Progress_Interface ) { + $type = 'lesson'; + $post_id = $progress->get_lesson_id(); + } elseif ( $progress instanceof Quiz_Progress_Interface ) { + $type = 'quiz'; + $post_id = $progress->get_quiz_id(); + } else { + throw new \InvalidArgumentException( 'Invalid progress type.' ); + } + + return [ + 'type' => $type, + 'post_id' => $post_id, + 'user_id' => $progress->get_user_id(), + 'status' => $progress->get_status(), + 'started_at' => $progress->get_started_at(), + 'completed_at' => $progress->get_completed_at(), + ]; + } + + /** + * Log a progress mismatch. + * + * @param object $comments_based_progress Comments based progress. + * @param object $tables_based_progress Tables based progress. + */ + private function add_mismatch_error( object $comments_based_progress, object $tables_based_progress ): void { + $this->add_error( + 'Data mismatch between comments and tables based progress.', + [ + array_merge( + [ + 'source' => 'comments', + ], + $this->get_progress_data( $comments_based_progress ) + ), + array_merge( + [ + 'source' => 'tables', + ], + $this->get_progress_data( $tables_based_progress ) + ), + ] + ); + } +} diff --git a/includes/internal/migration/validations/class-validation-error.php b/includes/internal/migration/validations/class-validation-error.php new file mode 100644 index 0000000000..e8021d85cc --- /dev/null +++ b/includes/internal/migration/validations/class-validation-error.php @@ -0,0 +1,86 @@ +message = $message; + $this->data = $data; + } + + /** + * Get the error message. + * + * @internal + * + * @since $$next-version$$ + * + * @return string + */ + public function get_message(): string { + return $this->message; + } + + /** + * Get the error data. + * + * @internal + * + * @since $$next-version$$ + * + * @return array + */ + public function get_data(): array { + return $this->data; + } + + /** + * Check if there is error data. + * + * @internal + * + * @since $$next-version$$ + * + * @return bool + */ + public function has_data(): bool { + return (bool) $this->data; + } +} From 505e91e2ebb53583278087c3afdca22eba9020f6 Mon Sep 17 00:00:00 2001 From: Miroslav Mitev Date: Wed, 18 Oct 2023 00:40:56 +0300 Subject: [PATCH 08/18] Add tests for the progress validation --- .../test-class-validation-error.php | 67 +++++ .../validations/test-progress-validation.php | 270 ++++++++++++++++++ 2 files changed, 337 insertions(+) create mode 100644 tests/unit-tests/internal/migration/migrations/validations/test-class-validation-error.php create mode 100644 tests/unit-tests/internal/migration/migrations/validations/test-progress-validation.php diff --git a/tests/unit-tests/internal/migration/migrations/validations/test-class-validation-error.php b/tests/unit-tests/internal/migration/migrations/validations/test-class-validation-error.php new file mode 100644 index 0000000000..4c26ef5f6c --- /dev/null +++ b/tests/unit-tests/internal/migration/migrations/validations/test-class-validation-error.php @@ -0,0 +1,67 @@ +get_message(); + + /* Assert. */ + $this->assertSame( 'foo', $actual ); + } + + public function testGetData_HasData_ReturnsData(): void { + /* Arrange. */ + $error = new Validation_Error( 'foo', [ 'bar' => 'baz' ] ); + + /* Act. */ + $actual = $error->get_data(); + + /* Assert. */ + $this->assertSame( [ 'bar' => 'baz' ], $actual ); + } + + public function testGetData_HasNoData_ReturnsEmptyArray(): void { + /* Arrange. */ + $error = new Validation_Error( 'foo' ); + + /* Act. */ + $actual = $error->get_data(); + + /* Assert. */ + $this->assertSame( [], $actual ); + } + + public function testHasData_HasData_ReturnsTrue(): void { + /* Arrange. */ + $error = new Validation_Error( 'foo', [ 'bar' => 'baz' ] ); + + /* Act. */ + $actual = $error->has_data(); + + /* Assert. */ + $this->assertTrue( $actual ); + } + + public function testHasData_HasNoData_ReturnsTrue(): void { + /* Arrange. */ + $error = new Validation_Error( 'foo' ); + + /* Act. */ + $actual = $error->has_data(); + + /* Assert. */ + $this->assertFalse( $actual ); + } +} diff --git a/tests/unit-tests/internal/migration/migrations/validations/test-progress-validation.php b/tests/unit-tests/internal/migration/migrations/validations/test-progress-validation.php new file mode 100644 index 0000000000..519cce8190 --- /dev/null +++ b/tests/unit-tests/internal/migration/migrations/validations/test-progress-validation.php @@ -0,0 +1,270 @@ +factory = new Sensei_Factory(); + } + + public function testRun_WhenProgressMigrationIsComplete_HasNoErrors(): void { + /* Arrange. */ + $progress_validation = new Progress_Validation(); + + $this->mask_migration_as_complete(); + + /* Act. */ + $progress_validation->run(); + + /* Assert. */ + $this->assertFalse( $progress_validation->has_errors() ); + } + + public function testRun_WhenProgressMigrationIsNotComplete_HasError(): void { + /* Arrange. */ + $progress_validation = new Progress_Validation(); + + /* Act. */ + $progress_validation->run(); + + /* Assert. */ + $this->assertSame( + 'The progress migration is not complete. Please run the progress migration first.', + $this->get_first_error_message( $progress_validation ) + ); + } + + public function testRun_WhenHasCourseProgressInTable_HasNoErrors(): void { + /* Arrange. */ + $course_id = $this->factory->course->create(); + $progress_validation = new Progress_Validation(); + + $this->directlyEnrolStudent( 1, $course_id ); + $this->mask_migration_as_complete(); + + /* Act. */ + $progress_validation->run(); + + /* Assert. */ + $this->assertFalse( $progress_validation->has_errors() ); + } + + public function testRun_WhenHasNoCourseProgressInTable_HasError(): void { + /* Arrange. */ + global $wpdb; + $course_id = $this->factory->course->create(); + $progress_validation = new Progress_Validation(); + $progress_repository = new Tables_Based_Course_Progress_Repository( $wpdb ); + + $this->directlyEnrolStudent( 1, $course_id ); + $progress_repository->delete_for_course( $course_id ); + $this->mask_migration_as_complete(); + + /* Act. */ + $progress_validation->run(); + + /* Assert. */ + $this->assertSame( + 'Course tables based progress not found.', + $this->get_first_error_message( $progress_validation ) + ); + } + + public function testRun_WhenHasCourseProgressButDataIsNotMatching_HasError(): void { + /* Arrange. */ + global $wpdb; + $course_id = $this->factory->course->create(); + $progress_validation = new Progress_Validation(); + $progress_repository = new Tables_Based_Course_Progress_Repository( $wpdb ); + + $this->directlyEnrolStudent( 1, $course_id ); + $progress = $progress_repository->get( $course_id, 1 ); + $progress->complete(); + $progress_repository->save( $progress ); + $this->mask_migration_as_complete(); + + /* Act. */ + $progress_validation->run(); + + /* Assert. */ + $this->assertSame( + 'Data mismatch between comments and tables based progress.', + $this->get_first_error_message( $progress_validation ) + ); + } + + public function testRun_WhenHasLessonProgressInTable_HasNoErrors(): void { + /* Arrange. */ + $lesson_id = $this->factory->lesson->create(); + $progress_validation = new Progress_Validation(); + + Sensei_Utils::user_start_lesson( 1, $lesson_id ); + $this->mask_migration_as_complete(); + + /* Act. */ + $progress_validation->run(); + + /* Assert. */ + $this->assertFalse( $progress_validation->has_errors() ); + } + + public function testRun_WhenHasNoLessonProgressInTable_HasError(): void { + /* Arrange. */ + global $wpdb; + $lesson_id = $this->factory->lesson->create(); + $progress_validation = new Progress_Validation(); + $progress_repository = new Tables_Based_Lesson_Progress_Repository( $wpdb ); + + Sensei_Utils::user_start_lesson( 1, $lesson_id ); + $progress_repository->delete_for_lesson( $lesson_id ); + $this->mask_migration_as_complete(); + + /* Act. */ + $progress_validation->run(); + + /* Assert. */ + $this->assertSame( + 'Lesson tables based progress not found.', + $this->get_first_error_message( $progress_validation ) + ); + } + + public function testRun_WhenHasLessonProgressButDataIsNotMatching_HasError(): void { + /* Arrange. */ + global $wpdb; + $lesson_id = $this->factory->lesson->create(); + $progress_validation = new Progress_Validation(); + $progress_repository = new Tables_Based_Lesson_Progress_Repository( $wpdb ); + + Sensei_Utils::user_start_lesson( 1, $lesson_id ); + $progress = $progress_repository->get( $lesson_id, 1 ); + $progress->complete(); + $progress_repository->save( $progress ); + $this->mask_migration_as_complete(); + + /* Act. */ + $progress_validation->run(); + + /* Assert. */ + $this->assertSame( + 'Data mismatch between comments and tables based progress.', + $this->get_first_error_message( $progress_validation ) + ); + } + + public function testRun_WhenHasQuizProgressInTable_HasNoErrors(): void { + /* Arrange. */ + $course_data = $this->factory->get_course_with_lessons(); + $course_id = $course_data['course_id']; + $lesson_id = $course_data['lesson_ids'][0]; + $quiz_id = $course_data['quiz_ids'][0]; + $progress_validation = new Progress_Validation(); + + $this->directlyEnrolStudent( 1, $course_id ); + $answers = $this->factory->generate_user_quiz_answers( $quiz_id ); + Sensei_Quiz::save_user_answers( $answers, [], $lesson_id, 1 ); + Sensei()->quiz->maybe_create_quiz_progress( $quiz_id, 1 ); + $this->mask_migration_as_complete(); + + /* Act. */ + $progress_validation->run(); + + /* Assert. */ + $this->assertFalse( $progress_validation->has_errors() ); + } + + public function testRun_WhenHasNoQuizProgressInTable_HasError(): void { + /* Arrange. */ + global $wpdb; + $course_data = $this->factory->get_course_with_lessons(); + $course_id = $course_data['course_id']; + $lesson_id = $course_data['lesson_ids'][0]; + $quiz_id = $course_data['quiz_ids'][0]; + $progress_validation = new Progress_Validation(); + $progress_repository = new Tables_Based_Quiz_Progress_Repository( $wpdb ); + + $this->directlyEnrolStudent( 1, $course_id ); + $answers = $this->factory->generate_user_quiz_answers( $quiz_id ); + Sensei_Quiz::save_user_answers( $answers, [], $lesson_id, 1 ); + Sensei()->quiz->maybe_create_quiz_progress( $quiz_id, 1 ); + $progress_repository->delete_for_quiz( $quiz_id ); + $this->mask_migration_as_complete(); + + /* Act. */ + $progress_validation->run(); + + /* Assert. */ + $this->assertSame( + 'Quiz tables based progress not found.', + $this->get_first_error_message( $progress_validation ) + ); + } + + public function testRun_WhenHasQuizProgressButDataIsNotMatching_HasError(): void { + /* Arrange. */ + global $wpdb; + $course_data = $this->factory->get_course_with_lessons(); + $course_id = $course_data['course_id']; + $lesson_id = $course_data['lesson_ids'][0]; + $quiz_id = $course_data['quiz_ids'][0]; + $progress_validation = new Progress_Validation(); + $progress_repository = new Tables_Based_Quiz_Progress_Repository( $wpdb ); + + $this->directlyEnrolStudent( 1, $course_id ); + $answers = $this->factory->generate_user_quiz_answers( $quiz_id ); + Sensei_Quiz::save_user_answers( $answers, [], $lesson_id, 1 ); + Sensei()->quiz->maybe_create_quiz_progress( $quiz_id, 1 ); + $progress = $progress_repository->get( $quiz_id, 1 ); + $progress->pass(); + $progress_repository->save( $progress ); + $this->mask_migration_as_complete(); + + /* Act. */ + $progress_validation->run(); + + /* Assert. */ + $this->assertSame( + 'Data mismatch between comments and tables based progress.', + $this->get_first_error_message( $progress_validation ) + ); + } + + private function mask_migration_as_complete(): void { + update_option( Migration_Job_Scheduler::COMPLETED_OPTION_NAME, microtime( true ) ); + } + + private function get_first_error_message( Progress_Validation $progress_validation ): ?string { + $errors = $progress_validation->get_errors(); + + if ( empty( $errors ) ) { + return null; + } + + return $errors[0]->get_message(); + } +} From f9924b2dde8b6804e603b94c78613980c0ffa5a7 Mon Sep 17 00:00:00 2001 From: Miroslav Mitev Date: Wed, 18 Oct 2023 00:45:22 +0300 Subject: [PATCH 09/18] Fix linter issues --- .../migrations/test-class-student-progress-migration.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit-tests/internal/migration/migrations/test-class-student-progress-migration.php b/tests/unit-tests/internal/migration/migrations/test-class-student-progress-migration.php index e6232145a7..60136e86d8 100644 --- a/tests/unit-tests/internal/migration/migrations/test-class-student-progress-migration.php +++ b/tests/unit-tests/internal/migration/migrations/test-class-student-progress-migration.php @@ -95,8 +95,8 @@ public function testRun_CommentsExist_CreatesProgressMatchingEntriesInCustomTabl ), ) ); - $user_1 = $this->factory->user->create(); - $user_2 = $this->factory->user->create(); + $user_1 = $this->factory->user->create(); + $user_2 = $this->factory->user->create(); update_post_meta( $quiz_id, '_quiz_lesson', $lesson_id ); From 75c0cfd3b7e5cfdbe448216454d98a10647fff6e Mon Sep 17 00:00:00 2001 From: Miroslav Mitev Date: Wed, 18 Oct 2023 19:40:18 +0300 Subject: [PATCH 10/18] Fix tests --- .../migrations/validations/test-progress-validation.php | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/tests/unit-tests/internal/migration/migrations/validations/test-progress-validation.php b/tests/unit-tests/internal/migration/migrations/validations/test-progress-validation.php index 519cce8190..bf9696b49a 100644 --- a/tests/unit-tests/internal/migration/migrations/validations/test-progress-validation.php +++ b/tests/unit-tests/internal/migration/migrations/validations/test-progress-validation.php @@ -180,12 +180,11 @@ public function testRun_WhenHasLessonProgressButDataIsNotMatching_HasError(): vo public function testRun_WhenHasQuizProgressInTable_HasNoErrors(): void { /* Arrange. */ $course_data = $this->factory->get_course_with_lessons(); - $course_id = $course_data['course_id']; $lesson_id = $course_data['lesson_ids'][0]; $quiz_id = $course_data['quiz_ids'][0]; $progress_validation = new Progress_Validation(); - $this->directlyEnrolStudent( 1, $course_id ); + add_filter( 'sensei_is_enrolled', '__return_true' ); $answers = $this->factory->generate_user_quiz_answers( $quiz_id ); Sensei_Quiz::save_user_answers( $answers, [], $lesson_id, 1 ); Sensei()->quiz->maybe_create_quiz_progress( $quiz_id, 1 ); @@ -202,13 +201,12 @@ public function testRun_WhenHasNoQuizProgressInTable_HasError(): void { /* Arrange. */ global $wpdb; $course_data = $this->factory->get_course_with_lessons(); - $course_id = $course_data['course_id']; $lesson_id = $course_data['lesson_ids'][0]; $quiz_id = $course_data['quiz_ids'][0]; $progress_validation = new Progress_Validation(); $progress_repository = new Tables_Based_Quiz_Progress_Repository( $wpdb ); - $this->directlyEnrolStudent( 1, $course_id ); + add_filter( 'sensei_is_enrolled', '__return_true' ); $answers = $this->factory->generate_user_quiz_answers( $quiz_id ); Sensei_Quiz::save_user_answers( $answers, [], $lesson_id, 1 ); Sensei()->quiz->maybe_create_quiz_progress( $quiz_id, 1 ); @@ -229,13 +227,12 @@ public function testRun_WhenHasQuizProgressButDataIsNotMatching_HasError(): void /* Arrange. */ global $wpdb; $course_data = $this->factory->get_course_with_lessons(); - $course_id = $course_data['course_id']; $lesson_id = $course_data['lesson_ids'][0]; $quiz_id = $course_data['quiz_ids'][0]; $progress_validation = new Progress_Validation(); $progress_repository = new Tables_Based_Quiz_Progress_Repository( $wpdb ); - $this->directlyEnrolStudent( 1, $course_id ); + add_filter( 'sensei_is_enrolled', '__return_true' ); $answers = $this->factory->generate_user_quiz_answers( $quiz_id ); Sensei_Quiz::save_user_answers( $answers, [], $lesson_id, 1 ); Sensei()->quiz->maybe_create_quiz_progress( $quiz_id, 1 ); From 1bb9ee25bcce122406d3d0df1cc3f1b4c67fe586 Mon Sep 17 00:00:00 2001 From: Miroslav Mitev Date: Wed, 18 Oct 2023 21:01:09 +0300 Subject: [PATCH 11/18] Fix Psalm issues --- .../validations/class-progress-validation.php | 6 ++++++ psalm.xml | 17 ++++++++++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/includes/internal/migration/validations/class-progress-validation.php b/includes/internal/migration/validations/class-progress-validation.php index 85dcd95737..776b8ee750 100644 --- a/includes/internal/migration/validations/class-progress-validation.php +++ b/includes/internal/migration/validations/class-progress-validation.php @@ -98,6 +98,8 @@ private function add_error( string $message, array $data = [] ): void { /** * Get the course IDs. * + * @psalm-suppress InvalidReturnType, InvalidReturnStatement -- Psalm doesn't understand the 'fields' argument. + * * @return int[] */ private function get_course_ids(): array { @@ -114,6 +116,8 @@ private function get_course_ids(): array { /** * Get the lesson IDs. * + * @psalm-suppress InvalidReturnType, InvalidReturnStatement -- Psalm doesn't understand the 'fields' argument. + * * @return int[] */ private function get_lesson_ids(): array { @@ -130,6 +134,8 @@ private function get_lesson_ids(): array { /** * Get the quiz IDs. * + * @psalm-suppress InvalidReturnType, InvalidReturnStatement -- Psalm doesn't understand the 'fields' argument. + * * @return int[] */ private function get_quiz_ids(): array { diff --git a/psalm.xml b/psalm.xml index 43611cc0a8..9288bddc11 100644 --- a/psalm.xml +++ b/psalm.xml @@ -25,6 +25,7 @@ + @@ -32,6 +33,20 @@ - + + + + + + + + + + + + + + + From bc92318bda111f9832ccb0fe41c17df7ac275488 Mon Sep 17 00:00:00 2001 From: Miroslav Mitev Date: Fri, 20 Oct 2023 00:10:52 +0300 Subject: [PATCH 12/18] Change the progress validate command to `sensei validate progress` --- includes/class-sensei-cli.php | 2 +- ...command.php => class-sensei-validate-progress-command.php} | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) rename includes/cli/{class-sensei-db-validate-progress-command.php => class-sensei-validate-progress-command.php} (93%) diff --git a/includes/class-sensei-cli.php b/includes/class-sensei-cli.php index 9c57136c04..46e2359df3 100644 --- a/includes/class-sensei-cli.php +++ b/includes/class-sensei-cli.php @@ -25,6 +25,6 @@ public function __construct() { */ private function register() { WP_CLI::add_command( 'sensei db seed', Sensei_DB_Seed_Command::class ); - WP_CLI::add_command( 'sensei db validate progress', Sensei_DB_Validate_Progress_Command::class ); + WP_CLI::add_command( 'sensei validate progress', Sensei_Validate_Progress_Command::class ); } } diff --git a/includes/cli/class-sensei-db-validate-progress-command.php b/includes/cli/class-sensei-validate-progress-command.php similarity index 93% rename from includes/cli/class-sensei-db-validate-progress-command.php rename to includes/cli/class-sensei-validate-progress-command.php index 4e7a079b57..f93d5d71a5 100644 --- a/includes/cli/class-sensei-db-validate-progress-command.php +++ b/includes/cli/class-sensei-validate-progress-command.php @@ -1,6 +1,6 @@ Date: Fri, 20 Oct 2023 00:14:17 +0300 Subject: [PATCH 13/18] Use old array syntax to follow WP code standards --- .../migration/validations/class-progress-validation.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/includes/internal/migration/validations/class-progress-validation.php b/includes/internal/migration/validations/class-progress-validation.php index 776b8ee750..1510ac7695 100644 --- a/includes/internal/migration/validations/class-progress-validation.php +++ b/includes/internal/migration/validations/class-progress-validation.php @@ -30,7 +30,7 @@ class Progress_Validation { * * @var Validation_Error[] */ - private $errors = []; + private array $errors = array(); /** * Run the validation. @@ -40,7 +40,7 @@ class Progress_Validation { * @since $$next-version$$ */ public function run(): void { - $this->errors = []; + $this->errors = array(); if ( ! $this->is_progress_migration_complete() ) { $this->add_error( 'The progress migration is not complete. Please run the progress migration first.' ); From 57e53d55c776563f288427f5f82713a541709504 Mon Sep 17 00:00:00 2001 From: Miroslav Mitev Date: Fri, 20 Oct 2023 00:24:54 +0300 Subject: [PATCH 14/18] Improve code readability --- includes/class-sensei-cli.php | 4 +++- includes/cli/class-sensei-db-seed-command.php | 4 +++- includes/cli/class-sensei-validate-progress-command.php | 4 +++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/includes/class-sensei-cli.php b/includes/class-sensei-cli.php index 46e2359df3..20f0c0a964 100644 --- a/includes/class-sensei-cli.php +++ b/includes/class-sensei-cli.php @@ -5,7 +5,9 @@ * @package sensei */ -defined( 'ABSPATH' ) || exit; +if ( ! defined( 'ABSPATH' ) ) { + exit; // Exit if accessed directly. +} /** * CLI class. diff --git a/includes/cli/class-sensei-db-seed-command.php b/includes/cli/class-sensei-db-seed-command.php index 1a01b1c14d..a2df020387 100644 --- a/includes/cli/class-sensei-db-seed-command.php +++ b/includes/cli/class-sensei-db-seed-command.php @@ -5,7 +5,9 @@ * @package sensei */ -defined( 'ABSPATH' ) || exit; +if ( ! defined( 'ABSPATH' ) ) { + exit; // Exit if accessed directly. +} /** * WP-CLI command that helps with seeding the database. diff --git a/includes/cli/class-sensei-validate-progress-command.php b/includes/cli/class-sensei-validate-progress-command.php index f93d5d71a5..26c5c94207 100644 --- a/includes/cli/class-sensei-validate-progress-command.php +++ b/includes/cli/class-sensei-validate-progress-command.php @@ -7,7 +7,9 @@ use Sensei\Internal\Migration\Validations\Progress_Validation; -defined( 'ABSPATH' ) || exit; +if ( ! defined( 'ABSPATH' ) ) { + exit; // Exit if accessed directly. +} /** * WP-CLI command that validates the progress data. From ff35467e653efc585f73164dbec8187d9aba364a Mon Sep 17 00:00:00 2001 From: Miroslav Mitev Date: Fri, 20 Oct 2023 00:38:53 +0300 Subject: [PATCH 15/18] Use `wp-cli-stubs` in Psalm --- composer.json | 1 + composer.lock | 50 ++++++++++++++++++++++++++++++++--- config/psalm/psalm-loader.php | 1 + psalm.xml | 10 ------- 4 files changed, 49 insertions(+), 13 deletions(-) diff --git a/composer.json b/composer.json index d7d7892c62..cc40fc9ff5 100755 --- a/composer.json +++ b/composer.json @@ -13,6 +13,7 @@ "humbug/php-scoper": "0.15.0", "pelago/emogrifier": "7.0.0", "php-stubs/wordpress-stubs": "^6.3", + "php-stubs/wp-cli-stubs": "^2.8", "phpcompatibility/phpcompatibility-wp": "2.1.4", "sirbrillig/phpcs-no-get-current-user": "1.1.0", "sirbrillig/phpcs-variable-analysis": "2.11.16", diff --git a/composer.lock b/composer.lock index acfe5adb1c..8a71b09621 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "0e4302cd363bf4222e4336cdeeee607e", + "content-hash": "42ec6978f53e8a797460fc8c19e65f61", "packages": [ { "name": "woocommerce/action-scheduler", @@ -1536,6 +1536,50 @@ }, "time": "2023-08-10T16:34:11+00:00" }, + { + "name": "php-stubs/wp-cli-stubs", + "version": "v2.8.0", + "source": { + "type": "git", + "url": "https://github.com/php-stubs/wp-cli-stubs.git", + "reference": "5a4fce1430c5d59f8d8a5f0ab573930139b0279b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-stubs/wp-cli-stubs/zipball/5a4fce1430c5d59f8d8a5f0ab573930139b0279b", + "reference": "5a4fce1430c5d59f8d8a5f0ab573930139b0279b", + "shasum": "" + }, + "require": { + "php-stubs/wordpress-stubs": "^4.7 || ^5.0 || ^6.0" + }, + "require-dev": { + "php": "~7.3 || ~8.0", + "php-stubs/generator": "^0.8.0" + }, + "suggest": { + "symfony/polyfill-php73": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", + "szepeviktor/phpstan-wordpress": "WordPress extensions for PHPStan" + }, + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "WP-CLI function and class declaration stubs for static analysis.", + "homepage": "https://github.com/php-stubs/wp-cli-stubs", + "keywords": [ + "PHPStan", + "static analysis", + "wordpress", + "wp-cli" + ], + "support": { + "issues": "https://github.com/php-stubs/wp-cli-stubs/issues", + "source": "https://github.com/php-stubs/wp-cli-stubs/tree/v2.8.0" + }, + "time": "2023-06-02T09:21:15+00:00" + }, { "name": "phpcompatibility/php-compatibility", "version": "9.3.5", @@ -4609,10 +4653,10 @@ "prefer-lowest": false, "platform": [], "platform-dev": { - "php": "^7.3 || ^8" + "php": "^7.4 || ^8" }, "platform-overrides": { - "php": "7.3" + "php": "7.4" }, "plugin-api-version": "2.3.0" } diff --git a/config/psalm/psalm-loader.php b/config/psalm/psalm-loader.php index 95fe62af27..4e83fb2165 100644 --- a/config/psalm/psalm-loader.php +++ b/config/psalm/psalm-loader.php @@ -16,4 +16,5 @@ require_once __DIR__ . '/../../vendor/autoload.php'; require_once __DIR__ . '/../../vendor/php-stubs/wordpress-stubs/wordpress-stubs.php'; +require_once __DIR__ . '/../../vendor/php-stubs/wp-cli-stubs/wp-cli-stubs.php'; require_once __DIR__ . '/../../vendor/woocommerce/action-scheduler/functions.php'; diff --git a/psalm.xml b/psalm.xml index 9288bddc11..0f809dee81 100644 --- a/psalm.xml +++ b/psalm.xml @@ -33,16 +33,6 @@ - - - - - - - - - - From 6678751f47ae167c0977d71ca1acf06c88e54088 Mon Sep 17 00:00:00 2001 From: Miroslav Mitev Date: Fri, 20 Oct 2023 15:53:15 +0300 Subject: [PATCH 16/18] Revert "Use `wp-cli-stubs` in Psalm" This reverts commit ff35467e653efc585f73164dbec8187d9aba364a. --- composer.json | 1 - composer.lock | 50 +++-------------------------------- config/psalm/psalm-loader.php | 1 - psalm.xml | 10 +++++++ 4 files changed, 13 insertions(+), 49 deletions(-) diff --git a/composer.json b/composer.json index cc40fc9ff5..d7d7892c62 100755 --- a/composer.json +++ b/composer.json @@ -13,7 +13,6 @@ "humbug/php-scoper": "0.15.0", "pelago/emogrifier": "7.0.0", "php-stubs/wordpress-stubs": "^6.3", - "php-stubs/wp-cli-stubs": "^2.8", "phpcompatibility/phpcompatibility-wp": "2.1.4", "sirbrillig/phpcs-no-get-current-user": "1.1.0", "sirbrillig/phpcs-variable-analysis": "2.11.16", diff --git a/composer.lock b/composer.lock index 8a71b09621..acfe5adb1c 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "42ec6978f53e8a797460fc8c19e65f61", + "content-hash": "0e4302cd363bf4222e4336cdeeee607e", "packages": [ { "name": "woocommerce/action-scheduler", @@ -1536,50 +1536,6 @@ }, "time": "2023-08-10T16:34:11+00:00" }, - { - "name": "php-stubs/wp-cli-stubs", - "version": "v2.8.0", - "source": { - "type": "git", - "url": "https://github.com/php-stubs/wp-cli-stubs.git", - "reference": "5a4fce1430c5d59f8d8a5f0ab573930139b0279b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-stubs/wp-cli-stubs/zipball/5a4fce1430c5d59f8d8a5f0ab573930139b0279b", - "reference": "5a4fce1430c5d59f8d8a5f0ab573930139b0279b", - "shasum": "" - }, - "require": { - "php-stubs/wordpress-stubs": "^4.7 || ^5.0 || ^6.0" - }, - "require-dev": { - "php": "~7.3 || ~8.0", - "php-stubs/generator": "^0.8.0" - }, - "suggest": { - "symfony/polyfill-php73": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", - "szepeviktor/phpstan-wordpress": "WordPress extensions for PHPStan" - }, - "type": "library", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "WP-CLI function and class declaration stubs for static analysis.", - "homepage": "https://github.com/php-stubs/wp-cli-stubs", - "keywords": [ - "PHPStan", - "static analysis", - "wordpress", - "wp-cli" - ], - "support": { - "issues": "https://github.com/php-stubs/wp-cli-stubs/issues", - "source": "https://github.com/php-stubs/wp-cli-stubs/tree/v2.8.0" - }, - "time": "2023-06-02T09:21:15+00:00" - }, { "name": "phpcompatibility/php-compatibility", "version": "9.3.5", @@ -4653,10 +4609,10 @@ "prefer-lowest": false, "platform": [], "platform-dev": { - "php": "^7.4 || ^8" + "php": "^7.3 || ^8" }, "platform-overrides": { - "php": "7.4" + "php": "7.3" }, "plugin-api-version": "2.3.0" } diff --git a/config/psalm/psalm-loader.php b/config/psalm/psalm-loader.php index 4e83fb2165..95fe62af27 100644 --- a/config/psalm/psalm-loader.php +++ b/config/psalm/psalm-loader.php @@ -16,5 +16,4 @@ require_once __DIR__ . '/../../vendor/autoload.php'; require_once __DIR__ . '/../../vendor/php-stubs/wordpress-stubs/wordpress-stubs.php'; -require_once __DIR__ . '/../../vendor/php-stubs/wp-cli-stubs/wp-cli-stubs.php'; require_once __DIR__ . '/../../vendor/woocommerce/action-scheduler/functions.php'; diff --git a/psalm.xml b/psalm.xml index 0f809dee81..9288bddc11 100644 --- a/psalm.xml +++ b/psalm.xml @@ -33,6 +33,16 @@ + + + + + + + + + + From f362a099d825ebd7f23541d9da0be1ef99ab8a31 Mon Sep 17 00:00:00 2001 From: Miroslav Mitev Date: Fri, 20 Oct 2023 16:23:31 +0300 Subject: [PATCH 17/18] Fix attempt for tests failing randomly --- .../validations/test-progress-validation.php | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/unit-tests/internal/migration/migrations/validations/test-progress-validation.php b/tests/unit-tests/internal/migration/migrations/validations/test-progress-validation.php index bf9696b49a..9ba0b8e8fe 100644 --- a/tests/unit-tests/internal/migration/migrations/validations/test-progress-validation.php +++ b/tests/unit-tests/internal/migration/migrations/validations/test-progress-validation.php @@ -30,6 +30,13 @@ protected function setUp(): void { parent::setUp(); $this->factory = new Sensei_Factory(); + $this->cleanup_custom_tables(); + } + + protected function tearDown(): void { + parent::tearDown(); + + $this->cleanup_custom_tables(); } public function testRun_WhenProgressMigrationIsComplete_HasNoErrors(): void { @@ -264,4 +271,14 @@ private function get_first_error_message( Progress_Validation $progress_validati return $errors[0]->get_message(); } + + private function cleanup_custom_tables() { + global $wpdb; + // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching + $wpdb->query( "TRUNCATE TABLE {$wpdb->prefix}sensei_lms_progress" ); + $wpdb->query( "TRUNCATE TABLE {$wpdb->prefix}sensei_lms_quiz_grades" ); + $wpdb->query( "TRUNCATE TABLE {$wpdb->prefix}sensei_lms_quiz_answers" ); + $wpdb->query( "TRUNCATE TABLE {$wpdb->prefix}sensei_lms_quiz_submissions" ); + // phpcs:enable + } } From f93f87ff7be787956ef546259717d29824927b4c Mon Sep 17 00:00:00 2001 From: Miroslav Mitev Date: Fri, 20 Oct 2023 19:44:54 +0300 Subject: [PATCH 18/18] Revert "Fix attempt for tests failing randomly" This reverts commit f362a099d825ebd7f23541d9da0be1ef99ab8a31. --- .../validations/test-progress-validation.php | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/tests/unit-tests/internal/migration/migrations/validations/test-progress-validation.php b/tests/unit-tests/internal/migration/migrations/validations/test-progress-validation.php index 9ba0b8e8fe..bf9696b49a 100644 --- a/tests/unit-tests/internal/migration/migrations/validations/test-progress-validation.php +++ b/tests/unit-tests/internal/migration/migrations/validations/test-progress-validation.php @@ -30,13 +30,6 @@ protected function setUp(): void { parent::setUp(); $this->factory = new Sensei_Factory(); - $this->cleanup_custom_tables(); - } - - protected function tearDown(): void { - parent::tearDown(); - - $this->cleanup_custom_tables(); } public function testRun_WhenProgressMigrationIsComplete_HasNoErrors(): void { @@ -271,14 +264,4 @@ private function get_first_error_message( Progress_Validation $progress_validati return $errors[0]->get_message(); } - - private function cleanup_custom_tables() { - global $wpdb; - // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching - $wpdb->query( "TRUNCATE TABLE {$wpdb->prefix}sensei_lms_progress" ); - $wpdb->query( "TRUNCATE TABLE {$wpdb->prefix}sensei_lms_quiz_grades" ); - $wpdb->query( "TRUNCATE TABLE {$wpdb->prefix}sensei_lms_quiz_answers" ); - $wpdb->query( "TRUNCATE TABLE {$wpdb->prefix}sensei_lms_quiz_submissions" ); - // phpcs:enable - } }