Source: includes/class-sensei-teacher.php

<?php

use Sensei\Internal\Services\Progress_Storage_Settings;

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

/**
 * Sensei Teacher class
 *
 * All functionality pertaining to the teacher role.
 *
 * @package Users
 * @author Automattic
 * @since 1.0.0
 */
class Sensei_Teacher {

	/**
	 * $teacher_role
	 *
	 * Keeps a reference to the teacher role object
	 *
	 * @access protected
	 * @since 1.8.0
	 */
	protected $teacher_role;

	/**
	 * $token
	 *
	 * Keeps a reference to the global sensei token
	 *
	 * @access protected
	 * @since 1.8.0
	 */
	public $token;

	/**
	 * The nonce name when submitting a new message.
	 *
	 * @var string
	 */
	const NONCE_FIELD_NAME = 'sensei_meta_nonce';

	/**
	 * The nonce action name when submitting a new message.
	 */
	const NONCE_ACTION_NAME = 'sensei_save_data';

	/**
	 * Sensei_Teacher::__constructor
	 *
	 * Constructor Function
	 *
	 * @since 1.8.0
	 * @access public
	 */
	public function __construct() {

		add_action( 'add_meta_boxes', [ $this, 'add_teacher_meta_boxes' ], 10, 2 );
		add_action( 'save_post', [ $this, 'save_teacher_meta_box' ] );

		add_filter( 'parse_query', array( $this, 'limit_teacher_edit_screen_post_types' ) );
		add_filter( 'pre_get_posts', array( $this, 'course_analysis_teacher_access_limit' ) );
		add_filter( 'wp_count_posts', array( $this, 'list_table_counts' ), 10, 3 );

		add_action( 'pre_get_posts', array( $this, 'filter_queries' ) );

		// Filter the learners on the Students screen to only show those associated with the current teacher.
		add_filter( 'sensei_learners_query', array( $this, 'filter_learners_query' ) );

		// filter the quiz submissions
		add_filter( 'sensei_check_for_activity', array( $this, 'filter_grading_activity_queries' ) );

		// grading totals count only those belonging to the teacher
		add_filter( 'sensei_count_statuses_args', array( $this, 'limit_grading_totals' ) );

		// show the courses owned by a user on his author archive page
		add_filter( 'pre_get_posts', array( $this, 'add_courses_to_author_archive' ) );

		// notify admin when a teacher creates a course
		add_action( 'transition_post_status', array( $this, 'notify_admin_teacher_course_creation' ), 10, 3 );

		// limit the analysis view to only the users taking courses belong to this teacher
		add_filter( 'sensei_analysis_overview_filter_users', array( $this, 'limit_analysis_learners' ), 5, 1 );

		// give teacher access to question post type
		add_filter( 'sensei_lesson_quiz_questions', array( $this, 'allow_teacher_access_to_questions' ), 20, 2 );

		// Teacher column on the courses list on the admin edit screen
		add_filter( 'manage_course_posts_columns', array( $this, 'course_column_heading' ), 10, 1 );
		add_filter( 'manage_course_posts_custom_column', array( $this, 'course_column_data' ), 10, 2 );

		// admin edit messages query limit teacher
		add_filter( 'pre_get_posts', array( $this, 'limit_edit_messages_query' ) );

		// add filter by teacher on courses list
		add_action( 'restrict_manage_posts', array( $this, 'course_teacher_filter_options' ) );
		add_filter( 'request', array( $this, 'teacher_filter_query_modify' ) );

		// Handle media library restrictions
		add_filter( 'request', array( $this, 'restrict_media_library' ), 10, 1 );
		add_filter( 'ajax_query_attachments_args', array( $this, 'restrict_media_library_modal' ), 10, 1 );

		// update lesson owner to course teacher before insert
		add_filter( 'wp_insert_post_data', array( $this, 'update_lesson_teacher' ), 99, 2 );

		// If a Teacher logs in, redirect to /wp-admin/
		add_filter( 'wp_login', array( $this, 'teacher_login_redirect' ), 10, 2 );

		add_action( 'admin_menu', array( $this, 'restrict_posts_menu_page' ), 10 );
		add_filter( 'pre_get_comments', array( $this, 'restrict_comment_moderation' ), 10, 1 );

		// If slug changed to custom, try to extract and save teacher id.
		add_action( 'edit_module', [ $this, 'extract_and_save_teacher_to_meta_from_slug' ] );

		add_action( 'sensei_course_new_teacher_assigned', [ $this, 'teacher_course_assigned_notification' ], 10, 2 );
	}

	/**
	 * Sensei_Teacher::create_teacher_role
	 *
	 * This function checks if the role exist, if not it creates it.
	 * for the teacher role
	 *
	 * @since 1.8.0
	 * @access public
	 * @return void
	 */
	public function create_role() {

		// check if the role exists
		$this->teacher_role = get_role( 'teacher' );

		// if the the teacher is not a valid WordPress role create it
		if ( ! is_a( $this->teacher_role, 'WP_Role' ) ) {
			// create the role
			$this->teacher_role = add_role( 'teacher', __( 'Teacher', 'sensei-lms' ) );
		}

		// add the capabilities before returning
		$this->add_capabilities();

	}

	/**
	 * Sensei_Teacher::add_capabilities
	 *
	 * @since 1.8.0
	 * @access protected
	 */
	protected function add_capabilities() {

		// if this is not a valid WP_Role object exit without adding anything
		if ( ! is_a( $this->teacher_role, 'WP_Role' ) || empty( $this->teacher_role ) ) {
			return;
		}

		/**
		 * Sensei teachers capabilities array filter
		 *
		 * These capabilities will be applied to the teacher role
		 *
		 * @param array $capabilities
		 * keys: (string) $cap_name => (bool) $grant
		 */
		$caps = apply_filters(
			'sensei_teacher_role_capabilities',
			array(
				// General access rules
				'read'                           => true,
				'manage_sensei_grades'           => true,
				'moderate_comments'              => true,
				'upload_files'                   => true,
				'edit_files'                     => true,

				// Lessons
				'publish_lessons'                => true,
				'manage_lesson_categories'       => true,
				'edit_lessons'                   => true,
				'edit_published_lessons'         => true,
				'edit_private_lessons'           => true,
				'read_private_lessons'           => true,
				'delete_published_lessons'       => true,

				// Courses
				'create_courses'                 => true,
				'publish_courses'                => false,
				'manage_course_categories'       => true,
				'edit_courses'                   => true,
				'edit_published_courses'         => true,
				'edit_private_courses'           => true,
				'read_private_courses'           => true,
				'delete_published_courses'       => true,

				// Modules
				'manage_modules'                 => true,

				// Quiz
				'publish_quizzes'                => true,
				'edit_quizzes'                   => true,
				'edit_published_quizzes'         => true,
				'edit_private_quizzes'           => true,
				'read_private_quizzes'           => true,

				// Questions
				'publish_questions'              => true,
				'manage_question_categories'     => true,
				'edit_questions'                 => true,
				'edit_published_questions'       => true,
				'edit_private_questions'         => true,
				'read_private_questions'         => true,

				// messages
				'publish_sensei_messages'        => true,
				'edit_sensei_messages'           => true,
				'edit_published_sensei_messages' => true,
				'edit_private_sensei_messages'   => true,
				'read_private_sensei_messages'   => true,

				// Comments -
				// Necessary cap so Teachers can moderate comments
				// on their own lessons. We restrict access to other
				// post types in $this->restrict_posts_menu_page()
				'edit_posts'                     => true,

			)
		);

		foreach ( $caps as $cap => $grant ) {

			// load the capability on to the teacher role.
			$this->teacher_role->add_cap( $cap, $grant );

		}

	}

