From 3ac99903e2bb42f69d5bf95885dd1831145797a5 Mon Sep 17 00:00:00 2001 From: Anindra Das Bivas Date: Thu, 1 Oct 2026 15:31:39 +0600 Subject: [PATCH 01/19] `/models` files security audit and fixes --- {.agent => .agents}/skills/core-development-skill/SKILL.md | 5 +++++ {.agent => .agents}/skills/wpcs-security-skill/SKILL.md | 5 +++++ 2 files changed, 10 insertions(+) rename {.agent => .agents}/skills/core-development-skill/SKILL.md (99%) rename {.agent => .agents}/skills/wpcs-security-skill/SKILL.md (99%) diff --git a/.agent/skills/core-development-skill/SKILL.md b/.agents/skills/core-development-skill/SKILL.md similarity index 99% rename from .agent/skills/core-development-skill/SKILL.md rename to .agents/skills/core-development-skill/SKILL.md index b5f71641be..1421698b09 100644 --- a/.agent/skills/core-development-skill/SKILL.md +++ b/.agents/skills/core-development-skill/SKILL.md @@ -1,3 +1,8 @@ +--- +name: core-development-skill +description: This skill governs all development, bug fixing, and feature work inside the **Tutor LMS** plugin codebase (`github.com/themeum/tutor`). It covers project structure, namespace conventions, coding patterns, PHPDoc standards, available helpers/utilities, security rules, and AI agent instructions. +--- + # Tutor LMS — Core Development Skill ## Overview diff --git a/.agent/skills/wpcs-security-skill/SKILL.md b/.agents/skills/wpcs-security-skill/SKILL.md similarity index 99% rename from .agent/skills/wpcs-security-skill/SKILL.md rename to .agents/skills/wpcs-security-skill/SKILL.md index 51ebe7845f..4c98fa5a22 100644 --- a/.agent/skills/wpcs-security-skill/SKILL.md +++ b/.agents/skills/wpcs-security-skill/SKILL.md @@ -1,3 +1,8 @@ +--- +name: wpcs-security-skill +description: Enforces strict WordPress coding standards (WPCS) and security best practices for PHP, JS, CSS, and HTML. +--- + # WordPress Coding Standards & Security Skill ## Overview From 7704e7d08142cf77ee80099a56dd7d22b2720f9e Mon Sep 17 00:00:00 2001 From: Anindra Das Bivas Date: Thu, 1 Oct 2026 15:35:04 +0600 Subject: [PATCH 02/19] Fix: typcast to int before placing it in string interpolation for query --- models/UserModel.php | 1 + 1 file changed, 1 insertion(+) diff --git a/models/UserModel.php b/models/UserModel.php index 678c6a09de..71f2f356b8 100644 --- a/models/UserModel.php +++ b/models/UserModel.php @@ -60,6 +60,7 @@ public function get_users_list( array $args ) { public function get_unenrolled_users( $object_id, $search_clause = array(), $limit = 10, $offset = 0 ) { global $wpdb; + $object_id = absint( $object_id ); $primary_table = "{$wpdb->users} AS u"; $joining_tables = array( array( From 0b0936c32db3c8914c0a465ace9ce2a066c6e70b Mon Sep 17 00:00:00 2001 From: Anindra Das Bivas Date: Thu, 1 Oct 2026 17:05:59 +0600 Subject: [PATCH 03/19] Security Fixes --- models/QuizModel.php | 64 +++++++++++++++++++------------------------- 1 file changed, 28 insertions(+), 36 deletions(-) diff --git a/models/QuizModel.php b/models/QuizModel.php index 0167b3b822..190db3882f 100644 --- a/models/QuizModel.php +++ b/models/QuizModel.php @@ -10,6 +10,10 @@ namespace Tutor\Models; +if ( ! defined( 'ABSPATH' ) ) { + exit; +} + use Tutor\Cache\TutorCache; use Tutor\Components\Badge; use TUTOR\Course_List; @@ -974,23 +978,27 @@ public static function get_answers_by_quiz_question( $question_id, $rand = false public static function get_quiz_answers_by_attempt_id( $attempt_id, $add_index = false ) { global $wpdb; - $ids = is_array( $attempt_id ) ? $attempt_id : array( $attempt_id ); - $ids_in = implode( ',', $ids ); + $ids = array_filter( array_map( 'absint', (array) $attempt_id ) ); - if ( empty( $ids_in ) ) { + if ( empty( $ids ) ) { // Prevent empty. return array(); } + $placeholders = implode( ', ', array_fill( 0, count( $ids ), '%d' ) ); + //phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared $results = $wpdb->get_results( - "SELECT answers.*, - question.* - FROM {$wpdb->prefix}tutor_quiz_attempt_answers answers - LEFT JOIN {$wpdb->prefix}tutor_quiz_questions question - ON answers.question_id = question.question_id - WHERE answers.quiz_attempt_id IN ({$ids_in}) - ORDER BY attempt_answer_id ASC;" + $wpdb->prepare( + "SELECT answers.*, + question.* + FROM {$wpdb->prefix}tutor_quiz_attempt_answers answers + LEFT JOIN {$wpdb->prefix}tutor_quiz_questions question + ON answers.question_id = question.question_id + WHERE answers.quiz_attempt_id IN ({$placeholders}) + ORDER BY attempt_answer_id ASC;", + $ids + ) ); //phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared @@ -1300,7 +1308,8 @@ function ( $id ) { public static function get_quiz_attempt_timing( $attempt_data ) { $attempt_duration = ''; $attempt_duration_taken = ''; - $attempt_info = @unserialize( $attempt_data->attempt_info ); + $raw_info = $attempt_data->attempt_info ?? ''; + $attempt_info = is_string( $raw_info ) ? unserialize( $raw_info, array( 'allowed_classes' => false ) ) : array(); if ( is_array( $attempt_info ) ) { // Allowed duration. if ( isset( $attempt_info['time_limit'] ) ) { @@ -1419,38 +1428,21 @@ public function get_first_or_last_attempt( int $quiz_id, int $user_id = 0, strin * * @since 2.2.0 * - * @param int|array $course_id Course id or array of course ids. + * @param int $course_id Course id. * * @return int */ public static function get_quiz_count_by_course( $course_id ) { - global $wpdb; - - $and_clause = is_array( $course_id ) && count( $course_id ) ? ' AND post_parent IN (' . QueryHelper::prepare_in_clause( $course_id ) . ')' : "AND post_parent = $course_id"; + $topic_ids = tutor_utils()->get_topics( absint( $course_id ), array( 'fields' => 'ids' ) )->get_posts(); - //phpcs:disable - $count = $wpdb->get_var( - $wpdb->prepare( - "SELECT - COUNT(ID) - FROM {$wpdb->posts} - WHERE post_parent IN - (SELECT - ID - FROM {$wpdb->posts} - WHERE post_type = %s - {$and_clause} - AND post_status = %s - ) - AND post_type = %s - AND post_status = %s", - 'topics', - 'publish', - 'tutor_quiz', - 'publish' + $count = QueryHelper::get_count( + 'posts', + array( + 'post_parent' => array( 'IN', $topic_ids ), + 'post_type' => tutor()->quiz_post_type, + 'post_status' => 'publish', ) ); - //phpcs:enable return $count ? $count : 0; } From 1813746ca9ad146fbf498a7412fc52f60ad7bc94 Mon Sep 17 00:00:00 2001 From: Anindra Das Bivas Date: Fri, 2 Oct 2026 15:45:04 +0600 Subject: [PATCH 04/19] Fix: Query hardened for the several model class --- models/CourseModel.php | 29 ++++++++++++++--------------- models/EnrollmentModel.php | 14 +++++++++++++- models/OrderModel.php | 13 +++++++------ 3 files changed, 34 insertions(+), 22 deletions(-) diff --git a/models/CourseModel.php b/models/CourseModel.php index f01d28814e..9bd5a3b101 100644 --- a/models/CourseModel.php +++ b/models/CourseModel.php @@ -10,6 +10,10 @@ namespace Tutor\Models; +if ( ! defined( 'ABSPATH' ) ) { + exit; +} + use Tutor\Helpers\DateTimeHelper; use TUTOR\Icon; use TUTOR\Course; @@ -164,22 +168,16 @@ public static function get_post_types( $post ) { public static function get_courses( $excludes = array(), $post_status = array( 'publish' ) ) { global $wpdb; - $excludes = (array) $excludes; + $excludes = array_filter( array_map( 'absint', (array) $excludes ) ); $exclude_query = ''; + $post_status = array_intersect( $post_status, self::get_status_list() ); if ( count( $excludes ) ) { - $exclude_query = implode( "','", $excludes ); + $exclude_placeholders = implode( ', ', array_fill( 0, count( $excludes ), '%d' ) ); + $exclude_query = "AND ID NOT IN({$exclude_placeholders})"; } - $post_status = array_map( - function ( $element ) { - return "'" . $element . "'"; - }, - $post_status - ); - - $post_status = implode( ',', $post_status ); - $course_post_type = tutor()->course_post_type; + $post_status = QueryHelper::prepare_in_clause( $post_status ); //phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared $query = $wpdb->get_results( @@ -192,10 +190,11 @@ function ( $element ) { menu_order FROM {$wpdb->posts} WHERE post_status IN ({$post_status}) - AND ID NOT IN('$exclude_query') + {$exclude_query} AND post_type = %s; ", - $course_post_type + self::POST_TYPE, + $excludes ) ); //phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared @@ -749,7 +748,7 @@ public static function get_paid_courses( array $args = array() ) { ); // Check if the current user is an admin. - if ( ! current_user_can( 'administrator' ) ) { + if ( ! current_user_can( 'manage_options' ) ) { $args['author'] = $current_user->ID; } @@ -1186,7 +1185,7 @@ public static function get_course_dropdown_options() { ), ); - $courses = current_user_can( 'administrator' ) ? self::get_courses() : self::get_courses_by_instructor(); + $courses = current_user_can( 'manage_options' ) ? self::get_courses() : self::get_courses_by_instructor(); if ( ! empty( $courses ) ) { foreach ( $courses as $course ) { $course_options[] = array( diff --git a/models/EnrollmentModel.php b/models/EnrollmentModel.php index bcb89a8286..127b07e17d 100644 --- a/models/EnrollmentModel.php +++ b/models/EnrollmentModel.php @@ -10,6 +10,10 @@ namespace Tutor\Models; +if ( ! defined( 'ABSPATH' ) ) { + exit; +} + use Tutor\Cache\TutorCache; use TUTOR\Course; use Tutor\Helpers\QueryHelper; @@ -281,9 +285,17 @@ public static function get_enrolment_by_enrol_id( $enrol_id = 0 ) { */ public static function get_enrolled_data( $user_id = 0, $course_id = 0, $status = self::STATUS_COMPLETED ) { global $wpdb; + $user_id = absint( $user_id ); + $course_id = absint( $course_id ); + + if ( ! in_array( $status, array( self::STATUS_COMPLETED, self::STATUS_CANCEL, self::STATUS_PENDING ), true ) ) { + return null; + } + $status_clause = $status ? $wpdb->prepare( 'AND post_status = %s ', $status ) : ''; + // If course ID provided, it will return single row data. - if ( $course_id > 0 ) { + if ( $course_id > 0 && tutor()->course_post_type === get_post_type( $course_id ) ) { return $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->posts} diff --git a/models/OrderModel.php b/models/OrderModel.php index c617d7f149..a4ada20145 100644 --- a/models/OrderModel.php +++ b/models/OrderModel.php @@ -10,12 +10,13 @@ namespace Tutor\Models; +if ( ! defined( 'ABSPATH' ) ) { + exit; +} + use DateTime; use Exception; use Tutor\Cache\TutorCache; -use Tutor\Components\Badge; -use Tutor\Components\Button; -use Tutor\Components\Constants\Variant; use TUTOR\Earnings; use Tutor\Ecommerce\BillingController; use Tutor\Ecommerce\CheckoutController; @@ -25,7 +26,6 @@ use Tutor\Helpers\DateTimeHelper; use Tutor\Helpers\QueryHelper; use TUTOR\User; -use TutorPro\Ecommerce\Invoice; /** * OrderModel Class @@ -421,6 +421,7 @@ public function create_order( array $data ) { } } } catch ( \Throwable $th ) { + $wpdb->query( 'ROLLBACK' ); throw new \Exception( $th->getMessage() ); } } @@ -1393,9 +1394,9 @@ public function get_refunds_by_user( int $user_id, string $period = '', $start_d ); //phpcs:enable } else { - $earning_table = $wpdb->tutor_earnings; + $earning_table = QueryHelper::prepare_table_name( 'earnings' ); if ( $user_id ) { - $user_clause = "AND {$user_id} = (SELECT user_id FROM {$earning_table} WHERE user_id = {$user_id} LIMIT 1)"; + $user_clause = $wpdb->prepare( "AND %d = (SELECT user_id FROM {$earning_table} WHERE user_id = %d LIMIT 1)", $user_id, $user_id ); //phpcs:ignore } //phpcs:disable From 9e185bb70b89b0e8a046d4cd17c8cb1001b051dd Mon Sep 17 00:00:00 2001 From: Anindra Das Bivas Date: Tue, 6 Oct 2026 15:30:17 +0600 Subject: [PATCH 05/19] updated abspath check to use shorthand syntax --- models/CourseModel.php | 4 +--- models/EnrollmentModel.php | 4 +--- models/OrderModel.php | 4 +--- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/models/CourseModel.php b/models/CourseModel.php index 9bd5a3b101..51733c3b0b 100644 --- a/models/CourseModel.php +++ b/models/CourseModel.php @@ -10,9 +10,7 @@ namespace Tutor\Models; -if ( ! defined( 'ABSPATH' ) ) { - exit; -} +defined( 'ABSPATH' ) || exit; use Tutor\Helpers\DateTimeHelper; use TUTOR\Icon; diff --git a/models/EnrollmentModel.php b/models/EnrollmentModel.php index 127b07e17d..0b1df2daf1 100644 --- a/models/EnrollmentModel.php +++ b/models/EnrollmentModel.php @@ -10,9 +10,7 @@ namespace Tutor\Models; -if ( ! defined( 'ABSPATH' ) ) { - exit; -} +defined( 'ABSPATH' ) || exit; use Tutor\Cache\TutorCache; use TUTOR\Course; diff --git a/models/OrderModel.php b/models/OrderModel.php index a4ada20145..800d0477d3 100644 --- a/models/OrderModel.php +++ b/models/OrderModel.php @@ -10,9 +10,7 @@ namespace Tutor\Models; -if ( ! defined( 'ABSPATH' ) ) { - exit; -} +defined( 'ABSPATH' ) || exit; use DateTime; use Exception; From a9c9e69ad6a2af2e6332ed1ddce2dd44a4b00f10 Mon Sep 17 00:00:00 2001 From: Anindra Das Bivas Date: Tue, 6 Oct 2026 15:50:05 +0600 Subject: [PATCH 06/19] updated abspath check to use shorthand syntax --- models/QuizModel.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/models/QuizModel.php b/models/QuizModel.php index 190db3882f..5185d01742 100644 --- a/models/QuizModel.php +++ b/models/QuizModel.php @@ -10,9 +10,7 @@ namespace Tutor\Models; -if ( ! defined( 'ABSPATH' ) ) { - exit; -} +defined( 'ABSPATH' ) || exit; use Tutor\Cache\TutorCache; use Tutor\Components\Badge; From 9796bf97a6b0a6e9ad58fe9fde61048f599d6271 Mon Sep 17 00:00:00 2001 From: Anindra Das Bivas Date: Thu, 8 Oct 2026 11:28:58 +0600 Subject: [PATCH 07/19] added given review changes --- models/CourseModel.php | 9 ++++++--- models/EnrollmentModel.php | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/models/CourseModel.php b/models/CourseModel.php index 51733c3b0b..3bb1b6c91c 100644 --- a/models/CourseModel.php +++ b/models/CourseModel.php @@ -170,8 +170,12 @@ public static function get_courses( $excludes = array(), $post_status = array( ' $exclude_query = ''; $post_status = array_intersect( $post_status, self::get_status_list() ); + if ( empty( $post_status ) ) { + $post_status = array( 'publish' ); + } + if ( count( $excludes ) ) { - $exclude_placeholders = implode( ', ', array_fill( 0, count( $excludes ), '%d' ) ); + $exclude_placeholders = QueryHelper::prepare_in_clause( $excludes ); $exclude_query = "AND ID NOT IN({$exclude_placeholders})"; } @@ -191,8 +195,7 @@ public static function get_courses( $excludes = array(), $post_status = array( ' {$exclude_query} AND post_type = %s; ", - self::POST_TYPE, - $excludes + self::POST_TYPE ) ); //phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared diff --git a/models/EnrollmentModel.php b/models/EnrollmentModel.php index 0b1df2daf1..1ad27328db 100644 --- a/models/EnrollmentModel.php +++ b/models/EnrollmentModel.php @@ -286,7 +286,7 @@ public static function get_enrolled_data( $user_id = 0, $course_id = 0, $status $user_id = absint( $user_id ); $course_id = absint( $course_id ); - if ( ! in_array( $status, array( self::STATUS_COMPLETED, self::STATUS_CANCEL, self::STATUS_PENDING ), true ) ) { + if ( ! empty( $status ) && ! in_array( $status, array( self::STATUS_COMPLETED, self::STATUS_CANCEL, self::STATUS_PENDING ), true ) ) { return null; } From 38f791fa6aad69a511594bdc2c6b47dbb65584f4 Mon Sep 17 00:00:00 2001 From: Anindra Das Bivas Date: Wed, 14 Oct 2026 12:21:50 +0600 Subject: [PATCH 08/19] added requested changes --- models/QuizModel.php | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/models/QuizModel.php b/models/QuizModel.php index 7e8922fe8a..79ea5c4f7a 100644 --- a/models/QuizModel.php +++ b/models/QuizModel.php @@ -976,7 +976,7 @@ public static function get_answers_by_quiz_question( $question_id, $rand = false public static function get_quiz_answers_by_attempt_id( $attempt_id, $add_index = false ) { global $wpdb; - $ids = array_filter( array_map( 'absint', (array) $attempt_id ) ); + $ids = array_values( array_filter( array_map( 'absint', (array) $attempt_id ) ) ); if ( empty( $ids ) ) { // Prevent empty. @@ -1377,7 +1377,7 @@ public static function get_quiz_attempt_timing( $attempt_data ) { $attempt_duration = ''; $attempt_duration_taken = ''; $raw_info = $attempt_data->attempt_info ?? ''; - $attempt_info = is_string( $raw_info ) ? unserialize( $raw_info, array( 'allowed_classes' => false ) ) : array(); + $attempt_info = is_string( $raw_info ) && is_serialized( $raw_info ) ? unserialize( $raw_info, array( 'allowed_classes' => false ) ) : array(); if ( is_array( $attempt_info ) ) { // Allowed duration. if ( isset( $attempt_info['time_limit'] ) ) { @@ -1496,22 +1496,32 @@ public function get_first_or_last_attempt( int $quiz_id, int $user_id = 0, strin * * @since 2.2.0 * - * @param int $course_id Course id. + * @param int|array $course_id Course id or array of course ids. * * @return int */ public static function get_quiz_count_by_course( $course_id ) { - $topic_ids = tutor_utils()->get_topics( absint( $course_id ), array( 'fields' => 'ids' ) )->get_posts(); + global $wpdb; + $course_ids = array_values( array_filter( array_map( 'absint', (array) $course_id ) ) ); + if ( empty( $course_ids ) ) { + return 0; + } - $count = QueryHelper::get_count( - 'posts', - array( - 'post_parent' => array( 'IN', $topic_ids ), - 'post_type' => tutor()->quiz_post_type, - 'post_status' => 'publish', + $placeholders = implode( ', ', array_fill( 0, count( $course_ids ), '%d' ) ); + + //phpcs:disable + return (int) $wpdb->get_var( + $wpdb->prepare( + "SELECT COUNT(ID) FROM {$wpdb->posts} + WHERE post_parent IN ( + SELECT ID FROM {$wpdb->posts} + WHERE post_type = 'topics' AND post_parent IN ({$placeholders}) AND post_status = 'publish' + ) + AND post_type = %s AND post_status = 'publish'", + array( ...$course_ids, tutor()->quiz_post_type ) ) ); - return $count ? $count : 0; + //phpcs:enable } /** From 9b62dbdd0a02adde3f92845a503157e73158dd59 Mon Sep 17 00:00:00 2001 From: Anindra Das Bivas Date: Wed, 14 Oct 2026 15:19:06 +0600 Subject: [PATCH 09/19] added requested changes --- models/QuizModel.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/models/QuizModel.php b/models/QuizModel.php index 79ea5c4f7a..f1d3beb1a4 100644 --- a/models/QuizModel.php +++ b/models/QuizModel.php @@ -976,7 +976,7 @@ public static function get_answers_by_quiz_question( $question_id, $rand = false public static function get_quiz_answers_by_attempt_id( $attempt_id, $add_index = false ) { global $wpdb; - $ids = array_values( array_filter( array_map( 'absint', (array) $attempt_id ) ) ); + $ids = array_values( array_filter( array_map( 'intval', (array) $attempt_id ) ) ); if ( empty( $ids ) ) { // Prevent empty. @@ -1376,7 +1376,7 @@ function ( $id ) { public static function get_quiz_attempt_timing( $attempt_data ) { $attempt_duration = ''; $attempt_duration_taken = ''; - $raw_info = $attempt_data->attempt_info ?? ''; + $raw_info = trim( (string) $attempt_data->attempt_info ?? '' ); $attempt_info = is_string( $raw_info ) && is_serialized( $raw_info ) ? unserialize( $raw_info, array( 'allowed_classes' => false ) ) : array(); if ( is_array( $attempt_info ) ) { // Allowed duration. @@ -1502,7 +1502,7 @@ public function get_first_or_last_attempt( int $quiz_id, int $user_id = 0, strin */ public static function get_quiz_count_by_course( $course_id ) { global $wpdb; - $course_ids = array_values( array_filter( array_map( 'absint', (array) $course_id ) ) ); + $course_ids = array_values( array_filter( array_map( 'intval', (array) $course_id ) ) ); if ( empty( $course_ids ) ) { return 0; } From 1b0db453f335797d007a62aefdf78cef4d866670 Mon Sep 17 00:00:00 2001 From: Anindra Das Bivas Date: Wed, 14 Oct 2026 16:03:59 +0600 Subject: [PATCH 10/19] updated array_filter for negative values and string values --- models/CourseModel.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/CourseModel.php b/models/CourseModel.php index 3bb1b6c91c..8cf6c9b9a7 100644 --- a/models/CourseModel.php +++ b/models/CourseModel.php @@ -166,7 +166,7 @@ public static function get_post_types( $post ) { public static function get_courses( $excludes = array(), $post_status = array( 'publish' ) ) { global $wpdb; - $excludes = array_filter( array_map( 'absint', (array) $excludes ) ); + $excludes = array_values( array_filter( array_map( 'intval', (array) $excludes ), fn( $id ) => $id > 0 ) ); $exclude_query = ''; $post_status = array_intersect( $post_status, self::get_status_list() ); From f10970f9f1438b9a61a340c616f2b97daf83c444 Mon Sep 17 00:00:00 2001 From: Anindra Das Bivas Date: Wed, 14 Oct 2026 16:06:11 +0600 Subject: [PATCH 11/19] added negative value check on filter --- models/QuizModel.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/models/QuizModel.php b/models/QuizModel.php index f1d3beb1a4..dc8668a5fa 100644 --- a/models/QuizModel.php +++ b/models/QuizModel.php @@ -976,7 +976,7 @@ public static function get_answers_by_quiz_question( $question_id, $rand = false public static function get_quiz_answers_by_attempt_id( $attempt_id, $add_index = false ) { global $wpdb; - $ids = array_values( array_filter( array_map( 'intval', (array) $attempt_id ) ) ); + $ids = array_values( array_filter( array_map( 'intval', (array) $attempt_id ), fn( $id ) => $id > 0 ) ); if ( empty( $ids ) ) { // Prevent empty. @@ -1502,7 +1502,7 @@ public function get_first_or_last_attempt( int $quiz_id, int $user_id = 0, strin */ public static function get_quiz_count_by_course( $course_id ) { global $wpdb; - $course_ids = array_values( array_filter( array_map( 'intval', (array) $course_id ) ) ); + $course_ids = array_values( array_filter( array_map( 'intval', (array) $course_id ), fn( $id ) => $id > 0 ) ); if ( empty( $course_ids ) ) { return 0; } From 75900836c1d2e5a4a2e1427c10842d52295c319f Mon Sep 17 00:00:00 2001 From: Anindra Das Bivas Date: Fri, 23 Oct 2026 11:53:28 +0600 Subject: [PATCH 12/19] updated to use fluent query and added test cases --- models/CourseModel.php | 39 +++++++------------------------ tests/phpunit/CourseModelTest.php | 36 ++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 30 deletions(-) diff --git a/models/CourseModel.php b/models/CourseModel.php index 10fd52f15e..8670ee5bce 100644 --- a/models/CourseModel.php +++ b/models/CourseModel.php @@ -18,6 +18,7 @@ use TUTOR\Lesson; use Tutor\Ecommerce\Tax; use InvalidArgumentException; +use Tutor\Core\DB; use Tutor\Helpers\QueryHelper; use TUTOR\User; use TUTOR_ASSIGNMENTS\Assignments; @@ -164,43 +165,21 @@ public static function get_post_types( $post ) { * @return array|null|object */ public static function get_courses( $excludes = array(), $post_status = array( 'publish' ) ) { - global $wpdb; - $excludes = array_values( array_filter( array_map( 'intval', (array) $excludes ), fn( $id ) => $id > 0 ) ); - $exclude_query = ''; - $post_status = array_intersect( $post_status, self::get_status_list() ); + $excludes = array_values( array_filter( array_map( 'intval', (array) $excludes ), fn( $id ) => $id > 0 ) ); + $post_status = array_intersect( $post_status, self::get_status_list() ); if ( empty( $post_status ) ) { $post_status = array( 'publish' ); } - if ( count( $excludes ) ) { - $exclude_placeholders = QueryHelper::prepare_in_clause( $excludes ); - $exclude_query = "AND ID NOT IN({$exclude_placeholders})"; - } - - $post_status = QueryHelper::prepare_in_clause( $post_status ); - - //phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared - $query = $wpdb->get_results( - $wpdb->prepare( - "SELECT ID, - post_author, - post_title, - post_name, - post_status, - menu_order - FROM {$wpdb->posts} - WHERE post_status IN ({$post_status}) - {$exclude_query} - AND post_type = %s; - ", - self::POST_TYPE - ) - ); - //phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared + $query = DB::table( 'posts' ) + ->select( 'ID', 'post_author', 'post_title', 'post_name', 'post_status', 'menu_order' ) + ->where_in( 'post_status', $post_status ) + ->where( 'post_type', self::POST_TYPE ) + ->where_not_in( 'ID', $excludes ); - return $query; + return $query->get(); } /** diff --git a/tests/phpunit/CourseModelTest.php b/tests/phpunit/CourseModelTest.php index e48462e95a..4d5fdeb110 100644 --- a/tests/phpunit/CourseModelTest.php +++ b/tests/phpunit/CourseModelTest.php @@ -1,5 +1,6 @@ assertTrue( $res ); + } + + /** + * Get Courses test case added. + * + * @return void + */ + public function test_get_courses() { + + $course1 = self::factory()->post->create( + array( + 'post_type' => tutor()->course_post_type, + 'post_title' => 'Test Course', + 'post_status' => 'publish', + ) + ); + $course2 = self::factory()->post->create( + array( + 'post_type' => tutor()->course_post_type, + 'post_title' => 'Test Course 2', + 'post_status' => 'draft', + ) + ); + + $courses = CourseModel::get_courses(); + + $this->assertEquals( 1, count( $courses ) ); + + $courses = CourseModel::get_courses( array(), array( 'publish', 'draft' ) ); + + $this->assertEquals( 2, count( $courses ) ); + + $courses = CourseModel::get_courses( array( $course2 ) ); + $this->assertEquals( 1, count( $courses ) ); + $this->assertEquals( $course1, $courses[0]->ID ); } } From e8e0588dd0dc488dbd242a890c7c8c408d4ae829 Mon Sep 17 00:00:00 2001 From: Anindra Das Bivas Date: Fri, 23 Oct 2026 12:53:09 +0600 Subject: [PATCH 13/19] updated quiz model queries and added unit test --- models/QuizModel.php | 51 +++++++++++----------------- tests/phpunit/QuizModelTest.php | 59 +++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+), 32 deletions(-) create mode 100644 tests/phpunit/QuizModelTest.php diff --git a/models/QuizModel.php b/models/QuizModel.php index 758e32a333..3e976c9072 100644 --- a/models/QuizModel.php +++ b/models/QuizModel.php @@ -14,6 +14,7 @@ use Tutor\Cache\TutorCache; use Tutor\Components\Badge; +use Tutor\Core\DB; use TUTOR\Course_List; use Tutor\Helpers\DateTimeHelper; use Tutor\Helpers\QueryHelper; @@ -974,7 +975,6 @@ public static function get_answers_by_quiz_question( $question_id, $rand = false * @return array|null|object */ public static function get_quiz_answers_by_attempt_id( $attempt_id, $add_index = false ) { - global $wpdb; $ids = array_values( array_filter( array_map( 'intval', (array) $attempt_id ), fn( $id ) => $id > 0 ) ); @@ -983,22 +983,13 @@ public static function get_quiz_answers_by_attempt_id( $attempt_id, $add_index = return array(); } - $placeholders = implode( ', ', array_fill( 0, count( $ids ), '%d' ) ); - - //phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared - $results = $wpdb->get_results( - $wpdb->prepare( - "SELECT answers.*, - question.* - FROM {$wpdb->prefix}tutor_quiz_attempt_answers answers - LEFT JOIN {$wpdb->prefix}tutor_quiz_questions question - ON answers.question_id = question.question_id - WHERE answers.quiz_attempt_id IN ({$placeholders}) - ORDER BY attempt_answer_id ASC;", - $ids - ) - ); - //phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared + $results = DB::table( 'tutor_quiz_attempt_answers' ) + ->as( 'answers' ) + ->select( 'answers.*', 'question.*' ) + ->join( 'tutor_quiz_questions as question', 'answers.question_id', '=', 'question.question_id' ) + ->where_in( 'answers.quiz_attempt_id', $ids ) + ->order_by( 'answers.attempt_answer_id' ) + ->get(); if ( $add_index ) { $new_array = array(); @@ -1501,27 +1492,23 @@ public function get_first_or_last_attempt( int $quiz_id, int $user_id = 0, strin * @return int */ public static function get_quiz_count_by_course( $course_id ) { - global $wpdb; $course_ids = array_values( array_filter( array_map( 'intval', (array) $course_id ), fn( $id ) => $id > 0 ) ); if ( empty( $course_ids ) ) { return 0; } - $placeholders = implode( ', ', array_fill( 0, count( $course_ids ), '%d' ) ); + $topic_subquery = DB::table( 'posts' ) + ->select( 'ID' ) + ->where( 'post_type', tutor()->topics_post_type ) + ->where_in( 'post_parent', $course_ids ) + ->where( 'post_status', 'publish' ) + ->to_sql(); - //phpcs:disable - return (int) $wpdb->get_var( - $wpdb->prepare( - "SELECT COUNT(ID) FROM {$wpdb->posts} - WHERE post_parent IN ( - SELECT ID FROM {$wpdb->posts} - WHERE post_type = 'topics' AND post_parent IN ({$placeholders}) AND post_status = 'publish' - ) - AND post_type = %s AND post_status = 'publish'", - array( ...$course_ids, tutor()->quiz_post_type ) - ) - ); - //phpcs:enable + return DB::table( 'posts' ) + ->where_raw( "post_parent IN ({$topic_subquery})" ) + ->where( 'post_type', tutor()->quiz_post_type ) + ->where( 'post_status', 'publish' ) + ->count( 'ID' ); } /** diff --git a/tests/phpunit/QuizModelTest.php b/tests/phpunit/QuizModelTest.php new file mode 100644 index 0000000000..b34a415ed9 --- /dev/null +++ b/tests/phpunit/QuizModelTest.php @@ -0,0 +1,59 @@ +post->create( + array( + 'post_type' => tutor()->course_post_type, + 'post_status' => 'publish', + 'post_title' => 'Test Course', + ) + ); + + $topic_id = self::factory()->post->create( + array( + 'post_type' => tutor()->topics_post_type, + 'post_status' => 'publish', + 'post_parent' => $course_id, + 'post_title' => 'Topic', + 'comment_status' => 'closed', + ) + ); + + $quiz_id = self::factory()->post->create( + array( + 'post_type' => tutor()->quiz_post_type, + 'post_status' => 'publish', + 'post_parent' => $topic_id, + 'post_title' => 'Quiz', + ) + ); + + $count = QuizModel::get_quiz_count_by_course( $course_id ); + + $this->assertEquals( 1, $count ); + } +} From 8991354c571419a3c4fa6bfaac0ab2ad1f22ec65 Mon Sep 17 00:00:00 2001 From: Anindra Das Bivas Date: Fri, 23 Oct 2026 13:38:06 +0600 Subject: [PATCH 14/19] added fluent query in --- models/EnrollmentModel.php | 40 +++++++++++++------------------------- 1 file changed, 13 insertions(+), 27 deletions(-) diff --git a/models/EnrollmentModel.php b/models/EnrollmentModel.php index 1ad27328db..d8b44ab068 100644 --- a/models/EnrollmentModel.php +++ b/models/EnrollmentModel.php @@ -13,6 +13,7 @@ defined( 'ABSPATH' ) || exit; use Tutor\Cache\TutorCache; +use Tutor\Core\DB; use TUTOR\Course; use Tutor\Helpers\QueryHelper; use TUTOR\User; @@ -282,7 +283,6 @@ public static function get_enrolment_by_enrol_id( $enrol_id = 0 ) { * @return object|mixed */ public static function get_enrolled_data( $user_id = 0, $course_id = 0, $status = self::STATUS_COMPLETED ) { - global $wpdb; $user_id = absint( $user_id ); $course_id = absint( $course_id ); @@ -290,34 +290,20 @@ public static function get_enrolled_data( $user_id = 0, $course_id = 0, $status return null; } - $status_clause = $status ? $wpdb->prepare( 'AND post_status = %s ', $status ) : ''; - // If course ID provided, it will return single row data. - if ( $course_id > 0 && tutor()->course_post_type === get_post_type( $course_id ) ) { - return $wpdb->get_row( - $wpdb->prepare( - "SELECT * FROM {$wpdb->posts} - WHERE post_type = %s - AND post_parent = %d - {$status_clause} - AND post_author = %d;", - self::POST_TYPE, - $course_id, - $user_id - ) - ); + if ( $course_id > 0 && in_array( get_post_type( $course_id ), array( tutor()->course_post_type, tutor()->bundle_post_type ), true ) ) { + return DB::table( 'posts' ) + ->where( 'post_type', self::POST_TYPE ) + ->where( 'post_parent', $course_id ) + ->where( 'post_author', $user_id ) + ->where( 'post_status', $status ?? self::STATUS_COMPLETED ) + ->first(); } else { - // Return all enrolled data by user ID. - return $wpdb->get_results( - $wpdb->prepare( - "SELECT * FROM {$wpdb->posts} - WHERE post_type = %s - {$status_clause} - AND post_author = %d;", - self::POST_TYPE, - $user_id - ) - ); + return DB::table( 'posts' ) + ->where( 'post_type', self::POST_TYPE ) + ->where( 'post_author', $user_id ) + ->where( 'post_status', $status ?? self::STATUS_COMPLETED ) + ->get(); } } From 67c455e669a03f45d2e8d4c3e976d089edaef322 Mon Sep 17 00:00:00 2001 From: Anindra Das Bivas Date: Fri, 23 Oct 2026 13:40:50 +0600 Subject: [PATCH 15/19] removed unused import --- tests/phpunit/CourseModelTest.php | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/tests/phpunit/CourseModelTest.php b/tests/phpunit/CourseModelTest.php index 4d5fdeb110..36686ca5d6 100644 --- a/tests/phpunit/CourseModelTest.php +++ b/tests/phpunit/CourseModelTest.php @@ -1,8 +1,15 @@ - Date: Mon, 26 Oct 2026 13:36:25 +0600 Subject: [PATCH 16/19] reverted subquery to original --- models/QuizModel.php | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/models/QuizModel.php b/models/QuizModel.php index 3e976c9072..497ad64395 100644 --- a/models/QuizModel.php +++ b/models/QuizModel.php @@ -1492,23 +1492,27 @@ public function get_first_or_last_attempt( int $quiz_id, int $user_id = 0, strin * @return int */ public static function get_quiz_count_by_course( $course_id ) { + global $wpdb; $course_ids = array_values( array_filter( array_map( 'intval', (array) $course_id ), fn( $id ) => $id > 0 ) ); if ( empty( $course_ids ) ) { return 0; } - $topic_subquery = DB::table( 'posts' ) - ->select( 'ID' ) - ->where( 'post_type', tutor()->topics_post_type ) - ->where_in( 'post_parent', $course_ids ) - ->where( 'post_status', 'publish' ) - ->to_sql(); + $placeholders = implode( ', ', array_fill( 0, count( $course_ids ), '%d' ) ); - return DB::table( 'posts' ) - ->where_raw( "post_parent IN ({$topic_subquery})" ) - ->where( 'post_type', tutor()->quiz_post_type ) - ->where( 'post_status', 'publish' ) - ->count( 'ID' ); + //phpcs:disable + return (int) $wpdb->get_var( + $wpdb->prepare( + "SELECT COUNT(ID) FROM {$wpdb->posts} + WHERE post_parent IN ( + SELECT ID FROM {$wpdb->posts} + WHERE post_type = 'topics' AND post_parent IN ({$placeholders}) AND post_status = 'publish' + ) + AND post_type = %s AND post_status = 'publish'", + array( ...$course_ids, tutor()->quiz_post_type ) + ) + ); + //phpcs:enable } /** From edff9ef2ccc05c8fd53a15cf20b29165a1b531ad Mon Sep 17 00:00:00 2001 From: Anindra Das Bivas Date: Tue, 27 Oct 2026 12:33:10 +0600 Subject: [PATCH 17/19] Updated Enrollment model query to use DB and added unit tests --- models/EnrollmentModel.php | 30 ++---- tests/phpunit/EnrollmentModelTest.php | 129 ++++++++++++++++++++++++++ 2 files changed, 136 insertions(+), 23 deletions(-) create mode 100644 tests/phpunit/EnrollmentModelTest.php diff --git a/models/EnrollmentModel.php b/models/EnrollmentModel.php index d8b44ab068..e3833f2598 100644 --- a/models/EnrollmentModel.php +++ b/models/EnrollmentModel.php @@ -320,22 +320,9 @@ public static function get_enrolled_data( $user_id = 0, $course_id = 0, $status * @return bool */ public static function update_enrollments( string $status, array $enrollment_ids, bool $trigger_hook = true ): bool { - global $wpdb; - $enrollment_ids_in = QueryHelper::prepare_in_clause( $enrollment_ids ); - $status = 'complete' === $status ? 'completed' : $status; - $post_table = $wpdb->posts; + $status = 'complete' === $status ? 'completed' : $status; - //phpcs:disable - $wpdb->query( - $wpdb->prepare( - "UPDATE {$post_table} - SET post_status = %s - WHERE ID IN ($enrollment_ids_in) - ", - $status - ) - ); - //phpcs:enable + DB::table( 'posts' )->where_in( 'ID', $enrollment_ids )->update( array( 'post_status' => $status ) ); if ( $trigger_hook ) { // Run action hook. @@ -358,13 +345,10 @@ public static function update_enrollments( string $status, array $enrollment_ids * @return bool */ public static function delete_enrollment_record( int $student_id, int $course_id ): bool { - return QueryHelper::delete( - 'posts', - array( - 'post_author' => $student_id, - 'post_parent' => $course_id, - 'post_type' => self::POST_TYPE, - ) - ); + return DB::table( 'posts' ) + ->where( 'post_author', $student_id ) + ->where( 'post_parent', $course_id ) + ->where( 'post_type', self::POST_TYPE ) + ->delete(); } } diff --git a/tests/phpunit/EnrollmentModelTest.php b/tests/phpunit/EnrollmentModelTest.php new file mode 100644 index 0000000000..abd88e3156 --- /dev/null +++ b/tests/phpunit/EnrollmentModelTest.php @@ -0,0 +1,129 @@ +post->create( + array( + 'post_type' => EnrollmentModel::POST_TYPE, + 'post_status' => EnrollmentModel::STATUS_COMPLETED, + 'post_author' => $user_id, + 'post_parent' => $course_id, + ) + ); + $enroll_id_2 = self::factory()->post->create( + array( + 'post_type' => EnrollmentModel::POST_TYPE, + 'post_status' => EnrollmentModel::STATUS_COMPLETED, + 'post_author' => $user_id, + 'post_parent' => $course_id, + ) + ); + + $enroll_id_3 = self::factory()->post->create( + array( + 'post_type' => EnrollmentModel::POST_TYPE, + 'post_status' => EnrollmentModel::STATUS_COMPLETED, + 'post_author' => $user_id, + 'post_parent' => $course_id, + ) + ); + + $total_enrollments = EnrollmentModel::get_enrolled_data( $user_id, $course_id ); + + $this->assertEquals( 3, count( $total_enrollments ) ); + + $enrollment = EnrollmentModel::delete_enrollment_record( $user_id, $course_id ); + $this->assertTrue( $enrollment ); + + $total_enrollments = EnrollmentModel::get_enrolled_data( $user_id, $course_id ); + $this->assertEquals( 0, count( $total_enrollments ) ); + } + + /** + * Test get enrolled data. + * + * @return void + */ + public function test_get_enrolled_data() { + $user_id = 1; + $course_id = 1; + + self::factory()->post->create( + array( + 'post_type' => EnrollmentModel::POST_TYPE, + 'post_status' => EnrollmentModel::STATUS_COMPLETED, + 'post_author' => $user_id, + 'post_parent' => $course_id, + ) + ); + + $enrollments = EnrollmentModel::get_enrolled_data( $user_id, $course_id ); + + $this->assertEquals( 1, count( $enrollments ) ); + } + + /** + * Test update enroll data. + * + * @return void + */ + public function test_update_enroll_data() { + $user_id = 1; + $course_id = 1; + + $enroll_id_1 = self::factory()->post->create( + array( + 'post_type' => EnrollmentModel::POST_TYPE, + 'post_status' => EnrollmentModel::STATUS_COMPLETED, + 'post_author' => $user_id, + 'post_parent' => $course_id, + ) + ); + + $update = EnrollmentModel::update_enrollments( EnrollmentModel::STATUS_CANCEL, array( $enroll_id_1 ), false ); + + $this->assertTrue( $update ); + + $updated_enrollment = EnrollmentModel::get_enrolled_data( $user_id, $course_id, EnrollmentModel::STATUS_CANCEL ); + $this->assertEquals( EnrollmentModel::STATUS_CANCEL, $updated_enrollment[0]->post_status ); + + $enroll_id_2 = self::factory()->post->create( + array( + 'post_type' => EnrollmentModel::POST_TYPE, + 'post_status' => EnrollmentModel::STATUS_COMPLETED, + 'post_author' => $user_id, + 'post_parent' => $course_id, + ) + ); + + $update = EnrollmentModel::update_enrollments( EnrollmentModel::STATUS_PENDING, array( $enroll_id_1, $enroll_id_2 ), false ); + + $this->assertTrue( $update ); + + $updated_enrollment = EnrollmentModel::get_enrolled_data( $user_id, $course_id, EnrollmentModel::STATUS_PENDING ); + $this->assertEquals( EnrollmentModel::STATUS_PENDING, $updated_enrollment[0]->post_status ); + $this->assertEquals( EnrollmentModel::STATUS_PENDING, $updated_enrollment[1]->post_status ); + } +} From 78399b345cb93c423a27c18a07172d3f8b44a6eb Mon Sep 17 00:00:00 2001 From: Anindra Das Bivas Date: Tue, 27 Oct 2026 15:32:49 +0600 Subject: [PATCH 18/19] Added BillingModel unit test --- tests/phpunit/BillingModelTest.php | 72 ++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 tests/phpunit/BillingModelTest.php diff --git a/tests/phpunit/BillingModelTest.php b/tests/phpunit/BillingModelTest.php new file mode 100644 index 0000000000..257557bf08 --- /dev/null +++ b/tests/phpunit/BillingModelTest.php @@ -0,0 +1,72 @@ +billing_model = new BillingModel(); + } + + /** + * Test insert billing info. + * + * @return void + */ + public function test_insert_billing_info() { + $user_id = 1; + $billing_info = array( + 'user_id' => $user_id, + 'billing_first_name' => 'John', + 'billing_last_name' => 'Doe', + 'billing_email' => 'john.doe@example.com', + 'billing_phone' => '1234567890', + 'billing_zip_code' => '123456', + 'billing_address' => '123 Main St', + 'billing_country' => 'US', + 'billing_state' => 'CA', + 'billing_city' => 'Los Angeles', + ); + + $billing_id = $this->billing_model->insert( $billing_info ); + $this->assertGreaterThan( 0, $billing_id ); + + $billing_info = $this->billing_model->get_info( $user_id ); + $this->assertEquals( $user_id, $billing_info->user_id ); + } + + /** + * Tear down test environment. + + * @return void + */ + public function tearDown() { + parent::tearDown(); + unset( $this->billing_model ); + } +} From f753ae2d2fb9eed07ee7e58c9fd78fc59ce31e5d Mon Sep 17 00:00:00 2001 From: Anindra Das Bivas Date: Tue, 27 Oct 2026 17:55:22 +0600 Subject: [PATCH 19/19] update billing model to use DB and added test cases --- models/BillingModel.php | 32 ++++++------------- tests/phpunit/BillingModelTest.php | 49 ++++++++++++++++++++++++++++-- 2 files changed, 57 insertions(+), 24 deletions(-) diff --git a/models/BillingModel.php b/models/BillingModel.php index 10512fe38f..ce412c7217 100644 --- a/models/BillingModel.php +++ b/models/BillingModel.php @@ -10,7 +10,7 @@ namespace Tutor\Models; -use Tutor\Helpers\QueryHelper; +use Tutor\Core\DB; /** * Billing model class for performing billing functionalities @@ -77,12 +77,7 @@ public function get_required_fields() { * @return int The ID of the inserted row on success, or 0 on failure. */ public function insert( $data ) { - global $wpdb; - - return QueryHelper::insert( - "{$wpdb->prefix}tutor_customers", - $data, - ); + return DB::table( 'tutor_customers' )->insert( $data ); } /** @@ -94,13 +89,14 @@ public function insert( $data ) { * @return bool True on success, false on failure. */ public function update( $data, $where ) { - global $wpdb; + $column = ''; + $value = null; + if ( tutor_utils()->count( $where ) ) { + $column = array_keys( $where )[0]; + $value = $where[ $column ]; + } - return QueryHelper::update( - "{$wpdb->prefix}tutor_customers", - $data, - $where, - ); + return DB::table( 'tutor_customers' )->where( $column, $value )->update( $data ); } /** @@ -111,15 +107,7 @@ public function update( $data, $where ) { * @return object|false The billing info as an object if found, or false if not found. */ public function get_info( $user_id ) { - global $wpdb; - - $billing_info = QueryHelper::get_row( - "{$wpdb->prefix}tutor_customers", - array( - 'user_id' => $user_id, - ), - 'id' - ); + $billing_info = DB::table( 'tutor_customers' )->where( 'user_id', $user_id )->first(); return $billing_info; } diff --git a/tests/phpunit/BillingModelTest.php b/tests/phpunit/BillingModelTest.php index 257557bf08..9b9c7ca635 100644 --- a/tests/phpunit/BillingModelTest.php +++ b/tests/phpunit/BillingModelTest.php @@ -28,7 +28,9 @@ class BillingModelTest extends \WP_UnitTestCase { * * @return void */ - public function setUp() { + public function setUp(): void { + global $table_prefix; + $table_prefix = 'bivas_'; parent::setUp(); $this->billing_model = new BillingModel(); } @@ -60,12 +62,55 @@ public function test_insert_billing_info() { $this->assertEquals( $user_id, $billing_info->user_id ); } + /** + * Test update billing info. + * + * @return void + */ + public function test_update_billing_info() { + $user_id = 1; + $billing_info = array( + 'user_id' => $user_id, + 'billing_first_name' => 'John', + 'billing_last_name' => 'Doe', + 'billing_email' => 'john.doe@example.com', + 'billing_phone' => '1234567890', + 'billing_zip_code' => '123456', + 'billing_address' => '123 Main St', + 'billing_country' => 'US', + 'billing_state' => 'CA', + 'billing_city' => 'Los Angeles', + ); + + $billing_id = $this->billing_model->insert( $billing_info ); + $this->assertGreaterThan( 0, $billing_id ); + + $billing_info = array( + 'billing_first_name' => 'John', + 'billing_last_name' => 'Snow', + 'billing_email' => 'john.doe@example.com', + 'billing_phone' => '1234567890', + 'billing_zip_code' => '123456', + 'billing_address' => '123 Main St', + 'billing_country' => 'US', + 'billing_state' => 'CA', + 'billing_city' => 'Dhaka', + ); + + $billing_id = $this->billing_model->update( $billing_info, array( 'user_id' => $user_id ) ); + $this->assertGreaterThan( 0, $billing_id ); + + $billing_info = $this->billing_model->get_info( $user_id ); + $this->assertEquals( 'Snow', $billing_info->billing_last_name ); + $this->assertEquals( 'Dhaka', $billing_info->billing_city ); + } + /** * Tear down test environment. * @return void */ - public function tearDown() { + public function tearDown(): void { parent::tearDown(); unset( $this->billing_model ); }