From 3ab289d4869874af26d09498e90f7afe4408089d Mon Sep 17 00:00:00 2001 From: Sanjana Khan Date: Fri, 24 Jul 2026 04:03:10 +0600 Subject: [PATCH 1/3] =?UTF-8?q?Fix(=F0=9F=9B=A0=EF=B8=8F):=20Update=20cour?= =?UTF-8?q?se=20content=20if=20main=20author=20is=20changed=20and=20multi-?= =?UTF-8?q?instructor=20addon=20is=20disabled.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- classes/Course.php | 9 +++++++++ models/CourseModel.php | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/classes/Course.php b/classes/Course.php index 72968bf5a4..5b7a469c87 100644 --- a/classes/Course.php +++ b/classes/Course.php @@ -1998,6 +1998,15 @@ public function save_course_meta( $post_ID, $post ) { } } + /** + * Update course content if main author is changed and multi-instructor addon is disabled. + * + * @since 4.0.3 + */ + if ( ! $attached && ! tutor_utils()->is_addon_enabled( 'tutor-multi-instructors' ) ) { + CourseModel::update_course_content_author( $post_ID, (int) $author_id ); + } + do_action( 'tutor_save_course_after', $post_ID, $post ); } diff --git a/models/CourseModel.php b/models/CourseModel.php index 26734513ae..82c6813a64 100644 --- a/models/CourseModel.php +++ b/models/CourseModel.php @@ -1750,4 +1750,38 @@ public static function get_course_duration_in_seconds( int $course_id ): int { + ( $duration['durationMinutes'] * MINUTE_IN_SECONDS ) + ( $duration['durationSeconds'] ); } + + /** + * Update the post_author of a course's content (topics, lessons, + * quizzes and assignments) to a given user. + * + * @since 4.0.3 + * + * @param int $course_id course id. + * @param int $author_id new author (user) id. + * + * @return bool + */ + public static function update_course_content_author( int $course_id, int $author_id ): bool { + global $wpdb; + + $default_post_types = array( tutor()->lesson_post_type, tutor()->quiz_post_type ); + $content_post_types = array_unique( apply_filters( 'tutor_course_contents_post_types', $default_post_types ) ); + + $post_types = QueryHelper::prepare_in_clause( $content_post_types ); + + // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared + $query = $wpdb->prepare( + "UPDATE {$wpdb->posts} + SET post_author = %d + WHERE ( post_parent = %d AND post_type = 'topics' ) + OR ( post_type IN ({$post_types}) AND post_parent IN ( + SELECT ID FROM {$wpdb->posts} WHERE post_parent = %d AND post_type = 'topics' + ) )", + array_merge( array( $author_id, $course_id ), $content_post_types, array( $course_id ) ) + ); + // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared + + return (bool) $wpdb->query( $query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared + } } From 68b9a7b5e1f0e6b6893a4fded8f575925b7618c6 Mon Sep 17 00:00:00 2001 From: sanjana4khan Date: Fri, 24 Jul 2026 16:40:37 +0600 Subject: [PATCH 2/3] If multi instructor is disabled then only main author will get a mail after a student submits an assignment. --- classes/Course.php | 7 +++-- models/CourseModel.php | 60 +++++++++++++++++++++++++++++++++--------- 2 files changed, 53 insertions(+), 14 deletions(-) diff --git a/classes/Course.php b/classes/Course.php index 5b7a469c87..b727af6a0a 100644 --- a/classes/Course.php +++ b/classes/Course.php @@ -2001,10 +2001,13 @@ public function save_course_meta( $post_ID, $post ) { /** * Update course content if main author is changed and multi-instructor addon is disabled. * - * @since 4.0.3 + * @since 4.0.4 */ if ( ! $attached && ! tutor_utils()->is_addon_enabled( 'tutor-multi-instructors' ) ) { CourseModel::update_course_content_author( $post_ID, (int) $author_id ); + // Remove all existing instructors from the course and add the new one. + delete_metadata( 'user', 0, '_tutor_instructor_course_id', $post_ID, true ); + add_user_meta( $author_id, '_tutor_instructor_course_id', $post_ID ); } do_action( 'tutor_save_course_after', $post_ID, $post ); @@ -3117,7 +3120,7 @@ public function tutor_reset_course_progress() { * * @param integer $course_id course ID. * @param integer $user_id user ID. - * + * * @return void */ public function enroll_after_login_if_attempt( int $course_id, int $user_id ) { diff --git a/models/CourseModel.php b/models/CourseModel.php index 82c6813a64..763aebbf71 100644 --- a/models/CourseModel.php +++ b/models/CourseModel.php @@ -1765,23 +1765,59 @@ public static function get_course_duration_in_seconds( int $course_id ): int { public static function update_course_content_author( int $course_id, int $author_id ): bool { global $wpdb; + $content_ids = get_posts( + array( + 'post_parent' => $course_id, + 'post_type' => tutor()->topics_post_type, + 'fields' => 'ids', + 'posts_per_page' => -1, + 'post_status' => 'any', + ) + ); + + $content_ids = is_array( $content_ids ) ? $content_ids : array(); $default_post_types = array( tutor()->lesson_post_type, tutor()->quiz_post_type ); $content_post_types = array_unique( apply_filters( 'tutor_course_contents_post_types', $default_post_types ) ); - $post_types = QueryHelper::prepare_in_clause( $content_post_types ); + $primary_table = 'posts as course'; + $topic_table = 'posts as topic'; + $content_table = 'posts as content'; - // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared - $query = $wpdb->prepare( - "UPDATE {$wpdb->posts} - SET post_author = %d - WHERE ( post_parent = %d AND post_type = 'topics' ) - OR ( post_type IN ({$post_types}) AND post_parent IN ( - SELECT ID FROM {$wpdb->posts} WHERE post_parent = %d AND post_type = 'topics' - ) )", - array_merge( array( $author_id, $course_id ), $content_post_types, array( $course_id ) ) + $joined_data = QueryHelper::get_joined_data( + $primary_table, + array( + array( + 'type' => 'INNER', + 'table' => $topic_table, + 'on' => 'course.ID = topic.post_parent', + ), + array( + 'type' => 'INNER', + 'table' => $content_table, + 'on' => 'topic.ID = content.post_parent', + ), + ), + array( 'content.ID' ), + array( + 'course.ID' => $course_id, + 'content.post_type' => array( 'IN', $content_post_types ), + ), + array(), + '', + -1 ); - // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared - return (bool) $wpdb->query( $query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared + $content_ids = array_merge( $content_ids, wp_list_pluck( $joined_data['results'], 'ID' ) ); + $content_ids = array_filter( array_unique( array_map( 'absint', $content_ids ) ) ); + + if ( empty( $content_ids ) ) { + return false; + } + + return (bool) QueryHelper::update_where_in( + $wpdb->posts, + array( 'post_author' => $author_id ), + implode( ',', $content_ids ) + ); } } From e036dbb5d8147893bdcfc92688abfa5bbbf12008 Mon Sep 17 00:00:00 2001 From: sanjana4khan Date: Mon, 27 Jul 2026 15:48:58 +0600 Subject: [PATCH 3/3] =?UTF-8?q?Fix(=F0=9F=9B=A0)=20:=20Only=20accept=20the?= =?UTF-8?q?=20requested=20author=20override=20if=20it=20targets=20a=20real?= =?UTF-8?q?,approved=20instructor=20or=20admin?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- classes/Course.php | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/classes/Course.php b/classes/Course.php index b727af6a0a..e63bc49b6e 100644 --- a/classes/Course.php +++ b/classes/Course.php @@ -1968,10 +1968,21 @@ public function save_course_meta( $post_ID, $post ) { /** * Adding author to instructor automatically */ + $requested_author_id = Input::post( 'post_author_override', 0, Input::TYPE_INT ); - // Override post author id. - $author_id = isset( $_POST['post_author_override'] ) ? $_POST['post_author_override'] : $post->post_author; //phpcs:ignore - $attached = (int) $wpdb->get_var( + /** + * Only accept the requested author override if it targets a real,approved instructor or admin + * + * @since 4.0.4 + */ + $author_id = $post->post_author; + if ( $requested_author_id && ( User::is_admin() || User::is_instructor() ) ) { + if ( User::is_admin( $requested_author_id ) || User::is_instructor( $requested_author_id, true ) ) { + $author_id = $requested_author_id; + } + } + + $attached = (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(umeta_id) FROM {$wpdb->usermeta} WHERE user_id = %d