Skip to content

Commit 1b3b794

Browse files
committed
Administration: Allow to wrap Settings sections with custom HTML content.
This changeset improves the `add_settings_section()` function to allow developers to pass extra HTML mark-up to be rendered before and after the settings section. Extra argument `$args` can now be passed to the function, and is an array that can contain the following items: - `before_section`: HTML content to prepend to the section's HTML output. Receives the section's class name provided with the `section_class` argument via an optional `%s` placeholder. Default empty. - `after_section`: HTML content to append to the section's HTML output. Default empty. - `section_class`: The class name to use for the section. Used by `before_section` if a `%s` placeholder is present. Default empty. The HTML passed using these extra arguments is escaped using `wp_kses_post()` just before rendering. This changeset also provides a set of unit tests for this new feature. Props griffinjt, nacin, scribu, ross_ritchey, ryan, chriscct7, palmiak, rehanali, costdev, martinkrcho, chaion07, audrasjb, hellofromtonya. Fixes #17851. git-svn-id: https://develop.svn.wordpress.org/trunk@54247 602fd350-edb4-49c9-b593-d223f7449a82
1 parent 0bdbce3 commit 1b3b794

2 files changed

Lines changed: 206 additions & 6 deletions

File tree

src/wp-admin/includes/template.php

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1561,6 +1561,7 @@ function do_accordion_sections( $screen, $context, $data_object ) {
15611561
* fields. It can output nothing if you want.
15621562
*
15631563
* @since 2.7.0
1564+
* @since 6.1.0 Added an `$args` parameter for the section's HTML wrapper and class name.
15641565
*
15651566
* @global array $wp_settings_sections Storage array of all settings sections added to admin pages.
15661567
*
@@ -1570,10 +1571,29 @@ function do_accordion_sections( $screen, $context, $data_object ) {
15701571
* @param string $page The slug-name of the settings page on which to show the section. Built-in pages include
15711572
* 'general', 'reading', 'writing', 'discussion', 'media', etc. Create your own using
15721573
* add_options_page();
1574+
* @param array $args {
1575+
* Arguments used to create the settings section.
1576+
*
1577+
* @type string $before_section HTML content to prepend to the section's HTML output.
1578+
* Receives the section's class name as `%s`. Default empty.
1579+
* @type string $after_section HTML content to append to the section's HTML output. Default empty.
1580+
* @type string $section_class The class name to use for the section. Default empty.
1581+
* }
15731582
*/
1574-
function add_settings_section( $id, $title, $callback, $page ) {
1583+
function add_settings_section( $id, $title, $callback, $page, $args = array() ) {
15751584
global $wp_settings_sections;
15761585

1586+
$defaults = array(
1587+
'id' => $id,
1588+
'title' => $title,
1589+
'callback' => $callback,
1590+
'before_section' => '',
1591+
'after_section' => '',
1592+
'section_class' => '',
1593+
);
1594+
1595+
$section = wp_parse_args( $args, $defaults );
1596+
15771597
if ( 'misc' === $page ) {
15781598
_deprecated_argument(
15791599
__FUNCTION__,
@@ -1600,11 +1620,7 @@ function add_settings_section( $id, $title, $callback, $page ) {
16001620
$page = 'reading';
16011621
}
16021622

1603-
$wp_settings_sections[ $page ][ $id ] = array(
1604-
'id' => $id,
1605-
'title' => $title,
1606-
'callback' => $callback,
1607-
);
1623+
$wp_settings_sections[ $page ][ $id ] = $section;
16081624
}
16091625

16101626
/**
@@ -1700,6 +1716,14 @@ function do_settings_sections( $page ) {
17001716
}
17011717

17021718
foreach ( (array) $wp_settings_sections[ $page ] as $section ) {
1719+
if ( '' !== $section['before_section'] ) {
1720+
if ( '' !== $section['section_class'] ) {
1721+
echo wp_kses_post( sprintf( $section['before_section'], esc_attr( $section['section_class'] ) ) );
1722+
} else {
1723+
echo wp_kses_post( $section['before_section'] );
1724+
}
1725+
}
1726+
17031727
if ( $section['title'] ) {
17041728
echo "<h2>{$section['title']}</h2>\n";
17051729
}
@@ -1714,6 +1738,10 @@ function do_settings_sections( $page ) {
17141738
echo '<table class="form-table" role="presentation">';
17151739
do_settings_fields( $page, $section['id'] );
17161740
echo '</table>';
1741+
1742+
if ( '' !== $section['after_section'] ) {
1743+
echo wp_kses_post( $section['after_section'] );
1744+
}
17171745
}
17181746
}
17191747

tests/phpunit/tests/template.php

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -456,6 +456,178 @@ public function test_embed_template_hierarchy_for_page() {
456456
);
457457
}
458458

459+
/**
460+
* @ticket 17851
461+
* @covers ::add_settings_section
462+
*/
463+
public function test_add_settings_section() {
464+
add_settings_section( 'test-section', 'Section title', '__return_false', 'test-page' );
465+
466+
global $wp_settings_sections;
467+
$this->assertIsArray( $wp_settings_sections, 'List of sections is not initialized.' );
468+
$this->assertArrayHasKey( 'test-page', $wp_settings_sections, 'List of sections for the test page has not been added to sections list.' );
469+
$this->assertIsArray( $wp_settings_sections['test-page'], 'List of sections for the test page is not initialized.' );
470+
$this->assertArrayHasKey( 'test-section', $wp_settings_sections['test-page'], 'Test section has not been added to the list of sections for the test page.' );
471+
472+
$this->assertEqualSetsWithIndex(
473+
array(
474+
'id' => 'test-section',
475+
'title' => 'Section title',
476+
'callback' => '__return_false',
477+
'before_section' => '',
478+
'after_section' => '',
479+
'section_class' => '',
480+
),
481+
$wp_settings_sections['test-page']['test-section'],
482+
'Test section data does not match the expected dataset.'
483+
);
484+
}
485+
486+
/**
487+
* @ticket 17851
488+
*
489+
* @param array $extra_args Extra arguments to pass to function `add_settings_section()`.
490+
* @param array $expected_section_data Expected set of section data.
491+
* @param string $expected_before_section_html Expected HTML markup to be rendered before the settings section.
492+
* @param string $expected_after_section_html Expected HTML markup to be rendered after the settings section.
493+
*
494+
* @covers ::add_settings_section
495+
* @covers ::do_settings_sections
496+
*
497+
* @dataProvider data_extra_args_for_add_settings_section
498+
*/
499+
public function test_add_settings_section_with_extra_args( $extra_args, $expected_section_data, $expected_before_section_html, $expected_after_section_html ) {
500+
add_settings_section( 'test-section', 'Section title', '__return_false', 'test-page', $extra_args );
501+
add_settings_field( 'test-field', 'Field title', '__return_false', 'test-page', 'test-section' );
502+
503+
global $wp_settings_sections;
504+
$this->assertIsArray( $wp_settings_sections, 'List of sections is not initialized.' );
505+
$this->assertArrayHasKey( 'test-page', $wp_settings_sections, 'List of sections for the test page has not been added to sections list.' );
506+
$this->assertIsArray( $wp_settings_sections['test-page'], 'List of sections for the test page is not initialized.' );
507+
$this->assertArrayHasKey( 'test-section', $wp_settings_sections['test-page'], 'Test section has not been added to the list of sections for the test page.' );
508+
509+
$this->assertEqualSetsWithIndex(
510+
$expected_section_data,
511+
$wp_settings_sections['test-page']['test-section'],
512+
'Test section data does not match the expected dataset.'
513+
);
514+
515+
ob_start();
516+
do_settings_sections( 'test-page' );
517+
$output = ob_get_clean();
518+
519+
$this->assertStringContainsString( $expected_before_section_html, $output, 'Test page output does not contain the custom markup to be placed before the section.' );
520+
$this->assertStringContainsString( $expected_after_section_html, $output, 'Test page output does not contain the custom markup to be placed after the section.' );
521+
}
522+
523+
/**
524+
* Data provider for `test_add_settings_section_with_extra_args()`.
525+
*
526+
* @return array
527+
*/
528+
public function data_extra_args_for_add_settings_section() {
529+
return array(
530+
'class placeholder section_class present' => array(
531+
array(
532+
'before_section' => '<div class="%s">',
533+
'after_section' => '</div><!-- end of the test section -->',
534+
'section_class' => 'test-section-wrap',
535+
),
536+
array(
537+
'id' => 'test-section',
538+
'title' => 'Section title',
539+
'callback' => '__return_false',
540+
'before_section' => '<div class="%s">',
541+
'after_section' => '</div><!-- end of the test section -->',
542+
'section_class' => 'test-section-wrap',
543+
),
544+
'<div class="test-section-wrap">',
545+
'</div><!-- end of the test section -->',
546+
),
547+
'missing class placeholder section_class' => array(
548+
array(
549+
'before_section' => '<div class="testing-section-wrapper">',
550+
'after_section' => '</div><!-- end of the test section -->',
551+
'section_class' => 'test-section-wrap',
552+
),
553+
array(
554+
'id' => 'test-section',
555+
'title' => 'Section title',
556+
'callback' => '__return_false',
557+
'before_section' => '<div class="testing-section-wrapper">',
558+
'after_section' => '</div><!-- end of the test section -->',
559+
'section_class' => 'test-section-wrap',
560+
),
561+
'<div class="testing-section-wrapper">',
562+
'</div><!-- end of the test section -->',
563+
),
564+
'empty section_class' => array(
565+
array(
566+
'before_section' => '<div class="test-section-container">',
567+
'after_section' => '</div><!-- end of the test section -->',
568+
'section_class' => '',
569+
),
570+
array(
571+
'id' => 'test-section',
572+
'title' => 'Section title',
573+
'callback' => '__return_false',
574+
'before_section' => '<div class="test-section-container">',
575+
'after_section' => '</div><!-- end of the test section -->',
576+
'section_class' => '',
577+
),
578+
'<div class="test-section-container">',
579+
'</div><!-- end of the test section -->',
580+
),
581+
'section_class missing' => array(
582+
array(
583+
'before_section' => '<div class="wp-whitelabel-section">',
584+
'after_section' => '</div><!-- end of the test section -->',
585+
),
586+
array(
587+
'id' => 'test-section',
588+
'title' => 'Section title',
589+
'callback' => '__return_false',
590+
'before_section' => '<div class="wp-whitelabel-section">',
591+
'after_section' => '</div><!-- end of the test section -->',
592+
'section_class' => '',
593+
),
594+
'<div class="wp-whitelabel-section">',
595+
'</div><!-- end of the test section -->',
596+
),
597+
'disallowed tag in before_section' => array(
598+
array(
599+
'before_section' => '<div class="video-settings-section"><iframe src="https://www.wordpress.org/" />',
600+
'after_section' => '</div><!-- end of the test section -->',
601+
),
602+
array(
603+
'id' => 'test-section',
604+
'title' => 'Section title',
605+
'callback' => '__return_false',
606+
'before_section' => '<div class="video-settings-section"><iframe src="https://www.wordpress.org/" />',
607+
'after_section' => '</div><!-- end of the test section -->',
608+
'section_class' => '',
609+
),
610+
'<div class="video-settings-section">',
611+
'</div><!-- end of the test section -->',
612+
),
613+
'disallowed tag in after_section' => array(
614+
array(
615+
'before_section' => '<div class="video-settings-section">',
616+
'after_section' => '</div><iframe src="https://www.wordpress.org/" />',
617+
),
618+
array(
619+
'id' => 'test-section',
620+
'title' => 'Section title',
621+
'callback' => '__return_false',
622+
'before_section' => '<div class="video-settings-section">',
623+
'after_section' => '</div><iframe src="https://www.wordpress.org/" />',
624+
'section_class' => '',
625+
),
626+
'<div class="video-settings-section">',
627+
'</div>',
628+
),
629+
);
630+
}
459631

460632
public function assertTemplateHierarchy( $url, array $expected, $message = '' ) {
461633
$this->go_to( $url );

0 commit comments

Comments
 (0)