diff --git a/assets/css/admin-custom.scss b/assets/css/admin-custom.scss index 5adef15b40..5512d235f6 100644 --- a/assets/css/admin-custom.scss +++ b/assets/css/admin-custom.scss @@ -276,8 +276,18 @@ div.question_boolean_fields { } } -.sensei-settings .sensei-custom-navigation__heading { - flex-flow: column wrap; +.sensei-settings { + .sensei-custom-navigation__heading { + flex-flow: column wrap; + } + + .sensei-custom-navigation__links a { + top: 0; + + &:focus { + box-shadow: none; + } + } } .sensei-custom-navigation__separator { diff --git a/assets/js/admin/course-theme/course-theme-sidebar.js b/assets/js/admin/course-theme/course-theme-sidebar.js index acdc0b60c0..fa973b5258 100644 --- a/assets/js/admin/course-theme/course-theme-sidebar.js +++ b/assets/js/admin/course-theme/course-theme-sidebar.js @@ -35,7 +35,7 @@ const CourseThemeSidebar = () => { > { globalLearningModeEnabled ? (

- + { __( 'Learning Mode is enabled globally.', 'sensei-lms' @@ -56,7 +56,7 @@ const CourseThemeSidebar = () => { } />

- + { __( 'Change Template', 'sensei-lms' ) }

diff --git a/assets/js/settings.js b/assets/js/settings.js index 2ed46c120c..ef7c7efbda 100644 --- a/assets/js/settings.js +++ b/assets/js/settings.js @@ -2,33 +2,134 @@ jQuery( document ).ready( function ( $ ) { /***** Settings Tabs *****/ const $senseiSettings = $( '#woothemes-sensei.sensei-settings' ); + // Show the current section. + showSection( getCurrentSectionId() ); + + // Switch to the section when the tab is clicked. + $senseiSettings.find( 'a.tab:not(.external)' ).on( 'click', function ( e ) { + const sectionUrl = $( this ).attr( 'href' ); + const sectionId = getSectionIdFromUrl( sectionUrl ); + + if ( ! sectionExists( sectionId ) ) { + return true; + } + + changeCurrentUrl( sectionUrl ); + updateReferer( sectionUrl ); + showSection( sectionId ); + + e.preventDefault(); + } ); + + // Change the section when the user navigates the session history. + addEventListener( 'popstate', ( e ) => { + const sectionId = getSectionIdFromUrl( window.location.href ); + + if ( sectionExists( sectionId ) ) { + updateReferer( window.location.href ); + showSection( sectionId ); + } + } ); + + /** + * Change the current browser URL. + * + * @param {string} url + */ + function changeCurrentUrl( url ) { + window.history.pushState( {}, null, url ); + } + + /** + * Update the hidden referer field. + * + * @param {string} url + */ + function updateReferer( url ) { + const urlObject = new URL( url ); + + $senseiSettings.find( 'input[name="_wp_http_referer"]' ) + .val( urlObject.pathname + urlObject.search ); + } + + /** + * Hide all sections. + */ function hideAllSections() { - $senseiSettings.find( 'section' ).hide(); - $senseiSettings.find( 'a.tab' ).removeClass( 'current' ); + $senseiSettings.find( 'section' ) + .hide(); } - function show( sectionId = '' ) { - $senseiSettings.find( `section#${ sectionId }` ).show(); + /** + * Show a settings section. + * + * @param {string} sectionId + */ + function showSection( sectionId ) { + hideAllSections(); + hideSettingsFormElements( sectionId ); + + $senseiSettings.find( `section#${ sectionId }` ) + .show(); + + $senseiSettings.find( 'a.tab.current' ) + .removeClass( 'current' ) + $senseiSettings - .find( `[href="#${ sectionId }"]` ) + .find( `a.tab[href*="tab=${ sectionId }"]` ) .addClass( 'current' ); + sensei_log_event( 'settings_view', { view: sectionId } ); markSectionAsVisited( sectionId ); } - // Hide header and submit on page load if needed - hideSettingsFormElements(); + /** + * Get section id from the current URL. + * + * @returns {string} + */ + function getCurrentSectionId() { + return getSectionIdFromUrl( window.location.href ); + } + + /** + * Get section id from a URL. + * + * @param {string} url + * @returns {string} + */ + function getSectionIdFromUrl( url ) { + const urlParams = new URLSearchParams( url ); - function hideSettingsFormElements() { - const urlHashSectionId = window.location.hash?.replace( '#', '' ); - if ( urlHashSectionId === 'woocommerce-settings' ) { + return urlParams.get( 'tab' ) + || url.split( '#' )[1] + || 'default-settings'; + } + + /** + * Check if a section exists. + * + * @param {string} sectionId + * @returns {boolean} + */ + function sectionExists( sectionId ) { + return $( '#' + sectionId ).length > 0; + } + + /** + * Hide the header and submit button if there are no settings in the section. + * + * @param {string} sectionId + */ + function hideSettingsFormElements( sectionId ) { + if ( sectionId === 'woocommerce-settings' ) { const formRows = $senseiSettings.find( '#woocommerce-settings tr' ); // Hide header and submit if there is not settings form in section hideHeaderAndSubmit( ! formRows.length && $senseiSettings.find( '#sensei-promo-banner' ) ); - } else if ( urlHashSectionId === 'sensei-content-drip-settings' ) { + } else if ( sectionId === 'sensei-content-drip-settings' ) { const formRows = $senseiSettings.find( '#sensei-content-drip-settings tr' ); @@ -42,6 +143,11 @@ jQuery( document ).ready( function ( $ ) { } } + /** + * Hide the header and submit button. + * + * @param {boolean} shouldHide + */ function hideHeaderAndSubmit( shouldHide ) { if ( shouldHide ) { $senseiSettings.find( '#submit' ).hide(); @@ -52,35 +158,12 @@ jQuery( document ).ready( function ( $ ) { } } - window.onhashchange = hideSettingsFormElements; - - // Show general settings section if no section is selected in url hasn. - const defaultSectionId = 'default-settings'; - const urlHashSectionId = window.location.hash?.replace( '#', '' ); - hideAllSections(); - if ( urlHashSectionId ) { - show( urlHashSectionId ); - } else { - show( defaultSectionId ); - } - - $senseiSettings.find( 'a.tab' ).on( 'click', function ( e ) { - const queryString = window.location.search; - const urlParams = new URLSearchParams( queryString ); - - const href = $( this ).attr( 'href' ); - if ( urlParams.has( 'tab' ) || ! href?.includes( '#' ) ) { - return true; - } - - e.preventDefault(); - const sectionId = href.split( '#' )[ 1 ]; - window.location.hash = '#' + sectionId; - hideAllSections(); - show( sectionId ); - return false; - } ); - + /** + * Mark a section as visited. + * This is used to track which sections are being used. + * + * @param {string} sectionId + */ function markSectionAsVisited( sectionId ) { const data = new FormData(); data.append( 'action', 'sensei_settings_section_visited' ); diff --git a/changelog/fix-settings-form-redirect b/changelog/fix-settings-form-redirect new file mode 100644 index 0000000000..9549e4b354 --- /dev/null +++ b/changelog/fix-settings-form-redirect @@ -0,0 +1,4 @@ +Significance: patch +Type: fixed + +Settings form not redirecting to the correct tab when submitted diff --git a/config/psalm/psalm-baseline.xml b/config/psalm/psalm-baseline.xml index c1186c515a..472edad268 100644 --- a/config/psalm/psalm-baseline.xml +++ b/config/psalm/psalm-baseline.xml @@ -309,9 +309,6 @@ $extensions self::$instance - - new Sensei_Setup_Wizard_Pages() - $extension->price @@ -324,9 +321,6 @@ $screen->id - - Sensei_Setup - @@ -463,18 +457,6 @@ isset( $this->local_plugin_updates ) - - - $this->get_email_notification_url() - - - '/admin.php?page=sensei-settings#email-notification-settings' - 'admin.php?page=sensei-settings&tab=email-notification-settings' - - - array - - $post->post_title @@ -735,18 +717,11 @@ - - $course_id - - + $course_id $course_id $course_id - get_permalink( absint( $target_post_id ?? $course_id ) ) - - $target_post_id - @@ -1080,10 +1055,6 @@ esc_html( sprintf( __( 'Please supply a %1$s ID.', 'sensei-lms' ) ), $post_type ) - - Sensei()->setup_wizard->pages - Sensei()->setup_wizard->pages - @@ -2877,9 +2848,7 @@ $this->file - - ! $encoded_feedback - ! $encoded_feedback + ! is_array( $quiz_answers ) ! is_array( $quiz_answers ) ! is_array( $unprepared_answers ) @@ -3896,7 +3865,6 @@ ! self::$instance self::$instance - Sensei()->plugin_url @@ -3915,9 +3883,6 @@ $original_theme - - The - @@ -5983,9 +5948,6 @@ Sensei_REST_API_Setup_Wizard_Controller - - $this->setup_wizard->pages - diff --git a/includes/admin/home/quick-links/class-sensei-home-quick-links-provider.php b/includes/admin/home/quick-links/class-sensei-home-quick-links-provider.php index 2730b8ed45..2f7cdc1c2a 100644 --- a/includes/admin/home/quick-links/class-sensei-home-quick-links-provider.php +++ b/includes/admin/home/quick-links/class-sensei-home-quick-links-provider.php @@ -33,9 +33,9 @@ public function get(): array { __( 'Settings', 'sensei-lms' ), [ $this->create_item( __( 'Email notifications', 'sensei-lms' ), admin_url( $this->get_email_notification_url() ) ), - $this->create_item( __( 'Learning mode', 'sensei-lms' ), admin_url( '/admin.php?page=sensei-settings#appearance-settings' ) ), - $this->create_item( __( 'WooCommerce', 'sensei-lms' ), admin_url( '/admin.php?page=sensei-settings#woocommerce-settings' ) ), - $this->create_item( __( 'Content drip', 'sensei-lms' ), admin_url( '/admin.php?page=sensei-settings#sensei-content-drip-settings' ) ), + $this->create_item( __( 'Learning mode', 'sensei-lms' ), admin_url( '/admin.php?page=sensei-settings&tab=appearance-settings' ) ), + $this->create_item( __( 'WooCommerce', 'sensei-lms' ), admin_url( '/admin.php?page=sensei-settings&tab=woocommerce-settings' ) ), + $this->create_item( __( 'Content drip', 'sensei-lms' ), admin_url( '/admin.php?page=sensei-settings&tab=sensei-content-drip-settings' ) ), ] ), $this->create_category( @@ -53,14 +53,14 @@ public function get(): array { /** * Return the correct email notification settings based on the feature flag. * - * @return array The magical link to create a demo course or the link to edit the demo course. + * @return string The magical link to create a demo course or the link to edit the demo course. */ private function get_email_notification_url() { if ( Sensei()->feature_flags->is_enabled( 'email_customization' ) ) { return 'admin.php?page=sensei-settings&tab=email-notification-settings'; } - return '/admin.php?page=sensei-settings#email-notification-settings'; + return '/admin.php?page=sensei-settings&tab=email-notification-settings'; } /** diff --git a/includes/admin/home/tasks/task/class-sensei-home-task-configure-learning-mode.php b/includes/admin/home/tasks/task/class-sensei-home-task-configure-learning-mode.php index 97724239f9..d0038adc6b 100644 --- a/includes/admin/home/tasks/task/class-sensei-home-task-configure-learning-mode.php +++ b/includes/admin/home/tasks/task/class-sensei-home-task-configure-learning-mode.php @@ -46,7 +46,7 @@ public function get_title(): string { * @return string */ public function get_url(): ?string { - return admin_url( 'admin.php?page=sensei-settings#appearance-settings' ); + return admin_url( 'admin.php?page=sensei-settings&tab=appearance-settings' ); } /** diff --git a/includes/class-sensei-course.php b/includes/class-sensei-course.php index 677e6618b3..f213fdb1d7 100755 --- a/includes/class-sensei-course.php +++ b/includes/class-sensei-course.php @@ -412,7 +412,7 @@ private function display_courses_navigation( WP_Screen $screen, array $tabs ) {
diff --git a/includes/class-sensei-lesson.php b/includes/class-sensei-lesson.php index 4a9f1d893b..3498451b56 100755 --- a/includes/class-sensei-lesson.php +++ b/includes/class-sensei-lesson.php @@ -218,7 +218,7 @@ private function display_lessons_navigation( WP_Screen $screen ) {
diff --git a/includes/class-sensei-settings-api.php b/includes/class-sensei-settings-api.php index b19838f778..425fa41175 100755 --- a/includes/class-sensei-settings-api.php +++ b/includes/class-sensei-settings-api.php @@ -299,18 +299,24 @@ public function settings_tabs() { $html .= '
    ' . "\n"; $sections = array(); + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce verification is not required here. + $current_tab = isset( $_GET['tab'] ) ? sanitize_key( wp_unslash( $_GET['tab'] ) ) : 'default-settings'; foreach ( $this->tabs as $k => $v ) { $classes = 'tab'; - if ( 'default-settings' === $k ) { + if ( $current_tab === $k ) { $classes .= ' current'; } + if ( ! empty( $v['external'] ) ) { + $classes .= ' external'; + } + $sections[ $k ] = array( 'href' => isset( $v['href'] ) ? esc_attr( $v['href'] ) - : admin_url( 'admin.php?page=' . $this->token . '#' . esc_attr( $k ) ), + : admin_url( 'admin.php?page=' . $this->token . '&tab=' . esc_attr( $k ) ), 'name' => esc_attr( $v['name'] ), 'class' => esc_attr( $classes ), ); diff --git a/includes/internal/emails/class-settings-menu.php b/includes/internal/emails/class-settings-menu.php index 4b0feff2f6..b963b02ba0 100644 --- a/includes/internal/emails/class-settings-menu.php +++ b/includes/internal/emails/class-settings-menu.php @@ -44,6 +44,7 @@ public function replace_email_tab( array $sections ) { 'name' => __( 'Emails', 'sensei-lms' ), 'description' => __( 'Settings for email notifications sent from your site.', 'sensei-lms' ), 'href' => admin_url( 'admin.php?page=sensei-settings&tab=email-notification-settings' ), + 'external' => true, ); return $sections; } diff --git a/tests/unit-tests/internal/emails/test-class-settings-menu.php b/tests/unit-tests/internal/emails/test-class-settings-menu.php index 22d44067d3..4dcedb2572 100644 --- a/tests/unit-tests/internal/emails/test-class-settings-menu.php +++ b/tests/unit-tests/internal/emails/test-class-settings-menu.php @@ -35,6 +35,7 @@ public function testReplaceEmailTab_SectionsGiven_ReplacesEmailNoficationSetting 'name' => 'Emails', 'description' => 'Settings for email notifications sent from your site.', 'href' => admin_url( 'admin.php?page=sensei-settings&tab=email-notification-settings' ), + 'external' => true, ], ]; self::assertSame( $expected, $sections ); diff --git a/tests/unit-tests/test-class-lesson.php b/tests/unit-tests/test-class-lesson.php index a9276dc197..60ff3336c5 100644 --- a/tests/unit-tests/test-class-lesson.php +++ b/tests/unit-tests/test-class-lesson.php @@ -824,7 +824,7 @@ public function testAddCustomNavigation_WhenCorrectScreen_HasOutput( string $scr
diff --git a/tests/unit-tests/test-class-sensei-settings-api.php b/tests/unit-tests/test-class-sensei-settings-api.php index dd1ca2395c..50f643b7fd 100644 --- a/tests/unit-tests/test-class-sensei-settings-api.php +++ b/tests/unit-tests/test-class-sensei-settings-api.php @@ -1,6 +1,8 @@ assertStringNotContainsString( ' multiple ', $output ); } + + public function testSettingsTabs_WhenNoTabParam_AddsTheCurrentClassToTheDefaultTabLink() { + /** Arrange. */ + $settings = new Sensei_Settings_Api(); + $settings->has_tabs = true; + $settings->sections = array( + 'default-settings' => array( + 'name' => 'Default Settings', + ), + 'other-settings' => array( + 'name' => 'Other Settings', + ), + ); + + /** Act. */ + ob_start(); + $settings->general_init(); + $settings->settings_tabs(); + $tabs = ob_get_clean(); + + /** Assert. */ + $this->assertStringContainsString( 'Default Settings', $tabs ); + } + + public function testSettingsTabs_WhenHasTabParam_AddsTheCurrentClassToTheTabLink() { + /** Arrange. */ + $settings = new Sensei_Settings_Api(); + $settings->has_tabs = true; + $settings->sections = array( + 'default-settings' => array( + 'name' => 'Default Settings', + ), + 'other-settings' => array( + 'name' => 'Other Settings', + ), + ); + + $_GET['tab'] = 'other-settings'; + + /** Act. */ + ob_start(); + $settings->general_init(); + $settings->settings_tabs(); + $tabs = ob_get_clean(); + + /** Assert. */ + $this->assertStringContainsString( 'Other Settings', $tabs ); + } + + public function testSettingsTabs_WhenTabIsExternal_AddsTheExternalClassToTheTabLink() { + /** Arrange. */ + $settings = new Sensei_Settings_Api(); + $settings->has_tabs = true; + $settings->sections = array( + 'other-settings' => array( + 'name' => 'Other Settings', + 'external' => true, + ), + ); + + /** Act. */ + ob_start(); + $settings->general_init(); + $settings->settings_tabs(); + $tabs = ob_get_clean(); + + /** Assert. */ + $this->assertStringContainsString( 'Other Settings', $tabs ); + } }