# elasticpress/5.3.5/includes/classes/REST/Features.php

ElasticPress, version 5.3.5. 272 lines.

- Page: https://pluginprobe.com/plugins/elasticpress/5.3.5/code/includes/classes/REST/Features.php
- Raw: https://pluginprobe.com/plugins/elasticpress/5.3.5/raw/includes/classes/REST/Features.php
- Modified: 2026-05-07T18:19:10+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/elasticpress/5.3.5/code/includes/classes/REST/Features.php#L10-L20`.

```php
<?php
/**
 * Features REST API Controller
 *
 * @since 5.0.0
 * @package elasticpress
 */

namespace ElasticPress\REST;

use ElasticPress\Features as FeaturesStore;
use ElasticPress\Utils;

/**
 * Features API controller class.
 *
 * @since 5.0.0
 * @package elasticpress
 */
class Features {

	/**
	 * Register routes.
	 *
	 * @return void
	 */
	public function register_routes() {
		register_rest_route(
			'elasticpress/v1',
			'features',
			[
				[
					'callback'            => [ $this, 'get_features' ],
					'methods'             => 'GET',
					'permission_callback' => [ $this, 'check_permission' ],
				],
				[
					'args'                => $this->get_args(),
					'callback'            => [ $this, 'update_settings' ],
					'methods'             => 'PUT',
					'permission_callback' => [ $this, 'check_permission' ],
				],
			]
		);
	}

	/**
	 * Get args schema.
	 *
	 * @return array
	 */
	public function get_args() {
		$args = [];

		$features = \ElasticPress\Features::factory()->registered_features;

		foreach ( $features as $feature ) {
			$properties = [];

			$schema = $feature->get_settings_schema();

			foreach ( $schema as $schema ) {
				if ( ! isset( $schema['label'] ) ) {
					continue;
				}

				$type     = $schema['type'] ?? '';
				$property = [
					'description' => $schema['label'],
					'type'        => 'string',
				];

				switch ( $type ) {
					case 'select':
					case 'radio':
						$property['enum'] = array_map( fn( $o ) => $o['value'], $schema['options'] );
						break;
					case 'toggle':
						$property['type'] = 'boolean';
						break;
					case 'number':
						$property['type'] = 'number';
						break;
					case 'url':
						$property['type']   = 'string';
						$property['format'] = 'uri';
						break;
					case 'field_group':
						$property['type']       = 'object';
						$property['properties'] = [];
				}

				$properties[ $schema['key'] ] = $property;
			}

			$args[ $feature->slug ] = [
				'description' => $feature->get_title(),
				'properties'  => $properties,
				'type'        => 'object',
			];

			if ( method_exists( $feature, 'sanitize_settings_callback' ) ) {
				$args[ $feature->slug ]['sanitize_callback'] = [ $feature, 'sanitize_settings_callback' ];
			}
		}

		return $args;
	}

	/**
	 * Check that the request has permission to save features.
	 *
	 * @return boolean
	 */
	public function check_permission() {
		$capability = Utils\get_capability();

		return current_user_can( $capability );
	}

	/**
	 * Update features settings.
	 *
	 * @param \WP_REST_Request $request Full details about the request.
	 * @return array
	 */
	public function update_settings( \WP_REST_Request $request ) {
		if ( Utils\is_indexing() ) {
			wp_send_json_error( 'is_syncing', 400 );
			exit;
		}

		$current_settings = FeaturesStore::factory()->get_feature_settings();
		$new_settings     = $current_settings;

		$features = \ElasticPress\Features::factory()->registered_features;

		$settings_that_requires_features = [];

		foreach ( $features as $slug => $feature ) {
			$param = $request->get_param( $slug );

			if ( ! $param ) {
				continue;
			}

			if ( empty( $current_settings[ $slug ] ) ) {
				$current_settings[ $slug ] = [];
				$new_settings[ $slug ]     = [];
			}

			$schema = $feature->get_settings_schema();

			foreach ( $schema as $schema ) {
				$key  = $schema['key'];
				$type = $schema['type'] ?? '';

				if ( isset( $param[ $key ] ) ) {
					// Handle field group values
					if ( 'field_group' === $type ) {
						// Save the nested structure for the settings UI
						$new_settings[ $slug ][ $key ] = $param[ $key ];

						// Flatten the field group values into the main settings array
						foreach ( $schema['fields'] as $field ) {
							$field_key = $field['key'];
							if ( isset( $param[ $key ][ $field_key ] ) ) {
								$new_settings[ $slug ][ $field_key ] = $param[ $key ][ $field_key ];

								// Only apply to current settings if no sync required
								if ( empty( $schema['requires_sync'] ) ) {
									$current_settings[ $slug ][ $field_key ] = $param[ $key ][ $field_key ];
								}
							}
						}
					} else {
						$new_settings[ $slug ][ $key ] = $param[ $key ];
					}

					// Only apply to the current settings if does not require a sync or if it is activating it
					if ( ! empty( $schema['requires_sync'] ) && ! empty( $param[ $key ] ) ) {
						continue;
					}

					/*
					 * If a setting requires another feature, we have to check for it after running through everything,
					 * as it is possible that the feature will be active after this foreach.
					 */
					if ( empty( $schema['requires_feature'] ) ) {
						$current_settings[ $slug ][ $key ] = $param[ $key ];
					} else {
						if ( ! isset( $settings_that_requires_features[ $slug ] ) ) {
							$settings_that_requires_features[ $slug ] = [];
						}
						$settings_that_requires_features[ $slug ][ $key ] = [
							'required_feature' => $schema['requires_feature'],
							'value'            => $param[ $key ],
						];
					}
				}
			}
		}

		foreach ( $settings_that_requires_features as $feature => $fields ) {
			foreach ( $fields as $field_key => $field_data ) {
				$required_features = (array) $field_data['required_feature'];

				$all_required_active = true;
				foreach ( $required_features as $required_feature_slug ) {
					if ( empty( $current_settings[ $required_feature_slug ]['active'] ) ) {
						$all_required_active = false;
						break;
					}
				}

				if ( $all_required_active ) {
					$current_settings[ $feature ][ $field_key ] = $field_data['value'];
				}
			}
		}

		foreach ( $current_settings as $slug => $feature ) {
			FeaturesStore::factory()->update_feature( $slug, $feature );
		}

		foreach ( $new_settings as $slug => $feature ) {
			FeaturesStore::factory()->update_feature( $slug, $feature, true, 'draft' );
		}

		return [
			'success' => true,
		];
	}

	/**
	 * Return the current features payload along with persisted settings.
	 *
	 * @since 5.3.0
	 * @return array
	 */
	public function get_features() {
		$store = FeaturesStore::factory();

		$settings       = $store->get_feature_settings();
		$settings_draft = $store->get_feature_settings_draft();

		return [
			'features'      => $this->get_features_payload(),
			'settings'      => is_array( $settings ) ? $settings : [],
			'settingsDraft' => is_array( $settings_draft ) ? $settings_draft : null,
			'success'       => true,
		];
	}

	/**
	 * Build the serialized features payload.
	 *
	 * @since 5.3.0
	 * @return array
	 */
	protected function get_features_payload() {
		$features_objects = FeaturesStore::factory()->registered_features;

		foreach ( $features_objects as $feature ) {
			$feature->reset_settings_schema();
		}

		$features_data = array_map( fn( $feature ) => $feature->get_json(), $features_objects );
		return array_values( $features_data );
	}
}

```