	/**
	 * Sensei_Teacher::teacher_meta_box
	 *
	 * Add the teacher meta_box to the course post type edit screen
	 *
	 * @since 1.8.0
	 * @access public
	 * @parameter string $post_type
	 * @parameter WP_Post $post
	 * @return void
	 */
	public function add_teacher_meta_boxes( $post ) {

		if ( ! current_user_can( 'manage_options' ) ) {
			return;
		}
		add_meta_box(
			'sensei-teacher',
			__( 'Teacher', 'sensei-lms' ),
			array( $this, 'teacher_meta_box_content' ),
			'course',
			'side',
			'core',
			[
				'__block_editor_compatible_meta_box' => true,
				'__back_compat_meta_box'             => true,
			]
		);

	}

	/**
	 * Sensei_Teacher::teacher_meta_box_content
	 *
	 * Render the teacher meta box markup
	 *
	 * @since 1.8.0
	 * @access public
	 * @parameters
	 */
	public function teacher_meta_box_content( $post ) {
		wp_nonce_field( self::NONCE_ACTION_NAME, self::NONCE_FIELD_NAME );

		// get the current author
		$current_author = $post->post_author;

		// get the users authorised to author courses
		$users = $this->get_teachers_and_authors_with_fields( [ 'ID', 'display_name' ] );

		?>
		<input type="hidden" name="post_author_override" value="<?php echo intval( $current_author ); ?>" />
		<input type="hidden" name="course_module_custom_slugs" />
		<select name="sensei-course-teacher-author" class="sensei course teacher">

			<?php foreach ( $users as $user_data ) { ?>

					<?php
						$user_id           = $user_data->ID;
						$user_display_name = $user_data->display_name;
					?>
					<option <?php selected( $current_author, $user_id, true ); ?> value="<?php echo esc_attr( $user_id ); ?>" >
						<?php echo esc_html( $user_display_name ); ?>
					</option>

				<?php
			}
			?>

		</select>

		<?php
	}

	/**
	 * Sensei_Teacher::get_teachers_and_authors
	 *
	 * Get a list of users who can author courses, lessons and quizes.
	 *
	 * @since 1.8.0
	 * @access private
	 * @parameters
	 * @return array $users user id array
	 */
	public function get_teachers_and_authors() {
		$edit_course_roles = [];
		foreach ( wp_roles()->roles as $role_slug => $role ) {
			if ( ! empty( $role['capabilities']['edit_courses'] ) ) {
				$edit_course_roles[] = $role_slug;
			}
		}

		return get_users(
			[
				'blog_id'  => $GLOBALS['blog_id'],
				'fields'   => 'any',
				'role__in' => $edit_course_roles,
			]
		);
	}

	/**
	 * Get list of users who can author courses, lessons, and quizzes.
	 * Optionally specify the fields to return from the DB.
	 *
	 * @since 3.13.4
	 * @access private
	 *
	 * @param  string|array $fields Fields to return from DB. Defaults to 'ID'.
	 * @return array
	 */
	public function get_teachers_and_authors_with_fields( $fields = 'ID' ) {
		$ids = $this->get_teachers_and_authors();

		return get_users(
			[
				'blog_id' => $GLOBALS['blog_id'],
				'include' => $ids,
				'fields'  => $fields,
			]
		);
	}

	/**
	 * Sensei_Teacher::save_teacher_meta_box
	 *
	 * Save the new teacher / author from the meta box form.
	 *
	 * Hooked into admin_init
	 *
	 * @since 1.8.0
	 * @access public
	 * @param int $course_id Course ID.
	 * @return void
	 */
	public function save_teacher_meta_box( $course_id ) {
		// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Do not change the nonce.
		if ( empty( $_POST[ self::NONCE_FIELD_NAME ] ) || ! wp_verify_nonce( wp_unslash( $_POST[ self::NONCE_FIELD_NAME ] ), self::NONCE_ACTION_NAME ) ) {
			return;
		}

		// check if this is a post from saving the teacher, if not exit early
		if ( ! isset( $_POST['sensei-course-teacher-author'] ) || ! isset( $_POST['post_ID'] ) ) {
			return;
		}

		// If a custom slug is of a module that belongs to another teacher from another course, don't process farther.
		if ( isset( $_POST['course_module_custom_slugs'] ) ) {
			$module_custom_slugs = json_decode( sanitize_text_field( wp_unslash( $_POST['course_module_custom_slugs'] ) ) );
			foreach ( $module_custom_slugs as $module_custom_slug ) {
				$course_name = self::is_module_in_use_by_different_course_and_teacher( $module_custom_slug, $course_id, absint( $_POST['sensei-course-teacher-author'] ) );
				if ( $course_name ) {
					return;
				}
			}
		}

		// don't fire this hook again
		remove_action( 'save_post', array( $this, 'save_teacher_meta_box' ) );

		$new_teacher = absint( $_POST['sensei-course-teacher-author'] );
		$this->save_teacher( $course_id, $new_teacher );
	}

	/**
	 * Sensei_Teacher::save_teacher
	 *
	 * Save the new teacher / author to course and all lessons
	 *
	 * @access public
	 * @param int $course_id  Course ID.
	 * @param int $new_teacher  Course ID.
	 * @return void
	 */
	public function save_teacher( $course_id, $new_teacher ) {
		$post = get_post( $course_id );

		// get the current teacher/author
		$current_teacher = absint( $post->post_author );

		// loop through all post lessons to update their authors as well
		$this->update_course_lessons_author( $course_id, $new_teacher );

		// do not do any processing if the selected author is the same as the current author
		if ( $current_teacher == $new_teacher ) {
			return;
		}

		// save the course  author
		$post_updates = array(
			'ID'          => $post->ID,
			'post_author' => $new_teacher,
		);

		wp_update_post( $post_updates );

		// ensure the modules are update so that then new teacher has access to them.
		self::update_course_modules_author( $course_id, $new_teacher );

		/**
		 * Fires when a new teacher is assigned to a course.
		 *
		 * @since 4.12.0
		 *
		 * @hook sensei_course_new_teacher_assigned
		 *
		 * @param {int} $new_teacher The ID of the new teacher.
		 * @param {int} $course_id   The ID of the course.
		 */
		do_action( 'sensei_course_new_teacher_assigned', $new_teacher, $course_id );
	}

