WordPress.org

Make WordPress Core

Ticket #51086: 51086.5.diff

File 51086.5.diff, 8.5 KB (added by stevegrunwell, 6 months ago)

Prevent <section> elements from being broken if no fields are registered for a setting (see https://github.com/stevegrunwell/wp-admin-tabbed-settings-pages/pull/11)

Line 
1Index: Gruntfile.js
2===================================================================
3--- Gruntfile.js        (revision 48834)
4+++ Gruntfile.js        (working copy)
5@@ -269,6 +269,7 @@
6                                        [ WORKING_DIR + 'wp-admin/js/postbox.js' ]: [ './src/js/_enqueues/admin/postbox.js' ],
7                                        [ WORKING_DIR + 'wp-admin/js/revisions.js' ]: [ './src/js/_enqueues/wp/revisions.js' ],
8                                        [ WORKING_DIR + 'wp-admin/js/set-post-thumbnail.js' ]: [ './src/js/_enqueues/admin/set-post-thumbnail.js' ],
9+                                       [ WORKING_DIR + 'wp-admin/js/settings-tabs.js' ]: [ './src/js/_enqueues/admin/settings-tabs.js' ],
10                                        [ WORKING_DIR + 'wp-admin/js/svg-painter.js' ]: [ './src/js/_enqueues/wp/svg-painter.js' ],
11                                        [ WORKING_DIR + 'wp-admin/js/tags-box.js' ]: [ './src/js/_enqueues/admin/tags-box.js' ],
12                                        [ WORKING_DIR + 'wp-admin/js/tags-suggest.js' ]: [ './src/js/_enqueues/admin/tags-suggest.js' ],
13Index: src/js/_enqueues/admin/settings-tabs.js
14===================================================================
15--- src/js/_enqueues/admin/settings-tabs.js     (nonexistent)
16+++ src/js/_enqueues/admin/settings-tabs.js     (working copy)
17@@ -0,0 +1,51 @@
18+/**
19+ * Scripting for WP-Admin Tabs.
20+ *
21+ * @output wp-admin/js/settings-tabs.js
22+ */
23+
24+( function ( window, document ) {
25+       const tabWrapper = document.querySelector( '.nav-tab-wrapper' );
26+       const tabs = tabWrapper.querySelectorAll( '.nav-tab' );
27+       const panels = document.querySelectorAll( 'section[role="tabpanel"]' );
28+
29+       /**
30+        * Set the current tab by its ID.
31+        *
32+        * @param {string} tabId - The ID of the active tab.
33+        */
34+       function setActiveTab( tabId ) {
35+               tabs.forEach( function ( tab ) {
36+                       const current = tab.id === tabId;
37+                       tab.classList.toggle( 'nav-tab-active', current );
38+                       tab.setAttribute( 'aria-selected', current );
39+               } );
40+
41+               panels.forEach( function ( panel ) {
42+                       if ( panel.getAttribute('aria-labelledby') === tabId ) {
43+                               panel.removeAttribute( 'hidden' );
44+                       } else {
45+                               panel.setAttribute( 'hidden', true );
46+                       }
47+               } );
48+       }
49+
50+       // Return early if there are no tabs on the page.
51+       if ( ! tabs || ! panels ) {
52+               return;
53+       }
54+
55+       // Determine which tab should be selected.
56+       let currentTab = window.location.hash.substr( 1 );
57+       currentTab = currentTab ? 'nav-tab-' + currentTab : tabs[0].getAttribute( 'id' );
58+
59+       // Set the current tab and register the event listener.
60+       setActiveTab( currentTab );
61+       tabWrapper.addEventListener( 'click', function ( e ) {
62+               if ( 'A' !== e.target.tagName ) {
63+                       return;
64+               }
65+
66+               setActiveTab( e.target.id );
67+       } );
68+}( window, document) );
69
70Property changes on: src/js/_enqueues/admin/settings-tabs.js
71___________________________________________________________________
72Added: svn:eol-style
73## -0,0 +1 ##
74+native
75\ No newline at end of property
76Added: svn:executable
77## -0,0 +1 ##
78+*
79\ No newline at end of property
80Index: src/wp-admin/includes/template.php
81===================================================================
82--- src/wp-admin/includes/template.php  (revision 48834)
83+++ src/wp-admin/includes/template.php  (working copy)
84@@ -1698,6 +1698,64 @@
85 }
86
87 /**
88+ * Render settings sections for a particular page using a tabbed interface.
89+ *
90+ * This function operates the same as do_settings_sections() as part of the Settings API.
91+ *
92+ * @global array $wp_settings_sections Storage array of all settings sections added to admin pages.
93+ * @global array $wp_settings_fields   Storage array of settings fields and info about their pages/sections.
94+ *
95+ * @param string $page The slug name of the page whose settings sections you want to output.
96+ */
97+function do_tabbed_settings_sections( $page ) {
98+       global $wp_settings_sections, $wp_settings_fields;
99+
100+       if ( ! isset( $wp_settings_sections[ $page ] ) ) {
101+               return;
102+       }
103+
104+       $sections = (array) $wp_settings_sections[ $page ];
105+
106+       // If there's only one section, don't bother rendering tabs.
107+       if ( 1 >= count( $sections ) ) {
108+               return do_settings_sections( $page );
109+       }
110+
111+       // Render the list of tabs, then each section.
112+       echo '<nav class="nav-tab-wrapper hide-if-no-js" role="tablist">';
113+       foreach ( $sections as $section ) {
114+               printf(
115+                       '<a href="#%1$s" id="nav-tab-%1$s" class="nav-tab" role="tab">%2$s</a>',
116+                       esc_attr( $section['id'] ),
117+                       esc_html( $section['title'] )
118+               );
119+       }
120+       echo '</nav>';
121+
122+       foreach ( (array) $wp_settings_sections[ $page ] as $section ) {
123+               printf( '<section id="tab-%1$s" role="tabpanel" aria-labelledby="nav-tab-%1$s">', esc_attr( $section['id'] ) );
124+               if ( $section['title'] ) {
125+                       printf( '<h2>%1$s</h2>%2$s', esc_html( $section['title'] ), PHP_EOL );
126+               }
127+
128+               if ( is_callable( $section['callback'] ) ) {
129+                       call_user_func( $section['callback'], $section );
130+               }
131+
132+               if ( isset( $wp_settings_fields[ $page ][ $section['id'] ] ) ) {
133+                       echo '<table class="form-table" role="presentation">';
134+                       do_settings_fields( $page, $section['id'] );
135+                       echo '</table>';
136+               }
137+               echo '</section>';
138+       }
139+
140+       // Finally, ensure the necessary scripts are enqueued.
141+       wp_enqueue_script( 'settings-tabs' );
142+}
143+
144+/**
145  * Print out the settings fields for a particular settings section.
146  *
147  * Part of the Settings API. Use this in a settings page to output
148Index: src/wp-includes/script-loader.php
149===================================================================
150--- src/wp-includes/script-loader.php   (revision 48834)
151+++ src/wp-includes/script-loader.php   (working copy)
152@@ -1079,6 +1079,8 @@
153        // JS-only version of hoverintent (no dependencies).
154        $scripts->add( 'hoverintent-js', '/wp-includes/js/hoverintent-js.min.js', array(), '2.2.1', 1 );
155
156+       $scripts->add( 'settings-tabs', "/wp-admin/js/settings-tabs$suffix.js", array(), false, 1 );
157+
158        $scripts->add( 'customize-base', "/wp-includes/js/customize-base$suffix.js", array( 'jquery', 'json2', 'underscore' ), false, 1 );
159        $scripts->add( 'customize-loader', "/wp-includes/js/customize-loader$suffix.js", array( 'customize-base' ), false, 1 );
160        $scripts->add( 'customize-preview', "/wp-includes/js/customize-preview$suffix.js", array( 'wp-a11y', 'customize-base' ), false, 1 );
161Index: tests/phpunit/tests/admin/includesTemplate.php
162===================================================================
163--- tests/phpunit/tests/admin/includesTemplate.php      (revision 48834)
164+++ tests/phpunit/tests/admin/includesTemplate.php      (working copy)
165@@ -3,6 +3,20 @@
166  * @group admin
167  */
168 class Tests_Admin_includesTemplate extends WP_UnitTestCase {
169+       /**
170+        * Clean up some global settings before each test.
171+        *
172+        * @global $wp_settings_sections
173+        */
174+       public function setUp() {
175+               global $wp_settings_sections;
176+
177+               parent::setUp();
178+
179+               $wp_settings_sections = array();
180+               wp_dequeue_script( 'settings-tabs' );
181+       }
182+
183        function test_equal() {
184                $this->assertEquals( ' selected=\'selected\'', selected( 'foo', 'foo', false ) );
185                $this->assertEquals( ' checked=\'checked\'', checked( 'foo', 'foo', false ) );
186@@ -204,4 +218,69 @@
187                );
188        }
189
190+       /**
191+        * @ticket
192+        * @covers ::do_tabbed_settings_sections()
193+        */
194+       public function test_do_tabbed_settings_sections_with_multiple_sections()
195+       {
196+               add_settings_section(
197+                       'tabbed-settings-1',
198+                       'Tabbed settings 1',
199+                       '__return_empty_string',
200+                       'tabbed-settings'
201+               );
202+               add_settings_section(
203+                       'tabbed-setting-2',
204+                       'Tabbed settings 2',
205+                       '__return_empty_string',
206+                       'tabbed-settings'
207+               );
208+
209+               ob_start();
210+               do_tabbed_settings_sections( 'tabbed-settings' );
211+               $output = ob_get_clean();
212+
213+               $this->assertContains( '<nav class="nav-tab-wrapper', $output );
214+               $this->assertContains(
215+                       '<a href="#tabbed-settings-1" id="nav-tab-tabbed-settings-1" class="nav-tab" role="tab">Tabbed settings 1</a>',
216+                       $output
217+               );
218+               $this->assertContains( '<section id="tab-tabbed-settings-1" role="tabpanel"', $output );
219+
220+               $this->assertTrue(
221+                       wp_script_is( 'settings-tabs', 'enqueued' ),
222+                       'The tab script should have been enqueued.'
223+               );
224
225
226+       }
227+
228+       /**
229+        * @ticket
230+        * @covers ::do_tabbed_settings_sections()
231+        */
232+       public function test_do_tabbed_settings_sections_with_a_single_section()
233+       {
234+               add_settings_section(
235+                       'tabbed-settings-1',
236+                       'Tabbed settings 1',
237+                       '__return_empty_string',
238+                       'tabbed-settings'
239+               );
240+
241+               ob_start();
242+               do_tabbed_settings_sections( 'tabbed-settings' );
243+               $output = ob_get_clean();
244+
245+               $this->assertNotContains( '<nav class="nav-tab-wrapper', $output );
246+               $this->assertNotContains(
247+                       '<a href="#tabbed-settings-1" id="nav-tab-tabbed-settings-1" class="nav-tab" role="tab">Tabbed settings 1</a>',
248+                       $output
249+               );
250+               $this->assertNotContains( '<section id="tab-tabbed-settings-1" role="tabpanel"', $output );
251+
252+               $this->assertFalse(
253+                       wp_script_is( 'settings-tabs', 'enqueued' ),
254+                       'The tab script is unnecessary if content is not being tabbed.'
255+               );
256+       }
257 }