Working with settings in WordPress is usually pretty straight forward with the settings API. However, if you’re using multiselect fields, there is a problem. Since the selection on a multiselect field can be completely empty, it makes deleting the last value impossible by default.

But why is that? If you select at least one value of a multiselect field (a <select> with a multiple attribute), this value (or list of values) will be part of $_POST by submitting the setting and thus will be updated in the database by the settings API.

However, if you deselect all values of the multiselect field afterwards and submit, there is no entry in $_POST (since empty fields are omitted) and thus the field is left as is by WordPress, since it doesn’t know that this field should be changed.

Unfortunately, there is no easy filter to tell WordPress which fields to check for changed values, and thus we need to trick a little bit here. The only filter that runs in this state is allowed_options, checking for whether the current given $_POST fields are allowed to be updated. So I (mis-)use this filter and delete the option if it is registered via register_setting, but is not part of the $_POST fields:

/**
 * Delete all empty settings.
 * 
 * @param	array	$allowed_options List of allowed options
 * @return	array List of allowed options
 */
static function delete_empty_settings( array $allowed_options ): array {
	global $wp_settings_fields;
	
	// phpcs:disable WordPress.Security.NonceVerification.Missing
	if (
		! isset( $_POST['option_page'] )
		|| ! isset( $_POST['action'] )
		|| \sanitize_text_field( \wp_unslash( $_POST['option_page'] ) ) !== 'product_settings'
		|| \sanitize_text_field( \wp_unslash( $_POST['action'] ) ) !== 'update'
	) {
		return $allowed_options;
	}
	
	foreach ( $wp_settings_fields['product_settings'] as $settings_fields ) {
		foreach ( \array_keys( $settings_fields ) as $field_name ) {
			if ( ! isset( $_POST[ $field_name ] ) ) {
				\delete_option( $field_name );
			}
		}
	}
	// phpcs:enable WordPress.Security.NonceVerification.Missing
	
	return $allowed_options;
}
Code language: PHP (php)

In line 14, I check whether my custom option page is the one the request comes from. This needs to be adjusted according to your settings page. The same applies to the key in the global $wp_settings_fields in line 20.

The code then iterates through all fields and checks whether they are part of the $_POST fields. If not, they will be deleted.

The $allowed_options always stay untouched. The filter is just used to be able to use the settings update request to run the code.

By the way: the ignored nonce verification is on purpose. The filter only runs after a successful nonce check in /wp-admin/options.php and multiple nonce verifications are not supported by WordPress.

Leave a Reply

Your email address will not be published. Required fields are marked *