	/**
	 * Check if the module with the slug provided is
	 * a part of any other course taught by a different teacher.
	 *
	 * @since 4.6.0
	 * @access private
	 *
	 * @param  string $module_slug Slug of the module to check for.
	 * @param  int    $course_id   Slugs of the modules to check for.
	 * @param  int    $teacher_id  Slugs of the modules to check for.
	 *
	 * @return string|boolean Returns the name of the first course it finds a match for, false otherwise.
	 */
	public static function is_module_in_use_by_different_course_and_teacher( $module_slug, $course_id, $teacher_id ) {
		$existing_module_by_slug = get_term_by( 'slug', $module_slug, 'module' );
		if ( $existing_module_by_slug ) {
			$args           = array(
				'post_type'      => 'course',
				'post_status'    => [ 'publish', 'draft', 'private' ],
				'posts_per_page' => -1,
				'tax_query'      => array( // phpcs:ignore WordPress.DB.SlowDBQuery
					array(
						'taxonomy' => 'module',
						'field'    => 'id',
						'terms'    => $existing_module_by_slug->term_id,
					),
				),
			);
			$module_courses = get_posts( $args );
			foreach ( $module_courses as $module_course ) {
				if ( intval( $module_course->post_author ) !== intval( $teacher_id ) &&
					intval( $course_id ) !== $module_course->ID ) {
					if ( user_can( $module_course->post_author, 'manage_options' ) && user_can( $teacher_id, 'manage_options' ) ) {
						continue;
					}
					return $module_course->post_title;
				}
			}
		}
		return false;
	}

	/**
	 * Update all the course terms set(selected) on the given course. Moving course term ownership to
	 * the new author. Making sure the course terms are maintained.
	 *
	 * This function also checks if terms are shared, with other courses
	 *
	 * @param $course_id
	 * @param $new_teacher_id
	 * @return void
	 */
	public static function update_course_modules_author( $course_id, $new_teacher_id ) {
		if ( empty( $course_id ) || empty( $new_teacher_id ) ) {
			return;
		}

		if ( ! taxonomy_exists( 'module' ) ) {
			// If for some reason the `module` taxonomy is not present (normally it is), register it now.
			// This mitigates a Jetpack-introduced bug.
			Sensei()->modules->setup_modules_taxonomy();
		}

		remove_filter( 'get_terms', array( Sensei()->modules, 'append_teacher_name_to_module' ), 70 );
		$modules = Sensei()->modules->get_course_modules( $course_id );
		add_filter( 'get_terms', array( Sensei()->modules, 'append_teacher_name_to_module' ), 70, 3 );

		if ( empty( $modules ) ) {
			return;
		}

		$lessons      = Sensei()->course->course_lessons( $course_id, null );
		$module_order = [];

		foreach ( $modules as $term ) {
			$term_author = Sensei_Core_Modules::get_term_author( $term->slug );

			if ( ! $term_author || intval( $new_teacher_id ) !== intval( $term_author->ID ) ) {
				$new_slug       = $new_teacher_id . '-' . sanitize_title( trim( $term->name ) );
				$search_slugs   = array();
				$search_slugs[] = $new_slug;

				// First, try to recycle an existing module.
				if ( user_can( $new_teacher_id, 'manage_options' ) ) {
					$admin_slug = sanitize_title( trim( $term->name ) );
					array_unshift( $search_slugs, $admin_slug );
					$new_slug = $admin_slug;
				}

				// Search for term to recycle.
				$new_term = false;

				foreach ( $search_slugs as $search_slug ) {
					$search_term = get_term_by( 'slug', $search_slug, 'module', ARRAY_A );

					if ( $search_term && ! is_wp_error( $search_term ) ) {
						$new_term = $search_term;
						break;
					}
				}

				if ( empty( $new_term ) ) {
					// Create new term and set it.
					$new_term = wp_insert_term(
						$term->name,
						'module',
						array(
							'slug' => $new_slug,
						)
					);
				}

				if ( is_wp_error( $new_term ) ) {
					// Something happened. Let's leave the module alone.
					continue;
				}

				$term_id        = $new_term['term_id'];
				$module_order[] = $term_id;

				Sensei_Core_Modules::update_module_teacher_meta( $term_id, $new_teacher_id );

				// Set the terms selected on the course.
				wp_set_object_terms( $course_id, $term_id, 'module', true );

				// Remove old term.
				if ( intval( $term_id ) !== intval( $term->term_id ) ) {
					wp_remove_object_terms( $course_id, $term->term_id, 'module' );
				}

				foreach ( $lessons as $lesson ) {
					if ( has_term( $term->term_id, 'module', $lesson ) ) {
						// Add the new term, the false at the end says to replace all terms on this module
						// with the new term.
						wp_set_object_terms( $lesson->ID, $term_id, 'module', false );

						$order_module     = 0;
						$old_order_module = get_post_meta( $lesson->ID, '_order_module_' . intval( $term->term_id ), true );

						if ( $old_order_module ) {
							$order_module = $old_order_module;

							if ( intval( $term->term_id ) !== intval( $term_id ) ) {
								delete_post_meta( $lesson->ID, '_order_module_' . intval( $term->term_id ) );
							}
						}

						update_post_meta( $lesson->ID, '_order_module_' . intval( $term_id ), $order_module );
					}
				}

				// Clean up module if no longer used.
				Sensei()->modules->remove_if_unused( $term->term_id );
			}
		}

		Sensei_Course_Structure::instance( $course_id )->save_module_order( $module_order );
	}

