Skip to content

Commit 18823a0

Browse files
committed
Restore section badges
The old library badged sections as well as fields, and a badged section disabled everything in it. The port dropped both. The section's badge is copied onto its fields at construction rather than resolved at render time, so every path downstream — rendering, sanitizing, saving — sees an ordinary badged field and needs no special case. That matters more than it looks: a section that locked only visually would have had its values wiped by the next save, because a disabled control sends nothing and the save path reads absence as cleared. A field's own badge wins over its section's; it is the more specific statement. 48 tests, 102 assertions. Verified on the live layout tab: four badges, three locked fields.
1 parent 74b28a8 commit 18823a0

2 files changed

Lines changed: 150 additions & 1 deletion

File tree

‎src/SettingFields.php‎

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
use ArrayPress\FieldKit\Contracts\Context;
2121
use ArrayPress\FieldKit\Field;
2222
use ArrayPress\FieldKit\FieldSet;
23+
use ArrayPress\FieldKit\Support\Badge;
2324
use ArrayPress\FieldKit\Support\PageHeader;
2425

2526
/**
@@ -112,6 +113,7 @@ public function __construct( string $id, array $config ) {
112113
$this->fields = (array) ( $config['fields'] ?? [] );
113114
$this->tabs = $this->parse_tabs( (array) ( $config['tabs'] ?? [] ) );
114115
$this->sections = (array) ( $config['sections'] ?? [] );
116+
$this->fields = $this->apply_section_badges( $this->fields );
115117

116118
$this->options = new OptionContext( (string) $this->config['option_name'] );
117119
$this->context = $this->decorate( $this->options );
@@ -134,6 +136,40 @@ public function __construct( string $id, array $config ) {
134136
add_action( 'admin_post_' . $this->action_slug( 'reset' ), [ $this, 'handle_reset' ] );
135137
}
136138

139+
/**
140+
* Copy a badged section's badge onto each of its fields.
141+
*
142+
* A section badge locks the whole section. Resolving it here rather than
143+
* at render time means every path downstream — rendering, sanitizing,
144+
* saving — sees an ordinary badged field and needs no special case. In
145+
* particular the save path skips a locked field, and a section that
146+
* locked only visually would have had its values cleared by the next save
147+
* because a disabled control sends nothing.
148+
*
149+
* A field's own badge wins: it is the more specific statement.
150+
*
151+
* @param array<string, array<string, mixed>> $fields Field configuration.
152+
*
153+
* @return array<string, array<string, mixed>>
154+
*/
155+
private function apply_section_badges( array $fields ): array {
156+
foreach ( $this->sections as $slug => $section ) {
157+
if ( null === Badge::resolve( $section['badge'] ?? null ) ) {
158+
continue;
159+
}
160+
161+
foreach ( $fields as $key => $field ) {
162+
if ( (string) ( $field['section'] ?? '' ) !== (string) $slug || isset( $field['badge'] ) ) {
163+
continue;
164+
}
165+
166+
$fields[ $key ]['badge'] = $section['badge'];
167+
}
168+
}
169+
170+
return $fields;
171+
}
172+
137173
/**
138174
* Wrap a store in this page's decorators.
139175
*
@@ -594,7 +630,14 @@ private function render_sections( string $tab ): void {
594630

595631
$shown = array_merge( $shown, array_keys( $in_section ) );
596632

597-
printf( '<h2 class="title">%s</h2>', esc_html( (string) ( $section['title'] ?? $slug ) ) );
633+
$badge = Badge::resolve( $section['badge'] ?? null );
634+
635+
printf(
636+
'<h2 class="title">%s%s</h2>',
637+
esc_html( (string) ( $section['title'] ?? $slug ) ),
638+
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- escaped as it is built.
639+
null === $badge ? '' : Badge::render( $badge )
640+
);
598641

599642
if ( '' !== (string) ( $section['description'] ?? '' ) ) {
600643
printf( '<p class="description">%s</p>', wp_kses_post( (string) $section['description'] ) );

‎tests/SettingFieldsTest.php‎

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -619,6 +619,112 @@ public function test_the_form_carries_the_tab_it_is_saving(): void {
619619
);
620620
}
621621

622+
/**
623+
* A badged section badges and locks every field in it.
624+
*
625+
* Locking a section only visually would be worse than not locking it: a
626+
* disabled control sends nothing, so the next save would read every field
627+
* in the section as cleared and wipe values the install had configured
628+
* while it still had the feature.
629+
*/
630+
public function test_a_badged_section_locks_its_fields(): void {
631+
$page = $this->page(
632+
[
633+
'sections' => [
634+
'premium' => [
635+
'title' => 'Premium',
636+
'tab' => 'one',
637+
'badge' => 'Pro',
638+
],
639+
],
640+
'fields' => [
641+
'locked' => [
642+
'type' => 'text',
643+
'tab' => 'one',
644+
'section' => 'premium',
645+
],
646+
'free' => [
647+
'type' => 'text',
648+
'tab' => 'one',
649+
],
650+
],
651+
]
652+
);
653+
654+
$html = $this->render( $page );
655+
656+
// Once in the section heading, once on the field it locked.
657+
$this->assertSame( 2, substr_count( $html, 'field-kit__badge' ) );
658+
$this->assertStringContainsString( 'field-kit__field--locked', $html );
659+
660+
// And its stored value survives a save that cannot include it.
661+
$GLOBALS['fk_options']['sf_demo'] = [ 'locked' => 'set-while-licensed' ];
662+
663+
$this->submit( $page, 'one', [ 'free' => 'y' ] );
664+
665+
$this->assertSame( 'set-while-licensed', $page->get_value( 'locked' ) );
666+
$this->assertSame( 'y', $page->get_value( 'free' ) );
667+
}
668+
669+
/**
670+
* A field's own badge wins over its section's.
671+
*/
672+
public function test_a_field_badge_beats_its_sections(): void {
673+
$page = $this->page(
674+
[
675+
'sections' => [
676+
'premium' => [
677+
'title' => 'Premium',
678+
'tab' => 'one',
679+
'badge' => 'Pro',
680+
],
681+
],
682+
'fields' => [
683+
'specific' => [
684+
'type' => 'text',
685+
'tab' => 'one',
686+
'section' => 'premium',
687+
'badge' => 'Business',
688+
],
689+
],
690+
]
691+
);
692+
693+
$this->assertStringContainsString( '>Business<', $this->render( $page ) );
694+
}
695+
696+
/**
697+
* A suppressed section badge locks nothing.
698+
*/
699+
public function test_a_suppressed_section_badge_locks_nothing(): void {
700+
$page = $this->page(
701+
[
702+
'sections' => [
703+
'premium' => [
704+
'title' => 'Premium',
705+
'tab' => 'one',
706+
'badge' => [
707+
'text' => 'Pro',
708+
'disabled' => true,
709+
],
710+
],
711+
],
712+
'fields' => [
713+
'field' => [
714+
'type' => 'text',
715+
'tab' => 'one',
716+
'section' => 'premium',
717+
],
718+
],
719+
]
720+
);
721+
722+
$html = $this->render( $page );
723+
724+
$this->assertStringNotContainsString( 'field-kit__badge', $html );
725+
$this->assertStringNotContainsString( 'field-kit__field--locked', $html );
726+
}
727+
622728
/**
623729
* A field with no section still appears.
624730
*/

0 commit comments

Comments
 (0)