list( $label, $control, $help ) ) {
printf(
'
%1$s%2$s%3$s
',
// The title of a group of boxes is text on its own line, as a label is on this form.
self::GROUP === $id ? '' . esc_html( $label ) . ' ' : self::label( $id, $label ), // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Escaped in label().
$control, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Built from escaped values in fields().
'' !== $help ? '' . esc_html( $help ) . '
' : ''
);
}
}
/**
* Prints the fields on the form that edits a board.
*
* @param WP_Term $board Board being edited.
*/
public static function render_on_edit( WP_Term $board ): void {
foreach ( self::fields( BoardSettings::get( $board->term_id ) ) as $id => list( $label, $control, $help ) ) {
printf(
'%1$s %2$s%3$s ',
self::GROUP === $id ? esc_html( $label ) : self::label( $id, $label ), // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Escaped in label().
$control, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Built from escaped values in fields().
'' !== $help ? '' . esc_html( $help ) . '
' : ''
);
}
// Inside a table cell, the only place this form allows more fields.
echo '';
wp_nonce_field( self::NONCE_ACTION, self::NONCE_FIELD );
echo ' ';
}
/**
* Saves the fields when a board is added or edited from its form.
*
* Boards are also created and updated by the REST API, WP-CLI and other code. Those
* send no fields, and must leave the board's settings as they are.
*
* @param int $board_id Board term ID.
*/
public static function save( int $board_id ): void {
$nonce = isset( $_POST[ self::NONCE_FIELD ] ) ? sanitize_text_field( wp_unslash( $_POST[ self::NONCE_FIELD ] ) ) : '';
if ( ! wp_verify_nonce( $nonce, self::NONCE_ACTION ) || ! current_user_can( 'edit_term', $board_id ) ) {
return;
}
$sent = isset( $_POST[ self::FIELD ] ) && is_array( $_POST[ self::FIELD ] ) ? map_deep( wp_unslash( $_POST[ self::FIELD ] ), 'sanitize_text_field' ) : [];
BoardSettings::update(
$board_id,
[
BoardSettings::VOTING => $sent[ BoardSettings::VOTING ] ?? '',
BoardSettings::SUBMITTING => $sent[ BoardSettings::SUBMITTING ] ?? '',
// An unticked box sends nothing.
BoardSettings::MODERATION => ! empty( $sent[ BoardSettings::MODERATION ] ),
BoardSettings::PAGE => absint( $sent[ BoardSettings::PAGE ] ?? 0 ),
// No ticked box sends nothing, which means every category.
BoardSettings::CATEGORIES => isset( $sent[ BoardSettings::CATEGORIES ] ) && is_array( $sent[ BoardSettings::CATEGORIES ] ) ? $sent[ BoardSettings::CATEGORIES ] : [],
]
);
}
/**
* The fields, by HTML id: their label, their control and their help text.
*
* @param array $settings Settings to show.
* @return array
*/
private static function fields( array $settings ): array {
$who = [
BoardSettings::ANYONE => __( 'Everyone, including visitors without an account', 'feature-request-system' ),
BoardSettings::LOGGED_IN => __( 'Logged-in users', 'feature-request-system' ),
BoardSettings::NOBODY => __( 'Nobody', 'feature-request-system' ),
];
$page = (string) wp_dropdown_pages(
[
'echo' => 0,
'name' => esc_attr( self::name( BoardSettings::PAGE ) ),
'id' => 'mwtfrs-board-page',
'selected' => (int) $settings[ BoardSettings::PAGE ],
'show_option_none' => esc_html__( '— Not set —', 'feature-request-system' ),
'option_none_value' => '0',
]
);
return [
'mwtfrs-board-voting' => [
__( 'Who can vote', 'feature-request-system' ),
self::select( 'mwtfrs-board-voting', BoardSettings::VOTING, $who, (string) $settings[ BoardSettings::VOTING ] ),
'',
],
'mwtfrs-board-submitting' => [
__( 'Who can suggest features', 'feature-request-system' ),
self::select( 'mwtfrs-board-submitting', BoardSettings::SUBMITTING, $who, (string) $settings[ BoardSettings::SUBMITTING ] ),
'',
],
'mwtfrs-board-moderation' => [
__( 'New requests', 'feature-request-system' ),
sprintf(
' %3$s ',
esc_attr( self::name( BoardSettings::MODERATION ) ),
checked( (bool) $settings[ BoardSettings::MODERATION ], true, false ),
esc_html__( 'Hold new requests until a moderator approves them', 'feature-request-system' )
),
__( 'Requests from editors and administrators are always published straight away.', 'feature-request-system' ),
],
'mwtfrs-board-page' => [
__( 'Board page', 'feature-request-system' ),
// No pages yet: nothing to choose from.
'' !== $page ? $page : esc_html__( 'No pages yet.', 'feature-request-system' ),
__( 'The page that shows this board. Each request links back to it. Filled in by itself the first time the board is shown on a page.', 'feature-request-system' ),
],
self::GROUP => [
__( 'Categories', 'feature-request-system' ),
self::category_boxes( array_map( 'intval', (array) $settings[ BoardSettings::CATEGORIES ] ) ),
__( 'The categories that people can choose for a request on this board, and that the board can be filtered by. Tick none to offer every category.', 'feature-request-system' ),
],
];
}
/**
* Returns a checkbox for every category.
*
* @param int[] $chosen Term IDs of the ticked categories.
*/
private static function category_boxes( array $chosen ): string {
$categories = get_terms(
[
'taxonomy' => CategoryTaxonomy::NAME,
'hide_empty' => false,
'orderby' => 'name',
]
);
if ( ! is_array( $categories ) || ! $categories ) {
return esc_html__( 'No categories yet.', 'feature-request-system' );
}
$html = '' . esc_html__( 'Categories', 'feature-request-system' ) . ' ';
foreach ( $categories as $category ) {
$html .= sprintf(
' %4$s ',
esc_attr( self::name( BoardSettings::CATEGORIES ) ),
(int) $category->term_id,
checked( in_array( $category->term_id, $chosen, true ), true, false ),
esc_html( $category->name )
);
}
return $html . ' ';
}
/**
* Returns a field's label.
*
* @param string $id HTML id of the field.
* @param string $label Label text.
*/
private static function label( string $id, string $label ): string {
return sprintf( '%2$s ', esc_attr( $id ), esc_html( $label ) );
}
/**
* Returns a dropdown.
*
* @param string $id HTML id.
* @param string $setting Setting the dropdown is for.
* @param array $options Labels by value.
* @param string $selected Selected value.
*/
private static function select( string $id, string $setting, array $options, string $selected ): string {
$html = sprintf( '', esc_attr( $id ), esc_attr( self::name( $setting ) ) );
foreach ( $options as $value => $label ) {
$html .= sprintf( '%3$s ', esc_attr( $value ), selected( $selected, $value, false ), esc_html( $label ) );
}
return $html . ' ';
}
/**
* Name of a setting's form field.
*
* @param string $setting Setting key.
*/
private static function name( string $setting ): string {
return self::FIELD . '[' . $setting . ']';
}
}