	/**
	 * Update all course lessons and their quiz with a new author.
	 *
	 * @since 1.8.0
	 * @access public
	 *
	 * @param int $course_id  The course id.
	 * @param int $new_author The new authors id.
	 *
	 * @return bool True when the author is updated.
	 */
	public function update_course_lessons_author( $course_id, $new_author ) {

		if ( empty( $course_id ) || empty( $new_author ) ) {
			return false;
		}

		// Get a list of course lessons.
		$lessons = Sensei()->course->course_lessons( $course_id, 'any' );

		if ( empty( $lessons ) || ! is_array( $lessons ) ) {
			return false;
		}

		// Temporarily remove the author match filter.
		remove_filter( 'wp_insert_post_data', array( $this, 'update_lesson_teacher' ), 99 );

		// Update each lesson and quiz author.
		foreach ( $lessons as $lesson ) {

			// Don't update if the author is tha same as the new author.
			if ( $new_author == $lesson->post_author ) {
				continue;
			}

			// Update lesson author.
			wp_update_post(
				array(
					'ID'          => $lesson->ID,
					'post_author' => $new_author,
				)
			);

			// Update quiz author.
			$lesson_quiz_id = Sensei()->lesson->lesson_quizzes( $lesson->ID );
			if ( ! empty( $lesson_quiz_id ) ) {
				Sensei()->quiz->update_quiz_author( (int) $lesson_quiz_id, (int) $new_author );
			}
		}

		// Reinstate the author match filter.
		add_filter( 'wp_insert_post_data', array( $this, 'update_lesson_teacher' ), 99, 2 );

		return true;
	}



	/**
	 * Sensei_Teacher::course_analysis_teacher_access_limit
	 *
	 * Alter the query so that users can only see their courses on the analysis page
	 *
	 * @since 1.8.0
	 * @access public
	 * @parameters $query
	 * @return array $users user id array
	 */
	public function course_analysis_teacher_access_limit( $query ) {

		if ( ! is_admin() || ( defined( 'DOING_AJAX' ) && DOING_AJAX ) ) {
			return $query;
		}

		if ( ! function_exists( 'get_current_screen' ) ) {
			return $query;
		}

		$screen            = get_current_screen();
		$sensei_post_types = array( 'course', 'lesson', 'question' );

		// exit early for the following conditions.
		$limit_screen_ids = array( 'sensei-lms_page_' . Sensei_Analysis::PAGE_SLUG, 'sensei-lms_page_module-order' );

		if ( ! $this->is_admin_teacher() || empty( $screen ) || ! in_array( $screen->id, $limit_screen_ids )
			|| ! in_array( $query->query['post_type'], $sensei_post_types ) ) {
			return $query;
		}

		global $current_user;
		// set the query author to the current user to only show those those posts
		$query->set( 'author', $current_user->ID );
		return $query;

	}


	/**
	 * Sensei_Teacher::limit_teacher_edit_screen_post_types
	 *
	 * Determine if we're in admin and the current logged in use is a teacher
	 *
	 * @since 1.8.0
	 * @access public
	 * @parameters array $wp_query
	 * @return bool $is_admin_teacher
	 */
	public function is_admin_teacher() {
		if ( ! function_exists( 'is_user_logged_in' ) ) {
			return false;
		}

		if ( ! is_user_logged_in() ) {
			return false;
		}
		$is_admin_teacher = false;

		if ( is_admin() && self::is_a_teacher( get_current_user_id() ) ) {

			$is_admin_teacher = true;

		}

		return $is_admin_teacher;

	}

	/**
	 * Show correct post counts on list table for Sensei post types
	 *
	 * @since 1.8.0
	 *
	 * @param  object $counts Default status counts
	 * @param  string $type   Current post type
	 * @param  string $perm   User permission level
	 * @return object         Modified status counts
	 */
	public function list_table_counts( $counts, $type, $perm ) {
		global $current_user;

		if ( ! in_array( $type, array( 'course', 'lesson', 'question' ) ) ) {
			return $counts;
		}

		if ( ! $this->is_admin_teacher() ) {
			return $counts;
		}

		$args = array(
			'post_type'      => $type,
			'author'         => $current_user->ID,
			'posts_per_page' => -1,
		);

		 // Get all available statuses
		$stati = get_post_stati();

		// Update count object
		foreach ( $stati as $status ) {
			$args['post_status'] = $status;
			$posts               = get_posts( $args );
			$counts->$status     = count( $posts );
		}

		return $counts;
	}

	/**
	 * Filter the post queries to show
	 * only lesson/course that belong
	 * to the current logged teacher.
	 *
	 * @since 1.8.0
	 */
	public function filter_queries( $query ) {
		global $current_user;

		if ( ! $this->is_admin_teacher() ) {
			return;
		}

		if ( ! function_exists( 'get_current_screen' ) ) {
			return;
		}

		$screen = get_current_screen();
		if ( empty( $screen ) ) {
			return $query;
		}
		switch ( $screen->id ) {
			case 'sensei-lms_page_sensei_grading':
			case 'sensei-lms_page_' . Sensei_Analysis::PAGE_SLUG:
			case 'sensei-lms_page_sensei_learners':
			case 'lesson':
			case 'course':
			case 'question':
			case 'lesson_page_module-order':
				/**
				 * Filter the author Sensei set for queries.
				 *
				 * @since 1.8.0
				 *
				 * @hook sensei_filter_queries_set_author
				 *
				 * @param {int}    $user_id   The user ID to set as author.
				 * @param {string} $screen_id The current screen ID.
				 * @return {int} Filtered author ID.
				 */
				$query->set( 'author', apply_filters( 'sensei_filter_queries_set_author', $current_user->ID, $screen->id ) );
				break;
		}
	}

	/**
	 * Filter the learners query to only show learners associated with the current teacher.
	 *
	 * To determine if a learner is associated to a teacher, we check if the learner has
	 * progress for a course that belongs to the teacher.
	 *
	 * @since 4.20.0
	 *
	 * @param string $learners_sql The learners SQL query.
	 *
	 * @return string
	 */
	public function filter_learners_query( $learners_sql ) {
		if ( ! $this->is_admin_teacher() ) {
			return $learners_sql;
		}

		$teacher_course_ids = $this->get_teacher_courses( get_current_user_id(), true );
		if ( ! $teacher_course_ids ) {
			$teacher_course_ids = [ 0 ]; // Show no learners.
		}

		$teacher_course_ids_imploded = implode( ',', array_map( 'absint', $teacher_course_ids ) );

		global $wpdb;
		if ( Progress_Storage_Settings::is_tables_repository() ) {
			$replacement_sql = "
INNER JOIN {$wpdb->prefix}sensei_lms_progress AS progress ON u.ID = progress.user_id
WHERE 1=1
AND progress.post_id IN ($teacher_course_ids_imploded)";
		} else {
			$replacement_sql = "
INNER JOIN $wpdb->comments AS comments ON u.ID = comments.user_id
WHERE 1=1
AND comments.comment_post_ID IN ($teacher_course_ids_imploded)
AND comments.comment_type = 'sensei_course_status'";
		}

		return str_replace(
			'WHERE 1=1',
			$replacement_sql,
			$learners_sql
		);
	}

