diff --git a/wp-content/themes/pub/wporg-learn-2024/functions.php b/wp-content/themes/pub/wporg-learn-2024/functions.php index 31649871b..2532a532f 100644 --- a/wp-content/themes/pub/wporg-learn-2024/functions.php +++ b/wp-content/themes/pub/wporg-learn-2024/functions.php @@ -2,6 +2,7 @@ namespace WordPressdotorg\Theme\Learn_2024; +use WP_HTML_Tag_Processor; use function WPOrg_Learn\Sensei\{get_my_courses_page_url, get_lesson_has_published_course}; // Block files @@ -42,6 +43,8 @@ add_filter( 'wporg_block_site_breadcrumbs', __NAMESPACE__ . '\set_site_breadcrumbs' ); add_filter( 'taxonomy_template_hierarchy', __NAMESPACE__ . '\modify_taxonomy_template_hierarchy' ); +add_filter( 'sensei_learning_mode_lesson_status_icon', __NAMESPACE__ . '\modify_lesson_status_icon_add_aria', 10, 2 ); + remove_filter( 'template_include', array( 'Sensei_Templates', 'template_loader' ), 10, 1 ); /** @@ -405,3 +408,34 @@ function modify_taxonomy_template_hierarchy( $templates ) { return $templates; } + +/** + * Filter the lesson status icon. + * + * @param string $icon The icon HTML. + * @param string $status The lesson status. + * + * @return string The updated icon HTML with aria data. + */ +function modify_lesson_status_icon_add_aria( $icon, $status ) { + // These statuses have been copied from Sensei\Blocks\Course_Theme\Course_Navigation\ICONS. + $labels = array( + 'not-started' => __( 'Not started', 'wporg-learn' ), + 'in-progress' => __( 'In progress', 'wporg-learn' ), + 'ungraded' => __( 'Ungraded', 'wporg-learn' ), + 'completed' => __( 'Completed', 'wporg-learn' ), + 'failed' => __( 'Failed', 'wporg-learn' ), + 'locked' => __( 'Locked', 'wporg-learn' ), + 'preview' => __( 'Preview', 'wporg-learn' ), + ); + + if ( ! isset( $labels[ $status ] ) ) { + return $icon; + } + + $html = new WP_HTML_Tag_Processor( $icon ); + $html->next_tag( 'svg' ); + $html->set_attribute( 'aria-label', $labels[ $status ] ); + $html->set_attribute( 'role', 'img' ); + return $html->get_updated_html(); +} diff --git a/wp-content/themes/pub/wporg-learn-2024/inc/block-hooks.php b/wp-content/themes/pub/wporg-learn-2024/inc/block-hooks.php index 223dc4b26..2e04c329e 100644 --- a/wp-content/themes/pub/wporg-learn-2024/inc/block-hooks.php +++ b/wp-content/themes/pub/wporg-learn-2024/inc/block-hooks.php @@ -5,9 +5,13 @@ * @package wporg-learn-2024 */ -use function DevHub\is_parsed_post_type; +namespace WordPressdotorg\Theme\Learn_2024\Block_Hooks; + +use WP_HTML_Tag_Processor, Sensei_Utils, Sensei_Course, Sensei_Lesson; add_filter( 'render_block_data', __NAMESPACE__ . '\modify_header_template_part' ); +add_filter( 'render_block_data', __NAMESPACE__ . '\modify_course_outline_lesson_block_attrs' ); +add_filter( 'render_block_sensei-lms/course-outline', __NAMESPACE__ . '\update_course_outline_block_add_aria', 10, 2 ); /** * Update header template based on current query. @@ -27,3 +31,76 @@ function modify_header_template_part( $parsed_block ) { } return $parsed_block; } + +/** + * Add the status to the outline lesson block as a class, so that it can be + * read by the `update_course_outline_block_add_aria` function. + * + * @param array $parsed_block The block being rendered. + * + * @return array The updated block. + */ +function modify_course_outline_lesson_block_attrs( $parsed_block ) { + if ( + 'sensei-lms/course-outline-lesson' !== $parsed_block['blockName'] || + ! isset( $parsed_block['attrs']['id'] ) + ) { + return $parsed_block; + } + + $lesson_id = $parsed_block['attrs']['id']; + $classes = array(); + $classes[] = $parsed_block['attrs']['className'] ?? ''; + + $status = 'not-started'; + $lesson_status = Sensei_Utils::user_lesson_status( $lesson_id ); + if ( $lesson_status ) { + $status = $lesson_status->comment_approved; + } + $classes[] = 'is-' . $status; + + // Add previewable and prerequisite-required lesson title to lesson data + if ( + ( ! Sensei_Utils::is_preview_lesson( $lesson_id ) && ! Sensei_Course::is_user_enrolled( get_the_ID() ) ) + || ! Sensei_Lesson::is_prerequisite_complete( $lesson_id, get_current_user_id() ) + ) { + $classes[] = 'is-locked'; + } + + $parsed_block['attrs']['className'] = implode( ' ', $classes ); + + return $parsed_block; +} + +/** + * Filter the course outline block to add accessible attributes. + * + * Note, this filters the entire `sensei-lms/course-outline` block instead of + * `sensei-lms/course-outline-lesson` due to Sensei's rendering of these + * blocks. The outline module & outline lesson blocks are not rendered + * individually, so they cannot be independently filtered. + * + * @param string $block_content The block content. + * @param array $block The full block, including name and attributes. + * + * @return string The updated icon HTML with aria data. + */ +function update_course_outline_block_add_aria( $block_content, $block ) { + $html = new WP_HTML_Tag_Processor( $block_content ); + + $label = ''; + while ( $html->next_tag( array( 'class_name' => 'wp-block-sensei-lms-course-outline-lesson' ) ) ) { + if ( $html->has_class( 'is-complete' ) ) { + $label = __( 'Completed', 'wporg-learn' ); + } else if ( $html->has_class( 'is-in-progress' ) ) { + $label = __( 'In progress', 'wporg-learn' ); + } else { + $label = __( 'Not started', 'wporg-learn' ); + } + + $html->next_tag( 'svg' ); + $html->set_attribute( 'aria-label', $label ); + $html->set_attribute( 'role', 'img' ); + } + return $html->get_updated_html(); +} diff --git a/wp-content/themes/pub/wporg-learn-2024/src/course-outline/index.js b/wp-content/themes/pub/wporg-learn-2024/src/course-outline/index.js index 941ac06ee..a36ecba22 100644 --- a/wp-content/themes/pub/wporg-learn-2024/src/course-outline/index.js +++ b/wp-content/themes/pub/wporg-learn-2024/src/course-outline/index.js @@ -1,32 +1,44 @@ -/* global wporgCourseOutlineData */ +/* global wporgCourseOutlineL10n */ import { Icon, drafts, lockOutline } from '@wordpress/icons'; import { renderToString } from '@wordpress/element'; document.addEventListener( 'DOMContentLoaded', () => { - if ( ! wporgCourseOutlineData ) { - return; - } + /** + * Find all in progress lessons, and replace the status icon with the Gutenberg-style `drafts` icon. + */ + document.querySelectorAll( '.wp-block-sensei-lms-course-outline-lesson.is-in-progress' ).forEach( ( link ) => { + const statusIcon = link.querySelector( '.wp-block-sensei-lms-course-outline-lesson__status' ); + if ( statusIcon ) { + const iconString = renderToString( + + ); - wporgCourseOutlineData[ 'in-progress' ]?.forEach( ( title ) => { - const lessonLinks = document.querySelectorAll( '.wp-block-sensei-lms-course-outline-lesson' ); - lessonLinks.forEach( ( link ) => { - const span = link.querySelector( 'span' ); - if ( span && span.textContent.trim() === title ) { - const statusIcon = link.querySelector( '.wp-block-sensei-lms-course-outline-lesson__status' ); - if ( statusIcon ) { - statusIcon.outerHTML = renderToString( ); - } - } - } ); + // Remove the `aria-hidden` attribute from the icon, as it has a readable label. + statusIcon.outerHTML = iconString.replace( ' aria-hidden="true"', '' ); + } } ); - wporgCourseOutlineData.locked?.forEach( ( title ) => { - const lessonLinks = document.querySelectorAll( '.wp-block-sensei-lms-course-outline-lesson' ); - lessonLinks.forEach( ( link ) => { - const span = link.querySelector( 'span' ); - if ( span && span.textContent.trim() === title ) { - span.insertAdjacentHTML( 'afterend', renderToString( ) ); - } - } ); + + /** + * Find all locked lessons, and inject a `lock` icon after the title. + */ + document.querySelectorAll( '.wp-block-sensei-lms-course-outline-lesson.is-locked' ).forEach( ( link ) => { + const span = link.querySelector( 'span' ); + if ( span ) { + span.insertAdjacentHTML( + 'afterend', + renderToString( + <> + + { wporgCourseOutlineL10n.locked } + + ) + ); + } } ); } ); diff --git a/wp-content/themes/pub/wporg-learn-2024/src/course-outline/index.php b/wp-content/themes/pub/wporg-learn-2024/src/course-outline/index.php index 0f2aff752..1f87c8e3f 100644 --- a/wp-content/themes/pub/wporg-learn-2024/src/course-outline/index.php +++ b/wp-content/themes/pub/wporg-learn-2024/src/course-outline/index.php @@ -22,50 +22,13 @@ function enqueue_assets() { true ); - $lesson_data = get_lesson_data(); wp_localize_script( 'wporg-learn-2024-course-outline', - 'wporgCourseOutlineData', - $lesson_data + 'wporgCourseOutlineL10n', + array( + 'inProgress' => __( 'In progress', 'wporg-learn' ), + 'locked' => __( 'Locked', 'wporg-learn' ), + ) ); } add_action( 'wp_enqueue_scripts', 'enqueue_assets' ); - -/** - * Get the titles of specific status lessons. - * - * The returned array $lesson_data has the following structure: - * [ - * 'in-progress' => [ (string) The title of the lesson, ... ], - * 'locked' => [ (string) The title of the lesson, ... ], - * ] - * - * @return array $lesson_data Array of lesson data. - */ -function get_lesson_data() { - $lesson_data = array(); - $lesson_ids = Sensei()->course->course_lessons( get_the_ID(), 'publish', 'ids' ); - - foreach ( $lesson_ids as $lesson_id ) { - $user_lesson_status = Sensei_Utils::user_lesson_status( $lesson_id, get_current_user_id() ); - $lesson_title = get_the_title( $lesson_id ); - $is_preview_lesson = Sensei_Utils::is_preview_lesson( $lesson_id ); - - // Add in-progress lesson title to lesson data - if ( $user_lesson_status ) { - $lesson_status = $user_lesson_status->comment_approved; - if ( 'in-progress' === $lesson_status ) { - $lesson_data['in-progress'][] = $lesson_title; - } - } - - // Add previewable and prerequisite-required lesson title to lesson data - if ( ( ! $is_preview_lesson && ! Sensei_Course::is_user_enrolled( get_the_ID() ) ) - || ! Sensei_Lesson::is_prerequisite_complete( $lesson_id, get_current_user_id() ) - ) { - $lesson_data['locked'][] = $lesson_title; - } - } - - return $lesson_data; -}