diff --git a/assets/css/frontend.scss b/assets/css/frontend.scss index ebfd014152..07a5f7fe8c 100644 --- a/assets/css/frontend.scss +++ b/assets/css/frontend.scss @@ -776,7 +776,7 @@ section.entry span.course-lesson-progress { margin-left: 10px; } /*-------------------------------------------------------------------------------------------*/ /* 1. Info Boxes */ /*-------------------------------------------------------------------------------------------*/ -.sensei, .course-container, .course, .lesson, .quiz, .learner-info { +.sensei, .course-container, .course, .module-container, .lesson, .quiz, .learner-info { p.sensei-message, div.sensei-message { clear: both; margin: 1.387em 0 1.618em 0; diff --git a/includes/class-sensei-course.php b/includes/class-sensei-course.php index b5f7116ae9..344da31424 100755 --- a/includes/class-sensei-course.php +++ b/includes/class-sensei-course.php @@ -53,8 +53,9 @@ public function __construct() { add_action( 'save_post', array( $this, 'meta_box_save' ) ); // Custom Write Panel Columns - add_filter( 'manage_course_posts_columns', array( $this, 'add_column_headings' ), 10, 1 ); + add_filter( 'manage_course_posts_columns', array( $this, 'add_column_headings' ), 20, 1 ); add_action( 'manage_course_posts_custom_column', array( $this, 'add_column_data' ), 10, 2 ); + add_filter( 'default_hidden_columns', array( $this, 'set_default_visible_columns' ), 10, 2 ); // Enqueue scripts. add_action( 'admin_enqueue_scripts', array( $this, 'register_admin_scripts' ) ); @@ -179,6 +180,7 @@ public static function is_user_enrolled( $course_id, $user_id = null ) { * @param int $course_id Course post ID. * @param int $user_id User ID. * @param string $context Context that we're checking for course content access (`lesson`, `quiz`, or `module`). + * @return bool True when user can access the course content, otherwise false. */ public function can_access_course_content( $course_id, $user_id = null, $context = 'lesson' ) { if ( null === $user_id ) { @@ -664,15 +666,16 @@ public function course_manage_meta_box_content() { } // End course_manage_meta_box_content() /** - * Add column headings to the "lesson" post list screen. + * Add column headings to the "course" post list screen, + * while moving the existing ones to the end. * - * @access public + * @access private * @since 1.0.0 - * @param array $defaults - * @return array $new_columns + * @param array $defaults Array of column header labels keyed by column ID. + * @return array Updated array of column header labels keyed by column ID. */ public function add_column_headings( $defaults ) { - $new_columns = array(); + $new_columns = []; $new_columns['cb'] = ''; $new_columns['title'] = _x( 'Course Title', 'column name', 'sensei-lms' ); $new_columns['course-prerequisite'] = _x( 'Pre-requisite Course', 'column name', 'sensei-lms' ); @@ -681,8 +684,59 @@ public function add_column_headings( $defaults ) { $new_columns['date'] = $defaults['date']; } + // Make sure other sensei columns stay directly behind the new columns. + $other_sensei_culumns = [ + 'taxonomy-module', + 'teacher', + 'module_order', + ]; + foreach ( $other_sensei_culumns as $column_key ) { + if ( isset( $defaults[ $column_key ] ) ) { + $new_columns[ $column_key ] = $defaults[ $column_key ]; + } + } + + // Add all remaining columns at the end. + foreach ( $defaults as $column_key => $column_value ) { + if ( ! isset( $new_columns[ $column_key ] ) ) { + $new_columns[ $column_key ] = $column_value; + } + } + return $new_columns; - } // End add_column_headings() + } + + /** + * Hide all columns by default, leaving only a default list. + * + * @access private + * @since 3.5.4 + * @param string[] $hidden_columns Array of IDs of columns hidden by default. + * @param WP_Screen $screen WP_Screen object of the current screen. + * @return string[] Updated array of IDs of columns hidden by default. + */ + public function set_default_visible_columns( $hidden_columns, $screen ) { + $default_course_columns = [ + 'cb', + 'title', + 'course-prerequisite', + 'course-category', + 'date', + ]; + + if ( ! isset( $screen->id ) || 'edit-course' !== $screen->id ) { + return $hidden_columns; + } + + $columns = get_column_headers( $screen ); + foreach ( $columns as $column => $column_value ) { + if ( ! in_array( $column, $default_course_columns, true ) ) { + $hidden_columns[] = $column; + } + } + + return $hidden_columns; + } /** * Add data for our newly-added custom columns. diff --git a/includes/class-sensei-lesson.php b/includes/class-sensei-lesson.php index e235adb7fa..b8c798895a 100755 --- a/includes/class-sensei-lesson.php +++ b/includes/class-sensei-lesson.php @@ -4057,7 +4057,7 @@ public static function user_not_taking_course_message() { } // end user_not_taking_course_message /** - * Outputs the lessons course signup lingk + * Outputs the lessons course signup link * * This hook runs inside the single lesson page. * @@ -4067,12 +4067,12 @@ public static function course_signup_link() { $course_id = Sensei()->lesson->get_course_id( get_the_ID() ); - if ( empty( $course_id ) || 'course' !== get_post_type( $course_id ) || sensei_all_access() || Sensei_Utils::is_preview_lesson( get_the_ID() ) ) { + if ( empty( $course_id ) || Sensei_Utils::is_preview_lesson( get_the_ID() ) ) { return; } - $show_course_signup_notice = sensei_is_login_required() && ! Sensei_Course::is_user_enrolled( $course_id ); - + $show_course_signup_notice = is_user_logged_in() && ! Sensei()->course->can_access_course_content( $course_id ); + /** * Filter for if we should show the course sign up notice on the lesson page. * @@ -4140,6 +4140,47 @@ public static function prerequisite_complete_message() { } + /** + * Determines if user should login in to get acces to a lesson or quiz. + * + * @since 3.2.0 + * @param int $lesson_id Lesson ID. + * @return bool True if login for the lesson is required. + */ + public static function user_should_login( $lesson_id ) { + if ( 'integer' !== gettype( $lesson_id ) || $lesson_id <= 0 ) { + return false; + } + $course_id = Sensei()->lesson->get_course_id( $lesson_id ); + if ( is_user_logged_in() + || empty( $course_id ) + || 'course' !== get_post_type( $course_id ) + || sensei_all_access() + || Sensei_Utils::is_preview_lesson( $lesson_id ) + || ! sensei_is_login_required() + ) { + return false; + } + + return true; + } + + /** + * Adds a login notice when appropriate. + * + * @since 3.2.0 + * @return void + */ + public static function login_notice() { + $login_notice = Sensei_Utils::login_notice( 'lesson' ); + if ( false === $login_notice ) { + return; + } + $message = wp_kses_post( $login_notice ); + $notice_level = 'info'; + Sensei()->notices->add_notice( $message, $notice_level ); + } + /** * Outputs the the lesson archive header. * diff --git a/includes/class-sensei-modules.php b/includes/class-sensei-modules.php index becc2837f3..c01c82efb8 100644 --- a/includes/class-sensei-modules.php +++ b/includes/class-sensei-modules.php @@ -73,6 +73,7 @@ public function __construct( $file ) { add_action( 'pre_get_posts', array( $this, 'module_archive_filter' ), 10, 1 ); add_filter( 'sensei_lessons_archive_text', array( $this, 'module_archive_title' ) ); add_action( 'sensei_loop_lesson_inside_before', array( $this, 'module_archive_description' ), 30 ); + add_action( 'sensei_taxonomy_module_content_inside_before', array( $this, 'login_notice' ), 30 ); add_action( 'sensei_taxonomy_module_content_inside_before', array( $this, 'course_signup_link' ), 30 ); add_action( 'sensei_taxonomy_module_content_inside_before', array( $this, 'module_archive_description' ), 30 ); @@ -830,6 +831,11 @@ public function course_signup_link() { $show_course_signup_notice = ! $this->can_view_module_content( null, $course_id ); + if ( $show_course_signup_notice && $this->user_should_login( $course_id ) ) { + // User first needs to login and login_notice() takes preference to this one. + $show_course_signup_notice = false; + } + /** * Filter for if we should show the course sign up notice on the module page. * @@ -872,6 +878,38 @@ public function course_signup_link() { Sensei()->notices->add_notice( $message, $notice_level ); } + /** + * Adds a login notice when appropriate. + * + * @since 3.2.0 + * @return void + */ + public static function login_notice() { + $login_notice = Sensei_Utils::login_notice( 'module' ); + if ( false === $login_notice ) { + return; + } + $message = wp_kses_post( $login_notice ); + $notice_level = 'info'; + Sensei()->notices->add_notice( $message, $notice_level ); + } + + /** + * Determines if user should login in to get acces to the course module. + * + * @since 3.2.0 + * @param int $lesson_id Lesson ID. + * @return bool True if login for the module is required. + */ + public static function user_should_login( $course_id ) { + if ( ! $course_id + || is_user_logged_in() + || Sensei()->course->can_access_course_content( $course_id, null, 'module' ) ) { + return false; + } + return true; + } + public function module_archive_body_class( $classes ) { if ( is_tax( $this->taxonomy ) ) { $classes[] = 'module-archive'; diff --git a/includes/class-sensei-quiz.php b/includes/class-sensei-quiz.php index 24dd9aa4c7..3670be6515 100755 --- a/includes/class-sensei-quiz.php +++ b/includes/class-sensei-quiz.php @@ -143,8 +143,8 @@ public function user_save_quiz_answers_listener() { $answers_saved = self::save_user_answers( $quiz_answers, $_FILES, $lesson_id, get_current_user_id() ); if ( intval( $answers_saved ) > 0 ) { - // update the message showed to user - Sensei()->frontend->messages = '
' . __( 'Quiz Saved Successfully.', 'sensei-lms' ) . '
'; + // Show Quiz saved message to user + Sensei()->notices->add_notice( __( 'Quiz Saved Successfully.', 'sensei-lms' ), 'note' ); } // remove the hook as it should only fire once per click @@ -1209,19 +1209,43 @@ public static function the_title() { * @param $quiz_id */ public static function the_user_status_message( $quiz_id ) { - $lesson_id = Sensei()->quiz->get_lesson_id( $quiz_id ); - $status = Sensei_Utils::sensei_user_quiz_status_message( $lesson_id, get_current_user_id() ); - $messages = Sensei()->frontend->messages; - $message = '
' . wp_kses_post( $status['message'] ) . '
'; - $messages = Sensei()->frontend->messages; + if ( $lesson_id > 0 + && Sensei()->lesson->user_should_login( $lesson_id ) ) { + /* + * bail out when user should still login, + * because login_notice() takes preference. + */ + return; + } - if ( ! empty( $messages ) ) { - $message .= wp_kses_post( $messages ); + $status = Sensei_Utils::sensei_user_quiz_status_message( $lesson_id, get_current_user_id() ); + $notice_level = esc_attr( $status['box_class'] ); + $message = wp_kses_post( $status['message'] ); + Sensei()->notices->add_notice( $message, $notice_level ); + + // should not occur anymore + if ( ! empty( Sensei()->frontend->messages ) ) { + $messages .= wp_kses_post( Sensei()->frontend->messages ); + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Output escaped above. + echo $messages; } + } - // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Output escaped above. - echo $message; + /** + * Creates a login notice when appropriate. + * + * @since 3.2.0 + * @return void + */ + public static function login_notice() { + $login_notice = Sensei_Utils::login_notice( 'quiz' ); + if ( false === $login_notice ) { + return; + } + $message = wp_kses_post( $login_notice ); + $notice_level = 'info'; + Sensei()->notices->add_notice( $message, $notice_level ); } /** diff --git a/includes/class-sensei-utils.php b/includes/class-sensei-utils.php index 2f5ecc6fe9..17bb69de87 100755 --- a/includes/class-sensei-utils.php +++ b/includes/class-sensei-utils.php @@ -1217,7 +1217,6 @@ public static function sensei_user_quiz_status_message( $lesson_id = 0, $user_id $box_class = 'info'; $message = __( "You have not taken this lesson's quiz yet", 'sensei-lms' ); $extra = ''; - if ( $lesson_id > 0 && $user_id > 0 ) { // Course ID $course_id = absint( get_post_meta( $lesson_id, '_lesson_course', true ) ); @@ -1255,19 +1254,17 @@ public static function sensei_user_quiz_status_message( $lesson_id = 0, $user_id // Quiz questions $has_quiz_questions = Sensei_Lesson::lesson_quiz_has_questions( $lesson_id ); - if ( ! $started_course ) { + if ( ! is_user_logged_in() ) { // should never occur, since user_id > 0 is true + $status = 'login_required'; + $box_class = 'info'; + $message = __( 'You must be logged in to take this quiz', 'sensei-lms' ); + } elseif ( ! $started_course ) { $status = 'not_started_course'; $box_class = 'info'; // translators: Placeholders are an opening and closing tag linking to the course permalink. $message = sprintf( __( 'Please sign up for %1$sthe course%2$s before taking this quiz', 'sensei-lms' ), '', '' ); - - } elseif ( ! is_user_logged_in() ) { - - $status = 'login_required'; - $box_class = 'info'; - $message = __( 'You must be logged in to take this quiz', 'sensei-lms' ); - } + // Lesson/Quiz is marked as complete thus passing any quiz restrictions elseif ( $lesson_complete ) { @@ -1389,12 +1386,67 @@ public static function sensei_user_quiz_status_message( $lesson_id = 0, $user_id ); } + /** + * Returns a login notice, or false when login is not required. + * + * @since 3.2.0 + * @param string $context Either 'lesson' or 'quiz'. + * @return string|bool Login notice, or false. + */ + public static function login_notice( $context = 'lesson' ) { + switch ($context) { + case 'lesson': + $lesson_id = get_the_ID(); + if ( 'lesson' !== get_post_type( $lesson_id ) + && ! Sensei()->lesson->user_should_login( $lesson_id ) ) { + return false; + } + $context_label = __( 'lesson', 'sensei-lms' ); + break; + case 'quiz': + $lesson_id = Sensei()->quiz->get_lesson_id( get_the_ID() ); + if ( 'lesson' !== get_post_type( $lesson_id ) + && ! Sensei()->lesson->user_should_login( $lesson_id ) ) { + return false; + } + $context_label = __( 'quiz', 'sensei-lms' ); + break; + case 'module': + if ( ! is_tax('module') ) { + return false; + }; + $course_id = isset( $_GET['course_id'] ) ? intval( $_GET['course_id'] ) : null; + if ( empty( $course_id ) || 'course' !== get_post_type( $course_id ) ) { + return false; + } + if ( ! Sensei()->modules->user_should_login( $course_id ) ) { + return false; + } + $context_label = __( 'module', 'sensei-lms' ); + break; + default: + return false; + break; + } + + $anchor_before = ''; + $anchor_after = ''; + $message = sprintf( + // translators: Placeholders %1$s and %2$s are an opening and closing tag linking to the login URL, %3$s is the context ("lesson" or "quiz"). + __( 'Please %1$slog in%2$s to access the %3$s.', 'sensei-lms' ), + $anchor_before, + $anchor_after, + $context_label + ); + return $message; + } + /** * Start course for user * * @since 1.4.8 - * @param integer $user_id User ID - * @param integer $course_id Course ID + * @param integer $user_id User ID. + * @param integer $course_id Course ID. * @return bool|int False if they haven't started; Comment ID of course progress if they have. */ public static function user_start_course( $user_id = 0, $course_id = 0 ) { diff --git a/includes/class-sensei.php b/includes/class-sensei.php index f2a258df93..66115d1d77 100755 --- a/includes/class-sensei.php +++ b/includes/class-sensei.php @@ -909,7 +909,7 @@ public function check_user_permissions( $page = '' ) { $this->permissions_message['title'] = get_the_title( $post->ID ) . ': ' . __( 'Restricted Access', 'sensei-lms' ); $course_link = '' . __( 'course', 'sensei-lms' ) . ''; // translators: The placeholder %1$s is a link to the Course. - $this->permissions_message['message'] = sprintf( __( 'Please sign up for the %1$s before taking this Quiz.', 'sensei-lms' ), $course_link ); + $this->permissions_message['message'] = sprintf( __( 'Please sign up for the %1$s before taking this quiz.', 'sensei-lms' ), $course_link ); } // End if(). break; default: diff --git a/includes/hooks/template.php b/includes/hooks/template.php index dc000f37fa..c6b2399fe4 100644 --- a/includes/hooks/template.php +++ b/includes/hooks/template.php @@ -73,6 +73,10 @@ // hook the single course title on the single course page add_action( 'sensei_single_course_content_inside_before', array( $sensei->course, 'course_image' ), 20 ); +// @since 1.9.10 +// hook in the course prerequisite completion message +add_action( 'sensei_single_course_content_inside_before', array( 'Sensei_Course', 'prerequisite_complete_message' ), 20 ); + // @1.9.0 // Filter the content and replace it with the excerpt if the user doesn't have full access add_filter( 'the_content', array( 'Sensei_Course', 'single_course_content' ) ); @@ -136,6 +140,10 @@ // hook in the quiz user message add_action( 'sensei_single_quiz_content_inside_before', array( 'Sensei_Quiz', 'the_user_status_message' ), 40 ); +// since 3.2.0 +// hook in the login notice. +add_action( 'sensei_single_quiz_content_inside_before', array( 'Sensei_Quiz', 'login_notice' ), 40 ); + // @since 1.9.0 // hook in the question title, description and quesiton media add_action( 'sensei_quiz_question_inside_before', array( 'Sensei_Question', 'the_question_title' ), 10 ); @@ -187,14 +195,14 @@ // hook in the lesson prerequisite completion message add_action( 'sensei_single_lesson_content_inside_before', array( 'Sensei_Lesson', 'prerequisite_complete_message' ), 20 ); -// @since 1.9.10 -// hook in the course prerequisite completion message -add_action( 'sensei_single_course_content_inside_before', array( 'Sensei_Course', 'prerequisite_complete_message' ), 20 ); - // @since 1.9.0 // hook the single lesson course_signup_link add_action( 'sensei_single_lesson_content_inside_before', array( 'Sensei_Lesson', 'course_signup_link' ), 30 ); +// @since 3.2.0 +// hook the single lesson login_notice +add_action( 'sensei_single_lesson_content_inside_before', array( 'Sensei_Lesson', 'login_notice' ), 30 ); + // @since 1.9.0 // Add the quiz specific buttons and notices to the lesson add_action( 'sensei_single_lesson_content_inside_after', array( 'Sensei_Lesson', 'footer_quiz_call_to_action' ) ); @@ -329,5 +337,6 @@ add_action( 'sensei_course_results_content_inside_before', array( $sensei->notices, 'maybe_print_notices' ) ); add_action( 'sensei_single_course_content_inside_before', array( $sensei->notices, 'maybe_print_notices' ), 40 ); add_action( 'sensei_single_lesson_content_inside_before', array( $sensei->notices, 'maybe_print_notices' ), 40 ); +add_action( 'sensei_single_quiz_content_inside_before', array( $sensei->notices, 'maybe_print_notices' ), 40 ); add_action( 'sensei_taxonomy_module_content_inside_before', array( $sensei->notices, 'maybe_print_notices' ), 40 ); diff --git a/templates/single-lesson.php b/templates/single-lesson.php index 19bb75eebc..b08b74ba07 100644 --- a/templates/single-lesson.php +++ b/templates/single-lesson.php @@ -35,8 +35,16 @@ * @param integer $lesson_id * * @hooked deprecated_lesson_image_hook - 10 - * @hooked Sensei_Lesson::lesson_image() - 17 + * @hooked Sensei_Lesson::maybe_start_lesson - 10 + * @hooked Sensei_Lesson::the_title - 15 + * @hooked Sensei_Lesson::lesson_image - 17 + * @hooked Sensei_Lesson::user_lesson_quiz_status_message - 20 + * @hooked Sensei_Lesson::prerequisite_complete_message - 20 * @hooked deprecate_lesson_single_main_content_hook - 20 + * @hooked Sensei_Lesson::course_signup_link - 30 + * @hooked Sensei_Lesson::login_notice - 30 + * @hooked Sensei_Messages::send_message_link - 30 + * @hooked Sensei_Notices::maybe_print_notices 40 */ do_action( 'sensei_single_lesson_content_inside_before', get_the_ID() ); diff --git a/templates/single-quiz.php b/templates/single-quiz.php index 5aa90e92b8..927b103efd 100644 --- a/templates/single-quiz.php +++ b/templates/single-quiz.php @@ -26,8 +26,14 @@ * * @since 1.9.0 * - * @hooked Sensei_Quiz::the_title - 20 - * @hooked Sensei_Quiz::the_user_status_message - 40 + * @hooked Sensei_Quiz::start_quiz_questions_loop - 10 + * @hooked Sensei_Quiz::user_quiz_submit_listener - 10 + * @hooked Sensei_Quiz::user_save_quiz_answers_listener - 10 + * @hooked Sensei_Quiz::the_title - 20 + * @hooked Sensei_Quiz::the_user_status_message - 40 + * @hooked Sensei_Quiz::login_notice - 40 + * @hooked Sensei_Quiz::maybe_print_notices - 40 + * @hooked Sensei_Quiz::load_global_quiz_data - 80 * @param integer $quiz_id */ do_action( 'sensei_single_quiz_content_inside_before', get_the_ID() );