	/**
	 * Limit grading quizzes to only those within courses belonging to the current teacher
	 * . This excludes the admin user.
	 *
	 * @since 1.8.0
	 * @hooked into the_comments
	 * @param array $comments
	 *
	 * @return array $comments
	 */
	public function filter_grading_activity_queries( $comments ) {

		if ( ! is_admin() || ! $this->is_admin_teacher() || is_numeric( $comments ) || ! is_array( $comments ) ) {
			return $comments;
		}

		// check if we're on the grading screen
		$screen = get_current_screen();

		if ( empty( $screen ) || 'sensei-lms_page_sensei_grading' != $screen->id ) {
			return $comments;
		}

		// get the course and determine if the current teacher is the owner
		// if not remove it from the list of comments to be returned
		foreach ( $comments as $key => $comment ) {
			$lesson    = get_post( $comment->comment_post_ID );
			$course_id = Sensei()->lesson->get_course_id( $lesson->ID );
			$course    = get_post( $course_id );
			/**
			 * Allows to change the list of teacher IDs with grading access allowed for a given course ID.
			 *
			 * @since 4.9.0
			 *
			 * @hook sensei_grading_allowed_user_ids
			 *
			 * @param {int[]} $user_ids  The list of user IDs with access granted. By default the course author.
			 * @param {int}   $course_id The course ID.
			 * @return {int[]} Filtered list of user IDs with access granted.
			 */
			$allowed_user_ids = apply_filters( 'sensei_grading_allowed_user_ids', [ intval( $course->post_author ) ], $course_id );
			if ( ! isset( $course->post_author ) || ! in_array( intval( get_current_user_id() ), $allowed_user_ids, true ) ) {
				// remove this as the teacher should see this.
				unset( $comments[ $key ] );
			}
		}
		return $comments;

	}

	/**
	 * Limit the grading screen totals to only show lessons in the course
	 * belonging to the currently logged in teacher. This only applies to
	 * the teacher role.
	 *
	 * @since 1.8.0
	 *
	 * @hooked into sensei_count_statuses_args
	 * @param array $args
	 *
	 * @return array  $args
	 */
	public function limit_grading_totals( $args ) {

		if ( ! is_admin() || ! $this->is_admin_teacher() || ! is_array( $args ) ) {
			return $args;
		}

		// get the teachers courses
		// the query is already filtered to only the teacher
		$courses = Sensei()->course->get_all_courses();

		if ( empty( $courses ) || ! is_array( $courses ) ) {
			return $args;
		}

		// setup the lessons quizzes  to limit the grading totals to
		$quiz_scope = array();
		foreach ( $courses as $course ) {

			$course_lessons = Sensei()->course->course_lessons( $course->ID );

			if ( ! empty( $course_lessons ) && is_array( $course_lessons ) ) {

				foreach ( $course_lessons as $lesson ) {

					array_push( $quiz_scope, $lesson->ID );

				}
			}
		}

		$args['post__in'] = $quiz_scope;

		return $args;
	}

	/**
	 * It ensures that the author archive shows course by the current user.
	 *
	 * This function is hooked into the pre_get_posts filter
	 *
	 * @param WP_Query $query
	 * @return WP_Query $query
	 */
	public function add_courses_to_author_archive( $query ) {

		if ( is_admin() || ! $query->is_author() ) {
			return $query;
		}

		// this should only apply to users with the teacher role
		$current_page_user = get_user_by( 'login', $query->get( 'author_name' ) );
		if ( ! $current_page_user || ! in_array( 'teacher', $current_page_user->roles ) ) {

			return $query;

		}

		// Change post types depending on what is set already
		$current_post_types = $query->get( 'post_type' );
		if ( empty( $current_post_types ) ) {

			// if empty it means post by default, so add post so that it also includes that for now
			$new_post_types = array( 'post', 'course' );

		} elseif ( is_array( $current_post_types ) ) {

			// merge the post types instead of overwriting it
			$new_post_types = array_merge( $current_post_types, array( 'course' ) );

		} else {

			// in this instance it is probably just one post type in string format
			$new_post_types = array( $current_post_types, 'course' );

		}

		// change the query before returning it
		$query->set( 'post_type', $new_post_types );

		/**
		 * Change the query on the teacher author archive template
		 *
		 * @since 1.8.4
		 *
		 * @hook sensei_teacher_archive_query
		 *
		 * @param {WP_Query} $query The query object.
		 * @return {WP_Query} The filtered query object.
		 */
		return apply_filters( 'sensei_teacher_archive_query', $query );

	}

	/**
	 * Notify teacher when someone assigns a course to their account.
	 *
	 * @since 1.8.0
	 *
	 * @param int $teacher_id The ID of the teacher.
	 * @param int $course_id  The ID of the course.
	 * @return bool
	 */
	public function teacher_course_assigned_notification( $teacher_id, $course_id ) {

		if ( 'course' != get_post_type( $course_id ) || ! get_userdata( $teacher_id ) ) {
			return false;
		}

		// if new user is the same as the current logged user, they don't need an email
		if ( $teacher_id == get_current_user_id() ) {
			return true;
		}

		// load the email class
		$email = new Sensei_Email_Teacher_New_Course_Assignment();
		$email->trigger( $teacher_id, $course_id );

		return true;
	}

