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;
-}