	/**
	 * Email the admin when a teacher creates a new course
	 *
	 * This function hooks into wp_insert_post
	 *
	 * @since 1.8.0
	 * @param int $course_id
	 * @return bool
	 */
	public function notify_admin_teacher_course_creation( $new_status, $old_status, $post ) {

		$course_id = $post->ID;

		if ( 'publish' == $old_status || 'course' != get_post_type( $course_id ) || 'auto-draft' == get_post_status( $course_id )
			|| 'trash' == get_post_status( $course_id ) || 'draft' == get_post_status( $course_id ) ) {

			return false;

		}

		/**
		 * Filter the option to send admin notification emails when teachers creation course.
		 *
		 * @since 1.8.0
		 *
		 * @hook sensei_notify_admin_new_course_creation
		 *
		 * @param {bool} $on The option of whether admin notification emails should be sent, default true.
		 * @return {bool} Filtered option.
		 */
		if ( ! apply_filters( 'sensei_notify_admin_new_course_creation', true ) ) {
			return false;
		}

		// setting up the data needed by the email template
		global $sensei_email_data;
		$template  = 'admin-teacher-new-course-created';
		$course    = get_post( $course_id );
		$teacher   = new WP_User( $course->post_author );
		$recipient = get_option( 'admin_email', true );

		// don't send if the course is created by admin
		if ( $recipient == $teacher->user_email || current_user_can( 'manage_options' ) ) {
			return false;
		}

		do_action( 'sensei_before_mail', $recipient );

		/**
		 * Filter the email header for the admin-teacher-new-course-created template
		 *
		 * @since 1.8.0
		 *
		 * @hook sensei_email_heading
		 *
		 * @param {string} $heading  Email heading, default: New course created.
		 * @param {string} $template Template name.
		 * @return {string} Filtered heading.
		 */
		$heading = apply_filters( 'sensei_email_heading', __( 'New course created.', 'sensei-lms' ), $template );

		/**
		 * Filter the email subject for the the admin-teacher-new-course-created template.
		 *
		 * @since 1.8.0
		 *
		 * @hook sensei_email_subject
		 *
		 * @param {string} $subject The email subject, default: New course created by {teacher name}.
		 * @param {string} $template Template name.
		 * @return {string} Filtered subject.
		 */
		$subject = apply_filters(
			'sensei_email_subject',
			'[' . get_bloginfo( 'name', 'display' ) . '] ' . __( 'New course created by', 'sensei-lms' ) . ' ' . $teacher->display_name,
			$template
		);

		// course edit link
		$course_edit_link = admin_url( 'post.php?post=' . $course_id . '&action=edit' );

		// Construct data array
		$email_data = array(
			'template'         => $template,
			'heading'          => $heading,
			'teacher'          => $teacher,
			'course_id'        => $course_id,
			'course_name'      => $course->post_title,
			'course_edit_link' => $course_edit_link,
		);

		/**
		 * Filter the sensei email data for the admin-teacher-new-course-created template
		 *
		 * @since 1.8.0
		 *
		 * @hook sensei_email_data
		 *
		 * @param {array}  $email_data The email data array.
		 * @param {string} $template   Template name.
		 * @return {array} Filtered email data array.
		 */
		$sensei_email_data = apply_filters( 'sensei_email_data', $email_data, $template );

		// Send mail
		Sensei()->emails->send( $recipient, $subject, Sensei()->emails->get_content( $template ) );

		do_action( 'sensei_after_sending_email' );

	}

	/**
	 * Limit the analysis view to only the users taking courses belong to this teacher.
	 *
	 * Hooked into `sensei_analysis_overview_filter_users`.
	 *
	 * @param array $args WP_User_Query arguments.
	 *
	 * @return array Learners query args.
	 */
	public function limit_analysis_learners( $args ) {

		// show default for none teachers
		if ( ! Sensei()->teacher->is_admin_teacher() ) {
				return $args;
		}

		$learner_ids_for_courses_with_edit_permission = $this->get_learner_ids_for_courses_with_edit_permission();

		// if there are no students taking the courses by this teacher don't show them any of the other users
		if ( empty( $learner_ids_for_courses_with_edit_permission ) ) {

			$args['include'] = array( 0 );

		} else {

			$args['include'] = $learner_ids_for_courses_with_edit_permission;

		}

		return $args;

	}

	/**
	 * Get the learner IDs enrolled in courses where the current user has permission
	 * to edit (author or admin).
	 *
	 * @since 3.9.0 Method extracted from `Sensei_Teacher::limit_analysis_learners`.
	 *
	 * @return int[] Learner IDs.
	 */
	public function get_learner_ids_for_courses_with_edit_permission() {
		// for teachers all courses only return those which belong to the teacher
		// as they don't have access to course belonging to other users
		$courses_with_edit_permission = Sensei()->course->get_all_courses();

		if ( empty( $courses_with_edit_permission ) || ! is_array( $courses_with_edit_permission ) ) {
			return [];
		}

		$course_ids             = wp_list_pluck( $courses_with_edit_permission, 'ID' );
		$course_ids_placeholder = implode( ',', array_fill( 0, count( $course_ids ), '%d' ) );

		global $wpdb;
		if ( Progress_Storage_Settings::is_tables_repository() ) {
			// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Using a direct query for performance reasons.
			$learner_ids = (array) $wpdb->get_col(
				$wpdb->prepare(
					"SELECT DISTINCT user_id
					FROM {$wpdb->prefix}sensei_lms_progress
					WHERE type = 'course'
					AND post_id IN ( $course_ids_placeholder )", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare -- Placeholders created dynamically.
					$course_ids
				)
			);
		} else {
			// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Using a direct query for performance reasons.
			$learner_ids = (array) $wpdb->get_col(
				$wpdb->prepare(
					"SELECT DISTINCT user_id
					FROM {$wpdb->comments}
					WHERE comment_type = 'sensei_course_status'
					AND comment_post_ID IN ( $course_ids_placeholder )", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare -- Placeholders created dynamically.
					$course_ids
				)
			);
		}

		return array_map( 'intval', $learner_ids );
	}

	/**
	 * Give teacher full admin access to the question post type
	 * in certain cases.
	 *
	 * @since 1.8.0
	 * @param $questions
	 * @return mixed
	 */
	public function allow_teacher_access_to_questions( $questions, $quiz_id ) {

		if ( ! $this->is_admin_teacher() ) {
			return $questions;
		}

		$screen = get_current_screen();

		// don't run this filter within this functions call to Sensei()->lesson->lesson_quiz_questions
		remove_filter( 'sensei_lesson_quiz_questions', array( $this, 'allow_teacher_access_to_questions' ), 20 );

		if ( ! empty( $screen ) && 'lesson' == $screen->post_type ) {

			$admin_user = get_user_by( 'email', get_bloginfo( 'admin_email' ) );
			if ( ! empty( $admin_user ) ) {

				$current_teacher_id = get_current_user_id();

				// set current user to admin so teacher can view all questions
				wp_set_current_user( $admin_user->ID );
				$questions = Sensei()->lesson->lesson_quiz_questions( $quiz_id );

				// set the teacher as the current use again
				wp_set_current_user( $current_teacher_id );
			}
		}
		// attach the filter again for other funtion calls to Sensei()->lesson->lesson_quiz_questions
		add_filter( 'sensei_lesson_quiz_questions', array( $this, 'allow_teacher_access_to_questions' ), 20, 2 );

		return $questions;
	}

	/**
	 * Give the teacher role access to questions from the question bank
	 *
	 * @since 1.8.0
	 * @param $wp_query
	 * @return mixed
	 */
	public function give_access_to_all_questions( $wp_query ) {

		if ( ! $this->is_admin_teacher() || ! function_exists( 'get_current_screen' ) || 'question' != $wp_query->get( 'post_type' ) ) {

			return $wp_query;
		}

		$screen = get_current_screen();
		if ( ( isset( $screen->id ) && 'lesson' == $screen->id )
			|| ( defined( 'DOING_AJAX' ) && DOING_AJAX ) ) {

			$admin_user = get_user_by( 'email', get_bloginfo( 'admin_email' ) );
			if ( ! empty( $admin_user ) ) {

				$current_teacher_id = get_current_user_id();

				// set current user to admin so teacher can view all questions
				wp_set_current_user( $admin_user->ID );

				// run new query as admin
				$wp_query = new WP_Query( $wp_query->query );

				// set the teache as current use again
				wp_set_current_user( $current_teacher_id );

			}
		}

		return $wp_query;
	}

	/**
	 * Add new column heading to the course admin edit list
	 *
	 * @since 1.8.0
	 * @param $columns
	 * @return array
	 */
	public function course_column_heading( $columns ) {

		if ( $this->is_admin_teacher() ) {
			return $columns;
		}
		$new_columns = array(
			'teacher' => __( 'Teacher', 'sensei-lms' ),
		);
		return array_merge( $columns, $new_columns );

	}

	/**
	 * Print out  teacher column data
	 *
	 * @since 1.8.0
	 * @param $column
	 * @param $course_id
	 */
	public function course_column_data( $column, $course_id ) {

		if ( $this->is_admin_teacher() || 'teacher' != $column ) {
			return;
		}

		$course  = get_post( $course_id );
		$teacher = get_userdata( $course->post_author );

		if ( ! $teacher ) {
			return;
		}

		echo '<a href="' . esc_url( get_edit_user_link( $teacher->ID ) ) . '" >' . esc_html( $teacher->display_name ) . '</a>';

	}

	/**
	 * Return only courses belonging to the given teacher.
	 *
	 * @since 1.8.0
	 *
	 * @param int  $teacher_id
	 * @param bool $return_ids_only
	 *
	 * @return array $teachers_courses
	 */
	public function get_teacher_courses( $teacher_id, $return_ids_only = false ) {

		$teachers_courses = array();

		if ( empty( $teacher_id ) ) {
			$teacher_id = get_current_user_id();
		}

		$all_courses = Sensei()->course->get_all_courses();

		if ( empty( $all_courses ) ) {
			return $all_courses;
		}

		foreach ( $all_courses as $course ) {

			if ( $course->post_author != $teacher_id ) {
				continue;
			}

			if ( $return_ids_only ) {

				$teachers_courses[] = $course->ID;

			} else {

				$teachers_courses[] = $course;

			}
		}

		return $teachers_courses;

	}

	/**
	 * Limit the message display to only those sent to the current teacher
	 *
	 * @since 1.8.0
	 *
	 * @param $query
	 * @return mixed
	 */
	public function limit_edit_messages_query( $query ) {
		if ( ! $this->is_admin_teacher() || 'sensei_message' != $query->get( 'post_type' ) ) {
			return $query;
		}

		$teacher = wp_get_current_user();

		$query->set( 'meta_key', '_receiver' );
		$meta_query_args = array(
			'key'     => '_receiver',
			'value'   => $teacher->get( 'user_login' ),
			'compare' => '=',
		);

		$query->set( 'meta_query', $meta_query_args );

		return $query;
	}


	/**
	 * Add options to filter courses by teacher
	 *
	 * @since 1.8.0
	 *
	 * @return void
	 */
	public function course_teacher_filter_options() {
		global $typenow;

		if ( ! is_admin() || 'course' != $typenow || ! current_user_can( 'manage_sensei' ) ) {
			return;
		}

		// get all roles
		$roles = get_editable_roles();

		// get roles with the course edit capability
		// and then get the users with those roles
		$role_users_who_can_edit_courses = array();
		foreach ( $roles as $role_item ) {

			$role = get_role( strtolower( $role_item['name'] ) );

			if ( is_a( $role, 'WP_Role' ) && $role->has_cap( 'edit_courses' ) ) {
				$role_users_who_can_edit_courses[] = $role->name;
			}
		}

		$user_query_args = array(
			'role__in' => $role_users_who_can_edit_courses,
			'fields'   => array( 'ID', 'display_name' ),
		);

		$users_who_can_edit_courses = get_users( $user_query_args );

		// Create the select element with the given users who can edit course
		$selected       = isset( $_GET['course_teacher'] ) ? $_GET['course_teacher'] : '';
		$course_options = '';
		foreach ( $users_who_can_edit_courses as $user ) {
			$course_options .= '<option value="' . esc_attr( $user->ID ) . '" ' . selected( $selected, $user->ID, false ) . '>' . esc_html( $user->display_name ) . '</option>';
		}

		$output  = '<select name="course_teacher" id="dropdown_course_teachers">';
		$output .= '<option value="">' . esc_html__( 'Show all teachers', 'sensei-lms' ) . '</option>';
		$output .= $course_options;
		$output .= '</select>';

		echo wp_kses(
			$output,
			array(
				'option' => array(
					'selected' => array(),
					'value'    => array(),
				),
				'select' => array(
					'id'   => array(),
					'name' => array(),
				),
			)
		);
	}

	/**
	 * Modify the main query on the admin course list screen
	 *
	 * @since 1.8.0
	 *
	 * @param $query
	 * @return $query
	 */
	public function teacher_filter_query_modify( $query ) {
		global $typenow;

		if ( ! is_admin() && 'course' != $typenow || ! current_user_can( 'manage_sensei' ) ) {
			return $query;
		}
		$course_teacher = isset( $_GET['course_teacher'] ) ? $_GET['course_teacher'] : '';

		if ( empty( $course_teacher ) ) {
			return $query;
		}

		$query['author'] = $course_teacher;
		return $query;
	}

	/**
	 * Only show current teacher's media in the media library
	 *
	 * @param  array $request Default request arguments
	 * @return array          Modified request arguments
	 */
	public function restrict_media_library( $request = array() ) {

		if ( ! is_admin() ) {
			return $request;
		}

		if ( ! $this->is_admin_teacher() ) {
			return $request;
		}

		$screen = get_current_screen();

		if ( in_array( $screen->id, array( 'upload', 'course', 'lesson', 'question' ) ) ) {
			$teacher = intval( get_current_user_id() );

			if ( $teacher ) {
				$request['author__in'] = array( $teacher );
			}
		}

		return $request;
	}

	/**
	 * Only show current teacher's media in the media library modal on the course/lesson/quesion edit screen
	 *
	 * @param  array $query Default query arguments
	 * @return array        Modified query arguments
	 */
	public function restrict_media_library_modal( $query = array() ) {

		if ( ! is_admin() ) {
			return $query;
		}

		if ( ! $this->is_admin_teacher() ) {
			return $query;
		}

		$teacher = intval( get_current_user_id() );

		if ( $teacher ) {
			$query['author__in'] = array( $teacher );
		}

		return $query;
	}

	/**
	 * When saving the lesson, update the teacher if the lesson belongs to a course
	 *
	 * @since 1.8.0
	 *
	 * @param int $lesson_id
	 */
	public function update_lesson_teacher( $data, $postarr = array() ) {

		if ( 'lesson' != $data['post_type'] ) {
			return $data;
		}

		$lesson_id = isset( $postarr['ID'] ) ? $postarr['ID'] : null;

		if ( empty( $lesson_id ) || ! $lesson_id ) {
			return $data;
		}

		$course_id = Sensei()->lesson->get_course_id( $lesson_id );

		if ( empty( $course_id ) || ! $course_id ) {
			return $data;
		}

		$course = get_post( $course_id );

		$data['post_author'] = $course->post_author;

		return $data;
	}

	/**
	 * Sensei_Teacher::limit_teacher_edit_screen_post_types
	 *
	 * Limit teachers to only see their courses, lessons and questions
	 *
	 * @since 1.8.0
	 * @access public
	 * @parameters array $wp_query
	 * @return WP_Query $wp_query
	 */
	public function limit_teacher_edit_screen_post_types( $wp_query ) {
		global $current_user;

		// exit early
		if ( ! $this->is_admin_teacher() ) {
			return $wp_query;
		}

		if ( ! function_exists( 'get_current_screen' ) ) {
			return $wp_query;
		}

		$screen = get_current_screen();

		if ( empty( $screen ) ) {
			return $wp_query;
		}

		// for any of these conditions limit what the teacher will see
		$limit_screens = array(
			'edit-lesson',
			'edit-course',
			'edit-question',
			'admin_page_course-order',
			'admin_page_lesson-order',
		);

		if ( in_array( $screen->id, $limit_screens ) ) {

			// set the query author to the current user to only show those those posts
			$wp_query->set( 'author', $current_user->ID );
		}

		return $wp_query;

	}


	/**
	 * Sensei_Teacher::teacher_login_redirect
	 *
	 * Redirect teachers to /wp-admin/ after login
	 *
	 * @since 1.8.7
	 * @access public
	 * @param string $user_login
	 * @param object $user
	 * @return void
	 */
	public function teacher_login_redirect( $user_login, $user ) {
		// If Jetpack's redirection cookie is set, let Jetpack handle redirection.
		if ( ! empty( $_COOKIE['jetpack_sso_redirect_to'] ) ) {
			return;
		}

		if ( user_can( $user, 'edit_courses' ) ) {

			// phpcs:ignore WordPress.Security.NonceVerification -- We are not making any changes based on this.
			if ( isset( $_REQUEST['redirect_to'] ) ) {

				// phpcs:ignore WordPress.Security.NonceVerification -- We are not making any changes based on this.
				wp_safe_redirect( wp_unslash( $_REQUEST['redirect_to'] ), 303 );

				exit;

			} else {

				wp_redirect( admin_url(), 303 );

				exit;

			}
		}

	}



	/**
	 * Sensei_Teacher::restrict_posts_menu_page()
	 *
	 * Remove the Posts menu page for teachers and restrict access to it.
	 * We have to do this because we give teachers the 'edit_posts' cap
	 * so they can 'moderate_comments' as well.
	 *
	 * @since 1.8.7
	 * @access public
	 * @parameters void
	 * @return void
	 */

	public function restrict_posts_menu_page() {

		global $pagenow, $typenow;

		$user = wp_get_current_user();

		/**
		 * Filter the option to hide the Posts menu page.
		 *
		 * @since 1.8.7
		 *
		 * @hook sensei_restrict_posts_menu_page
		 *
		 * @param {bool} $restrict Whether to hide the posts menu page, default true.
		 * @return {bool} Filtered option.
		 */
		$restrict = apply_filters( 'sensei_restrict_posts_menu_page', true );

		if ( in_array( 'teacher', (array) $user->roles ) && ! current_user_can( 'delete_posts' ) && $restrict ) {

			remove_menu_page( 'edit.php' );

			if ( $pagenow == 'edit.php' || $pagenow == 'post-new.php' ) {

				if ( $typenow == '' || $typenow == 'post' || $typenow == 'page' ) {

					wp_die( 'You do not have sufficient permissions to access this page.' );

				}
			}
		}

	}

	/**
	 * Sensei_Teacher::restrict_comment_moderation()
	 *
	 * Restrict commendation moderation for teachers
	 * so they can only moderate comments made to posts they own.
	 *
	 * @since 1.8.7
	 * @access public
	 * @parameters obj $clauses
	 * @return WP_Comment_Query  $clauses
	 */
	public function restrict_comment_moderation( $clauses ) {

		global $pagenow;

		if ( self::is_a_teacher( get_current_user_id() ) && $pagenow == 'edit-comments.php' ) {

			$clauses->query_vars['post_author'] = get_current_user_id();

		}

		return $clauses;

	}

	/**
	 * Determine if a user is a teacher by ID
	 *
	 * @param int $user_id
	 *
	 * @return bool
	 */
	public static function is_a_teacher( $user_id ) {

		$user = get_user_by( 'id', $user_id );

		if ( isset( $user->roles ) && in_array( 'teacher', $user->roles ) ) {

			return true;

		} else {

			return false;

		}

	}

	/**
	 * The archive title on the teacher archive filter
	 *
	 * @since 1.9.0
	 */
	public static function archive_title() {

		$author      = get_user_by( 'id', get_query_var( 'author' ) );
		$author_name = $author->display_name;
		?>
			<h2 class="teacher-archive-title">

				<?php
				// translators: Placeholder is the author name.
				echo esc_html( sprintf( __( 'All courses by %s', 'sensei-lms' ), $author_name ) );
				?>

			</h2>
		<?php

	}

	/**
	 * Removing course meta on the teacher archive page
	 *
	 * @since 1.9.0
	 */
	public static function remove_course_meta_on_teacher_archive() {

		remove_action( 'sensei_course_content_inside_before', array( Sensei()->course, 'the_course_meta' ) );

	}

	/**
	 * Try to extract teacher id from module slug to term meta
	 * if the meta does not exist already
	 *
	 * @since 4.6.0
	 * @access private
	 *
	 * @param int $term_id ID of the term being edited.
	 * @return void
	 */
	public function extract_and_save_teacher_to_meta_from_slug( $term_id ) {
		$term_meta = get_term_meta( $term_id, 'module_author', true );

		if ( $term_meta ) {
			return;
		}

		$term       = get_term( $term_id, 'module' );
		$split_slug = explode( '-', $term->slug );

		if ( count( $split_slug ) > 1 && is_numeric( $split_slug[0] ) ) {
			$user = get_user_by( 'id', $split_slug[0] );
			$user && Sensei_Core_Modules::update_module_teacher_meta( $term_id, $user->ID );
		}
	}
}